对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cadn.net.cn

LDAP 身份验证

组织通常将 LDAP 用作用户信息的中央存储库和身份验证服务。 它还可用于存储应用程序用户的角色信息。spring-doc.cadn.net.cn

Spring Security 的基于 LDAP 的身份验证在配置为接受用户名/密码进行身份验证时由 Spring Security 使用。 但是,尽管利用用户名/密码进行身份验证,但它不会使用UserDetailsService因为在 bind 身份验证中,LDAP 服务器不返回密码,因此应用程序无法执行密码验证。spring-doc.cadn.net.cn

对于如何配置 LDAP 服务器,有许多不同的场景,因此 Spring Security 的 LDAP 提供程序是完全可配置的。 它使用单独的策略接口进行身份验证和角色检索,并提供默认实现,这些实现可以配置为处理各种情况。spring-doc.cadn.net.cn

必需的依赖项

要开始使用,请添加spring-security-ldap依赖项。 使用 Spring Boot 时,添加以下依赖项:spring-doc.cadn.net.cn

Spring Security LDAP 依赖项
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
</dependency>
depenendencies {
    implementation "org.springframework.boot:spring-boot-starter-data-ldap"
    implementation "org.springframework.security:spring-security-ldap"
}

先决条件

在尝试将 LDAP 与 Spring Security 一起使用之前,您应该熟悉 LDAP。 以下链接很好地介绍了所涉及的概念,并提供了使用免费的 LDAP 服务器 OpenLDAP 设置目录的指南:www.zytrax.com/books/ldap/。 熟悉用于从 Java 访问 LDAP 的 JNDI API 也可能很有用。 我们在 LDAP 提供程序中没有使用任何第三方 LDAP 库(Mozilla、JLDAP 等),但广泛使用了 Spring LDAP,因此如果您打算添加自己的自定义项,那么熟悉该项目可能会很有用。spring-doc.cadn.net.cn

使用 LDAP 身份验证时,请务必确保正确配置 LDAP 连接池。 如果您不熟悉如何执行此作,可以参考 Java LDAP 文档spring-doc.cadn.net.cn

设置嵌入式 LDAP 服务器

您需要做的第一件事是确保您有一个 LDAP 服务器来指向您的配置。 为简单起见,通常最好从嵌入式 LDAP 服务器开始。 Spring Security 支持使用以下任一方法:spring-doc.cadn.net.cn

在下面的示例中,我们将以下内容公开为users.ldif作为 Classpath 资源,以使用用户useradmin这两个 Broker 的密码都是password.spring-doc.cadn.net.cn

用户.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
member: uid=admin,ou=people,dc=springframework,dc=org
member: uid=user,ou=people,dc=springframework,dc=org

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

嵌入式 UnboundID 服务器

如果要使用 UnboundID,请指定以下依赖项:spring-doc.cadn.net.cn

UnboundID 依赖项
<dependency>
	<groupId>com.unboundid</groupId>
	<artifactId>unboundid-ldapsdk</artifactId>
	<version>4.0.14</version>
	<scope>runtime</scope>
</dependency>
depenendencies {
	runtimeOnly "com.unboundid:unboundid-ldapsdk:4.0.14"
}

然后,您可以使用EmbeddedLdapServerContextSourceFactoryBean. 这将指示 Spring Security 启动内存中的 LDAP 服务器。spring-doc.cadn.net.cn

嵌入式 LDAP 服务器配置
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
	return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
    return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
}

或者,您可以手动配置嵌入式 LDAP 服务器。 如果选择此方法,您将负责管理嵌入式 LDAP 服务器的生命周期。spring-doc.cadn.net.cn

显式嵌入式 LDAP 服务器配置
@Bean
UnboundIdContainer ldapContainer() {
	return new UnboundIdContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}
<b:bean class="org.springframework.security.ldap.server.UnboundIdContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>
@Bean
fun ldapContainer(): UnboundIdContainer {
    return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif")
}

嵌入式 ApacheDS 服务器

Spring Security 使用不再维护的 ApacheDS 1.x。 不幸的是,ApacheDS 2.x 只发布了里程碑版本,没有稳定版本。 一旦 ApacheDS 2.x 的稳定版本可用,我们将考虑更新。spring-doc.cadn.net.cn

如果您希望使用 Apache DS,请指定以下依赖项:spring-doc.cadn.net.cn

ApacheDS 依赖项
<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>
depenendencies {
	runtimeOnly "org.apache.directory.server:apacheds-core:1.5.5"
	runtimeOnly "org.apache.directory.server:apacheds-server-jndi:1.5.5"
}

然后,您可以配置嵌入式 LDAP 服务器spring-doc.cadn.net.cn

嵌入式 LDAP 服务器配置
@Bean
ApacheDSContainer ldapContainer() {
	return new ApacheDSContainer("dc=springframework,dc=org",
				"classpath:users.ldif");
}
<b:bean class="org.springframework.security.ldap.server.ApacheDSContainer"
	c:defaultPartitionSuffix="dc=springframework,dc=org"
	c:ldif="classpath:users.ldif"/>
@Bean
fun ldapContainer(): ApacheDSContainer {
    return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif")
}

LDAP 上下文源

一旦有了将配置指向的 LDAP 服务器,就需要将 Spring Security 配置为指向应用于对用户进行身份验证的 LDAP 服务器。 这是通过创建 LDAP 来完成的ContextSource,相当于 JDBCDataSource. 如果您已经配置了EmbeddedLdapServerContextSourceFactoryBean,Spring Security 将创建一个 LDAPContextSource指向嵌入式 LDAP 服务器。spring-doc.cadn.net.cn

具有嵌入式 LDAP 服务器的 LDAP 上下文源
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
	EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
			EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
	contextSourceFactoryBean.setPort(0);
	return contextSourceFactoryBean;
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
    val contextSourceFactoryBean = EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
    contextSourceFactoryBean.setPort(0)
    return contextSourceFactoryBean
}

或者,您可以显式配置 LDAPContextSource以连接到提供的 LDAP 服务器。spring-doc.cadn.net.cn

LDAP 上下文源
ContextSource contextSource(UnboundIdContainer container) {
	return new DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org");
}
<ldap-server
	url="ldap://localhost:53389/dc=springframework,dc=org" />
fun contextSource(container: UnboundIdContainer): ContextSource {
    return DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org")
}

认证

Spring Security 的 LDAP 支持不使用UserDetailsService,因为 LDAP 绑定身份验证不允许客户端读取密码,甚至不允许密码的哈希版本。 这意味着 Spring Security 无法读取密码,然后对其进行身份验证。spring-doc.cadn.net.cn

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

有两个LdapAuthenticatorSpring Security 提供的实现:spring-doc.cadn.net.cn

使用 Bind 身份验证

绑定验证是使用 LDAP 验证用户的最常见机制。 在绑定身份验证中,用户凭证(即用户名/密码)被提交给 LDAP 服务器,该服务器对他们进行身份验证。 使用绑定身份验证的优点是用户的密钥(即密码)不需要暴露给客户端,这有助于防止它们泄露。spring-doc.cadn.net.cn

可以在下面找到 bind 身份验证配置的示例。spring-doc.cadn.net.cn

绑定身份验证
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
	factory.setUserDnPatterns("uid={0},ou=people");
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"/>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
    val factory = LdapBindAuthenticationManagerFactory(contextSource)
    factory.setUserDnPatterns("uid={0},ou=people")
    return factory.createAuthenticationManager()
}

这个简单的示例将获取用户的 DN,方法是将用户登录名替换为提供的模式,并尝试以该用户身份绑定登录密码。 如果您的所有用户都存储在目录中的单个节点下,则可以这样做。 如果您希望配置 LDAP 搜索过滤器来查找用户,则可以使用以下内容:spring-doc.cadn.net.cn

将身份验证与搜索过滤器绑定
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
	factory.setUserSearchFilter("(uid={0})");
	factory.setUserSearchBase("ou=people");
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
		user-search-filter="(uid={0})"
	user-search-base="ou=people"/>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
    val factory = LdapBindAuthenticationManagerFactory(contextSource)
    factory.setUserSearchFilter("(uid={0})")
    factory.setUserSearchBase("ou=people")
    return factory.createAuthenticationManager()
}

如果与ContextSource 定义,这将在 DN 下执行搜索ou=people,dc=springframework,dc=org(uid={0})作为过滤器。 同样,用户登录名被替换了过滤器名称中的参数,因此它将搜索带有uid属性等于用户名。 如果未提供用户搜索库,则将从根执行搜索。spring-doc.cadn.net.cn

使用口令验证

密码比较是指将用户提供的密码与存储库中存储的密码进行比较。 这可以通过检索 password 属性的值并在本地检查它来完成,也可以通过执行 LDAP“比较”作来完成,其中提供的密码将传递给服务器进行比较,并且永远不会检索实际密码值。 当密码使用随机盐正确哈希时,无法进行 LDAP 比较。spring-doc.cadn.net.cn

最小密码比较配置
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
			contextSource, NoOpPasswordEncoder.getInstance());
	factory.setUserDnPatterns("uid={0},ou=people");
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
		user-dn-pattern="uid={0},ou=people">
	<password-compare />
</ldap-authentication-provider>
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource?): AuthenticationManager? {
    val factory = LdapPasswordComparisonAuthenticationManagerFactory(
        contextSource, NoOpPasswordEncoder.getInstance()
    )
    factory.setUserDnPatterns("uid={0},ou=people")
    return factory.createAuthenticationManager()
}

可以在下面找到具有一些自定义的更高级配置。spring-doc.cadn.net.cn

密码比较配置
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
	LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
			contextSource, new BCryptPasswordEncoder());
	factory.setUserDnPatterns("uid={0},ou=people");
	factory.setPasswordAttribute("pwd");  (1)
	return factory.createAuthenticationManager();
}
<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" />
@Bean
fun authenticationManager(contextSource: BaseLdapPathContextSource): AuthenticationManager {
    val factory = LdapPasswordComparisonAuthenticationManagerFactory(
        contextSource, BCryptPasswordEncoder()
    )
    factory.setUserDnPatterns("uid={0},ou=people")
    factory.setPasswordAttribute("pwd") (1)
    return factory.createAuthenticationManager()
}
1 将 password 属性指定为pwd

LdapAuthoritiesPopulator

Spring Security 的LdapAuthoritiesPopulator用于确定为用户返回哪些授权。spring-doc.cadn.net.cn

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

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource, LdapAuthoritiesPopulator authorities) {
	LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
	factory.setUserDnPatterns("uid={0},ou=people");
	factory.setLdapAuthoritiesPopulator(authorities);
	return factory.createAuthenticationManager();
}
<ldap-authentication-provider
	user-dn-pattern="uid={0},ou=people"
	group-search-filter="member={0}"/>
@Bean
fun authorities(contextSource: BaseLdapPathContextSource): LdapAuthoritiesPopulator {
    val groupSearchBase = ""
    val authorities = DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase)
    authorities.setGroupSearchFilter("member={0}")
    return authorities
}

@Bean
fun authenticationManager(
    contextSource: BaseLdapPathContextSource,
    authorities: LdapAuthoritiesPopulator): AuthenticationManager {
    val factory = LdapBindAuthenticationManagerFactory(contextSource)
    factory.setUserDnPatterns("uid={0},ou=people")
    factory.setLdapAuthoritiesPopulator(authorities)
    return factory.createAuthenticationManager()
}

活动目录

Active Directory 支持自己的非标准身份验证选项,并且正常使用模式与标准LdapAuthenticationProvider. 通常,使用域用户名(格式为user@domain),而不是使用 LDAP 专有名称。 为了简化此作,Spring Security 具有针对典型 Active Directory 设置定制的身份验证提供程序。spring-doc.cadn.net.cn

配置ActiveDirectoryLdapAuthenticationProvider很简单。 您只需提供域名和提供服务器地址的 LDAP URL[1]. 示例配置如下所示:spring-doc.cadn.net.cn

Active Directory 配置示例
@Bean
ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
	return new ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/");
}
<bean id="authenticationProvider"
        class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
	<constructor-arg value="example.com" />
	<constructor-arg value="ldap://company.example.com/" />
</bean>
@Bean
fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider {
    return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/")
}

1. 也可以使用 DNS 查找获取服务器的 IP 地址。目前不支持此功能,但希望在未来版本中支持此功能。