此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1! |
LDAP 身份验证
LDAP(轻量级目录访问协议)通常被组织用作用户信息的中央存储库和身份验证服务。 它还可用于存储应用程序用户的角色信息。
当 Spring Security 配置为接受用户名/密码进行身份验证时,Spring Security 会使用 Spring Security 基于 LDAP 的身份验证。
但是,尽管使用用户名和密码进行身份验证,但它不会使用UserDetailsService
,因为在 bind 验证中,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
作为 Classpath 资源来初始化具有两个用户的嵌入式 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 绑定身份验证不允许 Client 端读取密码,甚至不允许密码的哈希版本。 这意味着 Spring Security 无法读取密码,然后对其进行身份验证。
因此,LDAP 支持是通过LdapAuthenticator
接口。
这LdapAuthenticator
interface 还负责检索任何必需的用户属性。
这是因为属性的权限可能取决于所使用的身份验证类型。
例如,如果以 user 身份绑定,则可能需要读取具有用户自身权限的属性。
Spring Security 提供两个LdapAuthenticator
实现:
使用 Bind 身份验证
绑定验证是使用 LDAP 验证用户的最常见机制。 在 bind 身份验证中,用户的凭证 (用户名和密码) 将提交给 LDAP 服务器,该服务器对用户进行身份验证。 使用绑定身份验证的优点是不需要向客户端公开用户的密钥(密码),这有助于防止它们泄露。
以下示例显示了 bind 身份验证配置:
-
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
定义,这将在 DN 下执行搜索ou=people,dc=springframework,dc=org
通过使用(uid={0})
作为过滤器。
同样,用户登录名被替换了筛选条件名称中的参数,因此它会搜索具有uid
属性等于用户名。
如果未提供用户搜索库,则从根执行搜索。
使用口令验证
密码比较是指将用户提供的密码与存储库中存储的密码进行比较。 这可以通过检索 password 属性的值并在本地检查它来完成,也可以通过执行 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 | 将 password 属性指定为pwd . |
LdapAuthoritiesPopulator
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/")
}