# 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 应用程序需要一些特殊的东西。当开箱即用的用户存储不能满足需求时，需要创建并配置一个定制的用户详细信息服务。


---

# 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-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?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.
