> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v5/di-4-zhang-spring-an-quan/4.2-pei-zhi-spring-security/4.2.3-ldap-zhi-chi-de-yong-hu-cun-chu.md).

# 4.2.3 LDAP 支持的用户存储

要为基于 LDAP 的身份验证配置 Spring Security，可以使用 ldapAuthentication() 方法。这个方法与 jdbcAuthentication() 类似。下面的 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() 方法提供了查找用户的基本查询。同样，groupSearchBase() 方法指定查找组的基本查询。这个示例不是从根目录进行搜索，而是指定要搜索用户所在的组织单元是 people，组应该搜索组织单元所在的 group。

**配置密码比较**

针对 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 BCryptPasswordEncoder())
            .passwordAttribute("passcode");
}
```

在本例中，指定密码属性应该与给定的密码进行比较。此外，还可以指定密码编码器，在进行服务器端密码比较时，最好在服务器端对实际密码加密。但是尝试的密码仍然会通过网络传递到 LDAP 服务器，并且可能被黑客截获。为了防止这种情况，可以通过调用 passwordEncoder() 方法来指定加密策略。

在前面的示例中，使用 bcrypt 密码散列函数对密码进行加密，这里的前提是密码在 LDAP 服务器中也是使用 bcrypt 加密的。

**引用远程 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}")
            .passwordCompare()
            .passwordEncoder(new BCryptPasswordEncoder())
            .passwordAttribute("passcode")
            .contextSource()
                .url("ldap://tacocloud.com:389/dc=tacocloud,dc=com");
}
```

contextSource() 方法返回 ContextSourceBuilder，其中提供了 url() 方法，它允许指定 LDAP 服务器的位置。

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

如果没有 LDAP 服务器去做身份验证，Spring Security 可提供一个嵌入式 LDAP 服务器。可以通过 root() 方法为嵌入式服务器指定根后缀，而不是将 URL 设置为远程 LDAP 服务器：

```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 BCryptPasswordEncoder())
            .passwordAttribute("passcode")
            .contextSource()
                .root("dc=tacocloud,dc=com");
}
```

当 LDAP 服务器启动时，它将尝试从类路径中找到的任何 LDIF 文件进行数据加载。LDIF（LDAP 数据交换格式）是在纯文本文件中表示 LDAP 数据的标准方法，每个记录由一个或多个行组成，每个行包含一个 name:value 对，记录之间用空行分隔。

如果不希望 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}")
            .passwordCompare()
            .passwordEncoder(new BCryptPasswordEncoder())
            .passwordAttribute("passcode")
            .contextSource()
                .root("dc=tacocloud,dc=com")
                .ldif("classpath:users.ldif");
}
```

这里，特别要求 LDAP 服务器从位于根路径下的 users.ldif 文件中加载数据。如果你感兴趣，这里有一个LDIF 文件，你可以使用它来加载内嵌 LDAP 服务器的用户数据：

```
dn: ou=groups,dc=tacocloud,dc=com
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=people,dc=tacocloud,dc=com
objectclass: top
objectclass: organizationalUnit
ou: people
dn: uid=buzz,ou=people,dc=tacocloud,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Buzz Lightyear
sn: Lightyear
uid: buzz
userPassword: password
dn: cn=tacocloud,ou=groups,dc=tacocloud,dc=com
objectclass: top
objectclass: groupOfNames
cn: tacocloud
member: uid=buzz,ou=people,dc=tacocloud,dc=com
```

Spring Security 的内置用户存储非常方便，涵盖了一些常见的用例。但是 Taco Cloud 应用程序需要一些特殊的东西。当开箱即用的用户存储不能满足需求时，需要创建并配置一个定制的用户详细信息服务。
