此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

LDAP 认证

LDAP(轻量级目录访问协议)通常被组织用作用户信息的中央存储库以及身份验证服务。 它还可用于存储应用程序用户的角色信息。spring-doc.cadn.net.cn

当 Spring Security 配置为接受用户名/密码进行身份验证时,会使用基于 LDAP 的身份验证。 然而,尽管使用用户名和密码进行身份验证,它并不使用 UserDetailsService,因为在绑定身份验证(bind authentication)中,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>
dependencies {
    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 作为类路径资源暴露出来,用于初始化嵌入式 LDAP 服务器,其中包含两个用户:useradmin,它们的密码均为 passwordspring-doc.cadn.net.cn

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
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>7.0.4</version>
	<scope>runtime</scope>
</dependency>
dependencies {
	runtimeOnly "com.unboundid:unboundid-ldapsdk:7.0.4"
}

然后,您可以使用 EmbeddedLdapServerContextSourceFactoryBean 来配置嵌入式 LDAP 服务器。 这将指示 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 7 移除了对 Apache DS 的支持。 请改用 UnboundIDspring-doc.cadn.net.cn

LDAP 上下文源

一旦你有了一个LDAP服务器用于配置指向,就需要配置Spring Security,使其指向该LDAP服务器以对用户进行身份验证。 为此,请创建一个LDAP ContextSource(它相当于JDBC的DataSource)。 如果你已经配置了EmbeddedLdapServerContextSourceFactoryBean,Spring Security将自动创建一个指向嵌入式LDAP服务器的LDAP ContextSourcespring-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
}

或者,您可以显式配置 LDAP ContextSource 以连接到所提供的 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

Spring Security 提供了两种 LdapAuthenticator 实现:spring-doc.cadn.net.cn

使用绑定认证

绑定认证(Bind Authentication) 是使用 LDAP 对用户进行身份验证的最常见机制。 在绑定认证中,用户的凭据(用户名和密码)会被提交到 LDAP 服务器,由该服务器对其进行身份验证。 使用绑定认证的优势在于,用户的机密信息(密码)无需暴露给客户端,从而有助于防止泄露。spring-doc.cadn.net.cn

以下示例展示了绑定身份验证的配置: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()
}

如果与前面所示的#servlet-authentication-ldap-contextsource定义一起使用,这将使用ou=people,dc=springframework,dc=org作为过滤器,在DN (uid={0})下执行搜索。 同样,用户登录名会替换过滤器中的参数,因此它会查找uid属性等于用户名的条目。 如果未提供用户搜索基准(user search base),则从根节点开始搜索。spring-doc.cadn.net.cn

使用密码认证

密码比对是指将用户提供的密码与存储在仓库中的密码进行比较。 这可以通过检索密码属性的值并在本地进行检查来实现,也可以通过执行 LDAP “compare”(比较)操作来实现,在该操作中,提供的密码会被发送到服务器进行比对,而真实的密码值永远不会被检索出来。 当密码使用随机盐值进行了正确哈希处理时,无法执行 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

LDAP 授权填充器

Spring Security 的 LdapAuthoritiesPopulator 用于确定为用户返回哪些权限。 以下示例展示了如何配置 LdapAuthoritiesPopulatorspring-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()
}

ActiveDirectory

Active Directory 支持其特有的非标准身份验证选项,而常规的使用模式与标准的 LdapAuthenticationProvider 并不完全契合。 通常,身份验证是通过使用域用户名(格式为 user@domain)来完成的,而不是使用 LDAP 的可分辨名称(distinguished name)。 为了简化这一过程,Spring Security 提供了一个专门针对典型 Active Directory 配置定制的身份验证提供者。spring-doc.cadn.net.cn

配置 ActiveDirectoryLdapAuthenticationProvider 非常简单。 您只需提供域名和一个 LDAP URL,该 URL 用于指定服务器的地址。spring-doc.cadn.net.cn

也可以通过使用 DNS 查询来获取服务器的 IP 地址。 目前尚不支持此功能,但希望在未来的版本中能够支持。spring-doc.cadn.net.cn

以下示例配置了 Active Directory: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/")
}