对于最新的稳定版本,请使用 Spring Security 6.5.3! |
LDAP 身份验证
组织通常将 LDAP 用作用户信息的中央存储库和身份验证服务。 它还可用于存储应用程序用户的角色信息。
当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
作为类路径资源,用于与用户一起初始化嵌入式 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>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 提供的实现:
使用绑定身份验证
绑定身份验证是使用 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
用于确定为用户返回哪些权限。
-
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/")
}