# LDAP 身份验证

LDAP 通常被组织用作用户信息的中心存储库和身份验证服务。它还可以用于存储应用程序用户的角色信息。

Spring 当安全性被配置为接受用户名/密码以进行身份验证时,安全性使用基于 LDAP 的身份验证。然而,尽管利用用户名/密码进行身份验证,但它并未使用UserDetailsService进行集成,因为在绑定身份验证中,LDAP 服务器不返回密码,因此应用程序无法对密码进行验证。

对于如何配置 LDAP 服务器,有许多不同的场景,因此 Spring Security 的 LDAP 提供者是完全可配置的。它使用独立的策略接口来进行身份验证和角色检索,并提供可配置为处理各种情况的默认实现。

# 先决条件

在尝试使用 Spring 安全性之前,你应该熟悉 LDAP。下面的链接很好地介绍了所涉及的概念,并提供了使用免费的 LDAP 服务器 OpenLDAP 设置目录的指南:https://www.zytrax.com/books/ldap/ (opens new window)。熟悉一些用于从 爪哇 访问 LDAP 的 JNDI API 也可能是有用的。在 LDAP 提供程序中,我们不使用任何第三方 LDAP 库(Mozilla、JLDAP 等),但是大量使用了 Spring LDAP,因此,如果你计划添加自己的定制,那么熟悉该项目可能会很有用。

在使用 LDAP 身份验证时,确保正确配置 LDAP 连接池非常重要。如果你不熟悉如何做到这一点,你可以参考爪哇 LDAP 文档 (opens new window)

# 设置嵌入式 LDAP 服务器

你需要做的第一件事是确保你有一个 LDAP 服务器来指向你的配置。为了简单起见,通常最好从嵌入式 LDAP 服务器开始。 Spring 安全支持使用以下两种方式之一:

在下面的示例中,我们以users.ldif作为 Classpath 资源公开以下内容,以初始化嵌入的 LDAP 服务器,其中用户useradmin的密码都为password

users.ldif

dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password

dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password

dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org

dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org

# 嵌入式无 boundid 服务器

如果你希望使用UnboundID (opens new window),那么请指定以下依赖项:

例 1。无边界依赖项

Maven

<dependency>
	<groupId>com.unboundid</groupId>
	<artifactId>unboundid-ldapsdk</artifactId>
	<version>4.0.14</version>
	<scope>runtime</scope>
</dependency>

Gradle

depenendencies {
	runtimeOnly "com.unboundid:unboundid-ldapsdk:4.0.14"
}

然后就可以配置嵌入式 LDAP 服务器了。

例 2。嵌入式 LDAP 服务器配置

爪哇

@Bean
UnboundIdContainer ldapContainer() {
	return new UnboundIdContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}

XML

<b:bean class="org.springframework.security.ldap.server.UnboundIdContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>

Kotlin

@Bean
fun ldapContainer(): UnboundIdContainer {
    return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif")
}

# 嵌入式 ApacheDS 服务器

Spring 安全性使用的是不再维护的 ApacheDS1.x。
遗憾的是,ApacheDS2.x 只发布了里程碑版本,没有稳定的发布。
一旦稳定的 ApacheDS2.x 版本可用,我们将考虑更新。

如果你希望使用Apache DS (opens new window),那么请指定以下依赖项:

例 3。Apacheds 依赖项

Maven

<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-core</artifactId>
	<version>1.5.5</version>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>org.apache.directory.server</groupId>
	<artifactId>apacheds-server-jndi</artifactId>
	<version>1.5.5</version>
	<scope>runtime</scope>
</dependency>

Gradle

depenendencies {
	runtimeOnly "org.apache.directory.server:apacheds-core:1.5.5"
	runtimeOnly "org.apache.directory.server:apacheds-server-jndi:1.5.5"
}

然后就可以配置嵌入式 LDAP 服务器了。

例 4。嵌入式 LDAP 服务器配置

爪哇

@Bean
ApacheDSContainer ldapContainer() {
	return new ApacheDSContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}

XML

<b:bean class="org.springframework.security.ldap.server.ApacheDSContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>

Kotlin

@Bean
fun ldapContainer(): ApacheDSContainer {
    return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif")
}

# LDAP ContextSource

一旦你有了一个 LDAP 服务器来指向你的配置,你就需要配置 Spring 安全性来指向一个应该用来验证用户身份的 LDAP 服务器。这是通过创建一个 LDAPContextSource来完成的,它相当于一个 JDBCDataSource

例 5。LDAP 上下文源代码

爪哇

ContextSource contextSource(UnboundIdContainer container) {
	return new DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org");
}

XML

<ldap-server
	url="ldap://localhost:53389/dc=springframework,dc=org" />

Kotlin

fun contextSource(container: UnboundIdContainer): ContextSource {
    return DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org")
}

# 认证

Spring Security 的 LDAP 支持不使用UserDetailsService,因为 LDAP BIND 身份验证不允许客户端读取密码,甚至不允许读取密码的散列版本。这意味着不可能读取密码,然后由 Spring Security 进行身份验证。

因此,LDAP 支持是使用LdapAuthenticator接口实现的。LdapAuthenticator还负责检索任何必需的用户属性。这是因为属性的权限可能取决于所使用的身份验证类型。例如,如果绑定为用户,则可能需要使用用户自己的权限来读取它们。

有两个LdapAuthenticator实现提供了 Spring 安全性:

# 使用绑定身份验证

绑定身份验证 (opens new window)是使用 LDAP 对用户进行身份验证的最常见机制。在 BIND 身份验证中,用户凭据(即用户名/密码)被提交给 LDAP 服务器,由该服务器对其进行身份验证。使用绑定身份验证的好处是,用户的秘密(即密码)不需要暴露给客户端,这有助于保护它们不被泄露。

可以在下面找到绑定身份验证配置的示例。

例 6。绑定身份验证

爪哇

@Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	BindAuthenticator authenticator = new BindAuthenticator(contextSource);
	authenticator.setUserDnPatterns(new String[] { "uid={0},ou=people" });
	return authenticator;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}

XML

<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"/>

Kotlin

@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): BindAuthenticator {
    val authenticator = BindAuthenticator(contextSource)
    authenticator.setUserDnPatterns(arrayOf("uid={0},ou=people"))
    return authenticator
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}

这个简单的示例将通过在提供的模式中替换用户登录名并尝试将该用户与登录密码绑定为该用户来获得用户的 DN。如果你的所有用户都存储在目录中的一个节点下,则可以这样做。如果你希望配置一个 LDAP 搜索过滤器来定位用户,那么可以使用以下方法:

例 7。用搜索筛选器绑定身份验证

爪哇

@Bean
BindAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	String searchBase = "ou=people";
	String filter = "(uid={0})";
	FilterBasedLdapUserSearch search =
		new FilterBasedLdapUserSearch(searchBase, filter, contextSource);
	BindAuthenticator authenticator = new BindAuthenticator(contextSource);
	authenticator.setUserSearch(search);
	return authenticator;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}

XML

<ldap-authentication-provider
		user-search-filter="(uid={0})"
	user-search-base="ou=people"/>

Kotlin

@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): BindAuthenticator {
    val searchBase = "ou=people"
    val filter = "(uid={0})"
    val search = FilterBasedLdapUserSearch(searchBase, filter, contextSource)
    val authenticator = BindAuthenticator(contextSource)
    authenticator.setUserSearch(search)
    return authenticator
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}

如果与ContextSource以上定义一起使用,则将在 dnou=people,dc=springframework,dc=org下使用(uid={0})作为过滤器执行搜索。同样,用户登录名被过滤器名中的参数所代替,因此它将搜索具有uid属性的条目,该属性等于用户名。如果没有提供用户搜索库,搜索将从根目录执行。

# 使用密码身份验证

密码比较是指将用户提供的密码与存储库中存储的密码进行比较。这可以通过检索 Password 属性的值并在本地进行检查来完成,也可以通过执行 LDAP“Compare”操作来完成,在该操作中,提供的密码被传递给服务器进行比较,而不会检索到真正的密码值。当密码被随机 SALT 正确散列时,无法进行 LDAP 比较。

例 8。最小密码比较配置

爪哇

@Bean
PasswordComparisonAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	return new PasswordComparisonAuthenticator(contextSource);
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}

XML

<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare />
</ldap-authentication-provider>

Kotlin

@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): PasswordComparisonAuthenticator {
    return PasswordComparisonAuthenticator(contextSource)
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}

可以在下面找到带有一些自定义功能的更高级配置。

例 9。密码比较配置

爪哇

@Bean
PasswordComparisonAuthenticator authenticator(BaseLdapPathContextSource contextSource) {
	PasswordComparisonAuthenticator authenticator =
		new PasswordComparisonAuthenticator(contextSource);
	authenticator.setPasswordAttributeName("pwd"); (1)
	authenticator.setPasswordEncoder(new BCryptPasswordEncoder()); (2)
	return authenticator;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator) {
	return new LdapAuthenticationProvider(authenticator);
}

XML

<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare password-attribute="pwd"> (1)
		<password-encoder ref="passwordEncoder" /> (2)
	</password-compare>
</ldap-authentication-provider>
<b:bean id="passwordEncoder"
	class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

Kotlin

@Bean
fun authenticator(contextSource: BaseLdapPathContextSource): PasswordComparisonAuthenticator {
    val authenticator = PasswordComparisonAuthenticator(contextSource)
    authenticator.setPasswordAttributeName("pwd") (1)
    authenticator.setPasswordEncoder(BCryptPasswordEncoder()) (2)
    return authenticator
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator)
}
1 将 password 属性指定为pwd
2 使用BCryptPasswordEncoder

# ldapAuthoritiesPopulator

Spring 安全性的LdapAuthoritiesPopulator用于确定将为用户返回什么授权。

例 10。ldapAuthoritiesPopulator 配置

Java

@Bean
LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
	String groupSearchBase = "";
	DefaultLdapAuthoritiesPopulator authorities =
		new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
	authorities.setGroupSearchFilter("member={0}");
	return authorities;
}

@Bean
LdapAuthenticationProvider authenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authorities) {
	return new LdapAuthenticationProvider(authenticator, authorities);
}

XML

<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"
	group-search-filter="member={0}"/>

Kotlin

@Bean
fun authorities(contextSource: BaseLdapPathContextSource): LdapAuthoritiesPopulator {
    val groupSearchBase = ""
    val authorities = DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase)
    authorities.setGroupSearchFilter("member={0}")
    return authorities
}

@Bean
fun authenticationProvider(authenticator: LdapAuthenticator, authorities: LdapAuthoritiesPopulator): LdapAuthenticationProvider {
    return LdapAuthenticationProvider(authenticator, authorities)
}

# 活动目录

Active Directory 支持其自己的非标准身份验证选项,并且正常的使用模式与标准LdapAuthenticationProvider不太匹配。通常,身份验证是使用域用户名(形式为[[email protected]](/cdn-cgi/l/email-protection))执行的,而不是使用 LDAP 专有名称。 Spring 为了使这更容易,Security 有一个身份验证提供者,该提供者是为典型的活动目录设置定制的。

配置ActiveDirectoryLdapAuthenticationProvider非常简单。你只需要提供域名和一个 LDAP URL,该 URL 提供服务器的地址[1]。下面是一个配置示例:

例 11。活动目录配置示例

Java

@Bean
ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
	return new ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/");
}

XML

<bean id="authenticationProvider"
        class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
	<constructor-arg value="example.com" />
	<constructor-arg value="ldap://company.example.com/" />
</bean>

Kotlin

@Bean
fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider {
    return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/")
}

1。还可以使用 DNS 查找获得服务器的 IP 地址。这是目前不支持的,但希望将在未来的版本。

DAoAuthenticationProviderSession Management