|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4! |
LDAP 认证
LDAP(轻量级目录访问协议)通常被组织用作用户信息的中央存储库以及身份验证服务。 它还可用于存储应用程序用户的角色信息。
当 Spring Security 配置为接受用户名/密码进行身份验证时,会使用基于 LDAP 的身份验证。
然而,尽管使用用户名和密码进行身份验证,它并不使用 UserDetailsService,因为在绑定身份验证(bind authentication)中,LDAP 服务器不会返回密码,因此应用程序无法对密码进行验证。
LDAP 服务器的配置方式多种多样,因此 Spring Security 的 LDAP 提供程序具有完全的可配置性。 它为身份验证和角色检索使用了独立的策略接口,并提供了默认实现,这些默认实现可进行配置以应对各种不同的场景。
必需的依赖项
首先,将 spring-security-ldap 依赖项添加到您的项目中。
当使用 Spring Boot 时,请添加以下依赖项:
-
Maven
-
Gradle
<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,因此如果您计划添加自己的自定义功能,熟悉该项目可能会有所帮助。
使用 LDAP 身份验证时,应确保正确配置 LDAP 连接池。 如果您不熟悉如何进行配置,请参阅Java LDAP 文档。
设置嵌入式 LDAP 服务器
你需要做的第一件事是确保你有一个 LDAP 服务器,以便将你的配置指向该服务器。 为简化起见,通常最好从一个嵌入式的 LDAP 服务器开始。 Spring Security 支持使用以下任一方式:
在以下示例中,我们将 users.ldif 作为类路径资源暴露出来,用于初始化嵌入式 LDAP 服务器,其中包含两个用户:user 和 admin,它们的密码均为 password:
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,请指定以下依赖项:
-
Maven
-
Gradle
<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 服务器:
-
Java
-
Kotlin
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
}
或者,您可以手动配置嵌入式 LDAP 服务器。 如果您选择此方法,您将负责管理嵌入式 LDAP 服务器的生命周期。
-
Java
-
XML
-
Kotlin
@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 的支持。 请改用 UnboundID。
LDAP 上下文源
一旦你有了一个LDAP服务器用于配置指向,就需要配置Spring Security,使其指向该LDAP服务器以对用户进行身份验证。
为此,请创建一个LDAP ContextSource(它相当于JDBC的DataSource)。
如果你已经配置了EmbeddedLdapServerContextSourceFactoryBean,Spring Security将自动创建一个指向嵌入式LDAP服务器的LDAP ContextSource。
-
Java
-
Kotlin
@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 服务器:
-
Java
-
XML
-
Kotlin
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 进行认证。
因此,LDAP 支持通过 LdapAuthenticator 接口实现。
LdapAuthenticator 接口还负责检索所需的任何用户属性。
这是因为这些属性的权限可能取决于所使用的认证类型。
例如,如果以用户身份进行绑定,则可能需要使用用户自身的权限来读取这些属性。
Spring Security 提供了两种 LdapAuthenticator 实现:
使用绑定认证
绑定认证(Bind Authentication) 是使用 LDAP 对用户进行身份验证的最常见机制。 在绑定认证中,用户的凭据(用户名和密码)会被提交到 LDAP 服务器,由该服务器对其进行身份验证。 使用绑定认证的优势在于,用户的机密信息(密码)无需暴露给客户端,从而有助于防止泄露。
以下示例展示了绑定身份验证的配置:
-
Java
-
XML
-
Kotlin
@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 搜索过滤器来定位用户,则可以使用以下方式:
-
Java
-
XML
-
Kotlin
@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),则从根节点开始搜索。
使用密码认证
密码比对是指将用户提供的密码与存储在仓库中的密码进行比较。 这可以通过检索密码属性的值并在本地进行检查来实现,也可以通过执行 LDAP “compare”(比较)操作来实现,在该操作中,提供的密码会被发送到服务器进行比对,而真实的密码值永远不会被检索出来。 当密码使用随机盐值进行了正确哈希处理时,无法执行 LDAP 比较操作。
-
Java
-
XML
-
Kotlin
@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()
}
以下示例展示了一个更高级的配置,并包含一些自定义设置:
-
Java
-
XML
-
Kotlin
@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 用于确定为用户返回哪些权限。
以下示例展示了如何配置 LdapAuthoritiesPopulator:
-
Java
-
XML
-
Kotlin
@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 配置定制的身份验证提供者。
配置 ActiveDirectoryLdapAuthenticationProvider 非常简单。
您只需提供域名和一个 LDAP URL,该 URL 用于指定服务器的地址。
|
也可以通过使用 DNS 查询来获取服务器的 IP 地址。 目前尚不支持此功能,但希望在未来的版本中能够支持。 |
以下示例配置了 Active Directory:
-
Java
-
XML
-
Kotlin
@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/")
}