对于最新的稳定版本,请使用 Spring Security 6.4.1! |
LDAP 身份验证
组织通常将 LDAP 用作用户信息的中央存储库和身份验证服务。 它还可用于存储应用程序用户的角色信息。
Spring Security 的基于 LDAP 的身份验证在配置为接受用户名/密码进行身份验证时由 Spring Security 使用。
但是,尽管利用用户名/密码进行身份验证,但它不会使用UserDetailsService
因为在 bind 身份验证中,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>
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,因此如果您打算添加自己的自定义项,那么熟悉该项目可能会很有用。
使用 LDAP 身份验证时,请务必确保正确配置 LDAP 连接池。 如果您不熟悉如何执行此作,可以参考 Java LDAP 文档。
设置嵌入式 LDAP 服务器
您需要做的第一件事是确保您有一个 LDAP 服务器来指向您的配置。 为简单起见,通常最好从嵌入式 LDAP 服务器开始。 Spring Security 支持使用以下任一方法:
在下面的示例中,我们将以下内容公开为users.ldif
作为 Classpath 资源,以使用用户user
和admin
这两个 Broker 的密码都是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>4.0.14</version>
<scope>runtime</scope>
</dependency>
depenendencies {
runtimeOnly "com.unboundid:unboundid-ldapsdk:4.0.14"
}
然后,您可以使用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 服务器。
这是通过创建 LDAP 来完成的ContextSource
,相当于 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
还负责检索任何必需的用户属性。
这是因为属性的权限可能取决于所使用的身份验证类型。
例如,如果以用户身份绑定,则可能需要使用用户自己的权限读取它们。
有两个LdapAuthenticator
Spring Security 提供的实现:
使用 Bind 身份验证
绑定验证是使用 LDAP 验证用户的最常见机制。 在绑定身份验证中,用户凭证(即用户名/密码)被提交给 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
用于确定为用户返回哪些授权。
-
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[1].
示例配置如下所示:
-
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/")
}