LDAP 认证

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

LDAP(轻量级目录访问协议)经常被组织用作用户信息的中央存储库和认证服务。它还可以用来存储应用程序用户的角色信息。

当Spring Security被配置为 接受用户名/密码 进行认证时,它就会使用基于LDAP的认证。然而,尽管使用了用户名和密码进行认证,但它并没有使用 UserDetailsService,因为在 绑定认证中,LDAP服务器并不返回密码,所以应用程序无法对密码进行验证。

对于如何配置LDAP服务器,有许多不同的情况,所以Spring Security的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服务器,有两个用户,useradmin,他们的密码都是 password

users.ldif
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,请指定以下依赖。

UnboundID Dependencies
  • Maven

  • Gradle

<dependency>
	<groupId>com.unboundid</groupId>
	<artifactId>unboundid-ldapsdk</artifactId>
	<version>6.0.9</version>
	<scope>runtime</scope>
</dependency>
depenendencies {
	runtimeOnly "com.unboundid:unboundid-ldapsdk:6.0.9"
}

然后你可以使用 EmbeddedLdapServerContextSourceFactoryBean 来配置嵌入式LDAP服务器。这将指示Spring Security启动一个内存中的LDAP服务器。

Embedded LDAP Server Configuration
  • Java

  • Kotlin

@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
	return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
}
@Bean
fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
    return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
}

另外,你也可以手动配置嵌入式 LDAP 服务器。如果你选择这种方式,你将负责管理嵌入式LDAP服务器的生命周期。

Explicit Embedded LDAP Server Configuration
  • 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,请指定以下依赖。

ApacheDS Dependencies
  • 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服务器。

Embedded LDAP Server Configuration
  • 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 ContextSource

一旦你有了一个LDAP服务器,你需要配置Spring Security以指向一个应该用来验证用户的LDAP服务器。为此,创建一个LDAP ContextSource(相当于一个JDBC DataSource)。如果你已经配置了一个 EmbeddedLdapServerContextSourceFactoryBean,Spring Security将创建一个指向嵌入式LDAP服务器的LDAP ContextSource

LDAP Context Source with Embedded LDAP Server
  • 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服务器。

LDAP Context Source
  • 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服务器,由其进行认证。使用绑定认证的好处是,用户的秘密(密码)不需要暴露给客户端,这有助于保护它们不被泄露。

下面的例子显示了绑定认证的配置。

Bind Authentication
  • 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搜索过滤器来定位用户,你可以使用以下方法。

Bind Authentication with Search Filter
  • 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 属性等于用户名称的条目。如果没有提供用户搜索基础,则从根目录进行搜索。

使用密码认证

密码比较是指将用户提供的密码与存储在资源库中的密码进行比较。这可以通过检索密码属性的值并在本地检查来完成,也可以通过执行LDAP “compare” 操作来完成,其中提供的密码被传递给服务器进行比较,真正的密码值不会被检索出来。当密码是用随机盐正确散列的时候,就不能进行LDAP比较。

Minimal Password Compare Configuration
  • 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()
}

下面的例子显示了一个更高级的配置,有一些自定义的配置。

Password Compare Configuration
  • 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

LdapAuthoritiesPopulator Configuration
  • 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有一个认证提供者,它是为典型的活动目录设置定制的。

配置 ActiveDirectoryLdapAuthenticationProvider 是非常直接的。你只需要提供域名和一个提供服务器地址的LDAP URL。

也可以通过使用DNS查询来获得服务器的IP地址。目前还不支持这个功能,但希望在未来的版本中能得到支持。

下面的例子配置了活动目录。

Example Active Directory Configuration
  • 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/")
}