对于最新的稳定版本,请使用 Spring Security 6.5.3! |
LDAP 身份验证
LDAP(轻量级目录访问协议)通常被组织用作用户信息的中央存储库和身份验证服务。它还可用于存储应用程序用户的角色信息。
当Spring Security配置为接受用户名/密码进行身份验证时,Spring Security会使用基于LDAP的身份验证。但是,尽管使用用户名和密码进行身份验证,但它不会使用UserDetailsService
,因为在绑定认证中,LDAP 服务器不返回密码,因此应用程序无法执行密码验证。
如何配置 LDAP 服务器有许多不同的场景,因此 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
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
嵌入式 UnboundID 服务器
如果要使用 UnboundID,请指定以下依赖项:
-
Maven
-
Gradle
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>6.0.11</version>
<scope>runtime</scope>
</dependency>
depenendencies {
runtimeOnly "com.unboundid:unboundid-ldapsdk:6.0.11"
}
然后,您可以使用EmbeddedLdapServerContextSourceFactoryBean
. 这将指示 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 使用 ApacheDS 1.x,该版本不再维护。不幸的是,ApacheDS 2.x 只发布了里程碑版本,没有稳定版本。一旦 ApacheDS 2.x 的稳定版本可用,我们将考虑更新。 |
如果您希望使用 Apache DS,请指定以下依赖项:
-
Maven
-
Gradle
<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 服务器:
-
Java
-
XML
-
Kotlin
@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 服务器。为此,请创建一个 LDAPContextSource
(相当于 JDBCDataSource
). 如果您已经配置了EmbeddedLdapServerContextSourceFactoryBean
,Spring Security 将创建一个 LDAPContextSource
指向嵌入式 LDAP 服务器。
-
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
}
或者,您可以显式配置 LDAPContextSource
要连接到提供的 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
interface 还负责检索任何必需的用户属性。
这是因为属性的权限可能取决于所使用的身份验证类型。
例如,如果以用户身份绑定,则可能需要使用用户自己的权限读取属性。
Spring Security 供应两个LdapAuthenticator
实现:
使用绑定身份验证
绑定身份验证是使用 LDAP 对用户进行身份验证的最常见机制。 在绑定身份验证中,用户的凭据(用户名和密码)将提交给 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()
}
如果与ContextSource
前面显示的定义,这将在 DNou=people,dc=springframework,dc=org
通过使用(uid={0})
作为过滤器。
同样,用户登录名将替换过滤器名称中的参数,因此它会搜索带有uid
属性等于用户名。
如果未提供用户搜索库,则从根目录执行搜索。
使用密码认证
密码比较是指将用户提供的密码与存储在存储库中的密码进行比较。 这可以通过检索密码属性的值并在本地检查它来完成,也可以通过执行 LDAP“比较”作来完成,其中提供的密码将传递给服务器进行比较,并且永远不会检索实际密码值。 当密码使用随机盐正确散列时,无法执行 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 | 将密码属性指定为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()
}
活动目录
Active Directory 支持自己的非标准身份验证选项,正常使用模式与标准不太契合LdapAuthenticationProvider
.
通常,身份验证是使用域用户名(以user@domain
),而不是使用 LDAP 可分辨名称。
为了简化此作,Spring Security 有一个身份验证提供程序,它是为典型的 Active Directory 设置定制的。
配置ActiveDirectoryLdapAuthenticationProvider
非常简单。
您只需提供域名和提供服务器地址的 LDAP 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/")
}