# 9.2.3　基于 LDAP 进行认证

为了让 Spring Security 使用基于 LDAP 的认证，我们可以使用 ldapAuthentication() 方法。这个方法在功能上类似于 jdbcAuthentication()，只不过是 LDAP 版本。如下的 configure() 方法展现了 LDAP 认证的简单配置：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchFilter("{uid={0}}")
    .groupSearchFilter("member={0}");
}
```

方法 userSearchFilter() 和 groupSearchFilter() 用来为基础 LDAP 查询提供过滤条件，它们分别用于搜索用户和组。默认情况下，对于用户和组的基础查询都是空的，也就是表明搜索会在 LDAP 层级结构的根开始。但是我们可以通过指定查询基础来改变这个默认行为：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchBase("ou=people");
    .userSearchFilter("{uid={0}}")
    .groupSearchBase("ou=groups");
    .groupSearchFilter("member={0}");
}
```

userSearchBase() 属性为查找用户提供了基础查询。同样，groupSearch-Base() 为查找组指定了基础查询。我们声明用户应该在名为 people 的组织单元下搜索而不是从根开始。而组应该在名为 groups 的组织单元下搜索。

**配置密码比对**

基于 LDAP 进行认证的默认策略是进行绑定操作，直接通过 LDAP 服务器认证用户。另一种可选的方式是进行比对操作。这涉及将输入的密码发送到 LDAP 目录上，并要求服务器将这个密码和用户的密码进行比对。因为比对是在 LDAP 服务器内完成的，实际的密码能保持私密。 如果你希望通过密码比对进行认证，可以通过声明 passwordCompare() 方法来实现：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchBase("ou=people");
    .userSearchFilter("{uid={0}}")
    .groupSearchBase("ou=groups");
    .groupSearchFilter("member={0}")
    .passwordCompare();
}
```

默认情况下，在登录表单中提供的密码将会与用户的 LDAP 条目中的 userPassword 属性进行比对。如果密码被保存在不同的属性中，可以通过 passwordAttribute() 方法来声明密码属性的名称：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchBase("ou=people");
    .userSearchFilter("{uid={0}}")
    .groupSearchBase("ou=groups");
    .groupSearchFilter("member={0}")
    .passwordCompare()
    .passwordEncoder(new Md5PasswordEncoder())
    .passwordAttribute("passcode");
}
```

在本例中，我们指定了要与给定密码进行比对的是“passcode”属性。另外，我们还可以指定密码转码器。在进行服务器端密码比对时，有一点非常好，那就是实际的密码在服务器端是私密的。但是进行尝试的密码还是需要通过线路传输到 LDAP 服务器上，这可能会被黑客所拦截。为了避免这一点，我们可以通过调用 passwordEncoder() 方法指定加密策略。

在本示例中，密码会进行 MD5 加密。这需要 LDAP 服务器上密码也使用 MD5 进行加密。

**引用远程的 LDAP 服务器**

到目前为止，我们忽略的一件事就是 LDAP 和实际的数据在哪里。我们很开心地配置 Spring 使用 LDAP 服务器进行认证，但是服务器在哪里呢？

默认情况下，Spring Security 的 LDAP 认证假设 LDAP 服务器监听本机的 33389 端口。但是，如果你的 LDAP 服务器在另一台机器上，那么可以使用 contextSource() 方法来配置这个地址：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchBase("ou=people");
    .userSearchFilter("{uid={0}}")
    .groupSearchBase("ou=groups");
    .groupSearchFilter("member={0}")
    .contextSource()
    .url("ldap://habuma.com:389/dc=habuma,dc=com");
}
```

contextSource() 方法会返回一个 ContextSourceBuilder 对象，这个对象除了其他功能以外，还提供了 url() 方法用来指定 LDAP 服务器的地址。

**配置嵌入式的 LDAP 服务器**

如果你没有现成的 LDAP 服务器供认证使用，Spring Security 还为我们提供了嵌入式的 LDAP 服务器。我们不再需要设置远程 LDAP 服务器的 URL，只需通过 root() 方法指定嵌入式服务器的根前缀就可以了：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchBase("ou=people");
    .userSearchFilter("{uid={0}}")
    .groupSearchBase("ou=groups");
    .groupSearchFilter("member={0}")
    .contextSource()
    .root("dc=habuma,dc=com");
}
```

当 LDAP 服务器启动时，它会尝试在类路径下寻找 LDIF 文件来加载数据。LDIF（LDAP Data Interchange Format，LDAP数据交换格式）是以文本文件展现 LDAP 数据的标准方式。每条记录可以有一行或多行，每项包含一个名值对。记录之间通过空行进行分割。

如果你不想让 Spring 从整个根路径下搜索 LDIF 文件的话，那么可以通过调用 ldif() 方法来明确指定加载哪个 LDIF 文件：

```java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth
    .ldapAuthentication()
    .userSearchBase("ou=people");
    .userSearchFilter("{uid={0}}")
    .groupSearchBase("ou=groups");
    .groupSearchFilter("member={0}")
    .contextSource()
    .root("dc=habuma,dc=com")
    .ldif("classpath:users.ldif");
}
```

在这里，我们明确要求 LDAP 服务器从类路径根目录下的 users.ldif 文件中加载内容。如果你比较好奇的话，如下就是一个包含用户数据 LDIF 文件，我们可以使用它来加载嵌入式 LDAP 服务器：

```
dn: ou=groups,dc=habuma,dc=com 
objectclass: top 
objectclass: organizationalUnit 
ou: groups
dn: ou=people,dc=habuma,dc=com 
objectclass: top 
objectclass: organizationalUnit 
ou: people
dn: uid=habuma, ou=people, dc=hab\oma, dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Craig Walls
sn: Walls
uid: habuma
userPassword: password
dn: uid=jsmith,ou=people,dc=habuma,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: John Smith
sn: Smith
uid: jsmith
userPassword: password
dn: cn=spittr,outgroups,dc=habuma,dc=com
objectclass: top
objectclass: groupOfNames
cn: spittr
member: uid=habuma,ou=people,dc=habuma,dc=com
```

Spring Security 内置的用户存储非常便利，并且涵盖了最为常用的用户场景。但是，如果你的认证需求不是那么通用的话，那么就需要创建并配置自定义的用户详细信息服务了。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://potoyang.gitbook.io/spring-in-action-v4/untitled-3/untitled-4/untitled-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
