29. LDAP Authentication

29.1 Overview

LDAP 通常被组织用作用户信息的中央存储库和身份验证服务。它还可以用于存储应用程序用户的角色信息。

关于如何配置 LDAP 服务器,有许多不同的方案,因此 Spring Security 的 LDAP 提供程序是完全可配置的。它使用单独的策略接口进行身份验证和角色检索,并提供可以配置为处理各种情况的默认实现。

在尝试将其与 Spring Security 结合使用之前,您应该熟悉 LDAP。以下链接很好地介绍了相关概念,并提供了使用免费 LDAP 服务器 OpenLDAP 设置目录的指南:http://www.zytrax.com/books/ldap/。熟悉用于从 Java 访问 LDAP 的 JNDI API 可能也很有用。我们在 LDAP 提供程序中不使用任何第三方 LDAP 库(Mozilla,JLDAP 等),但是 Spring LDAP 被广泛使用,因此如果您计划添加自己的自定义项,则对该项目有些熟悉可能会很有用。

使用 LDAP 身份验证时,重要的是要确保正确配置 LDAP 连接池。如果您不熟悉此操作,可以参考Java LDAP 文档

29.2 将 LDAP 与 Spring Security 结合使用

Spring Security 中的 LDAP 认证可以大致分为以下几个阶段。

  • 从登录名获取唯一的 LDAP“专有名称”或 DN。除非事先知道用户名到 DN 的确切 Map,否则这通常意味着在目录中执行搜索。因此,用户在登录时可能会 Importing 名称“ joe”,但是用于验证 LDAP 的实际名称将是完整 DN,例如uid=joe,ou=users,dc=spring,dc=io

  • 通过“绑定”该用户或通过对该用户的密码与 DN 目录条目中的 password 属性进行远程“比较”操作来对用户进行身份验证。

  • 加载用户的权限列表。

exception 是仅使用 LDAP 目录检索用户信息并在本地对其进行身份验证。这可能是不可能的,因为目录经常被设置为具有诸如用户密码之类的属性的有限读取权限。

我们将在下面查看一些配置方案。有关可用配置选项的完整信息,请查阅安全名称空间架构(XML 编辑器中应提供该信息)。

29.3 配置 LDAP 服务器

您需要做的第一件事是配置要对其进行身份验证的服务器。这是使用安全名称空间中的<ldap-server>元素完成的。可以使用url属性将其配置为指向外部 LDAP 服务器:

<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />

29.3.1 使用嵌入式测试服务器

<ldap-server>元素还可用于创建嵌入式服务器,这对于测试和演示非常有用。在这种情况下,您可以不使用url属性来使用它:

<ldap-server root="dc=springframework,dc=org"/>

在这里,我们指定目录的根 DIT 应该为“ dc = springframework,dc = org”,这是默认设置。通过这种方式,名称空间解析器将创建一个嵌入式 Apache Directory 服务器并扫描 Classpath 中是否有任何 LDIF 文件,它将尝试将其加载到服务器中。您可以使用ldif属性来自定义此行为,该属性定义了要加载的 LDIF 资源:

<ldap-server ldif="classpath:users.ldif" />

这使使用 LDAP 进行安装和运行变得容易得多,因为在任何时候都无法与外部服务器一起工作。它还使用户免受连接 Apache Directory 服务器所需的复杂 bean 配置的影响。使用普通的 Spring Beans,配置将更加混乱。您必须具有必要的 Apache Directory 依赖项 jar,供应用程序使用。这些可以从 LDAP 示例应用程序获得。

29.3.2 使用绑定身份验证

这是最常见的 LDAP 身份验证方案。

<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>

这个简单的示例将通过使用提供的模式替换用户登录名并尝试使用该登录密码将该用户绑定来获取该用户的 DN。如果所有用户都存储在目录中的单个节点下,则可以。相反,如果您希望配置 LDAP 搜索过滤器来定位用户,则可以使用以下方法:

<ldap-authentication-provider user-search-filter="(uid={0})"
	user-search-base="ou=people"/>

如果与上面的服务器定义一起使用,这将使用user-search-filter属性的值作为过滤器在 DN ou=people,dc=springframework,dc=org下执行搜索。再次用用户登录名代替过滤器名称中的参数,因此它将搜索uid属性等于用户名的条目。如果未提供user-search-base,则将从根目录执行搜索。

29.3.3 加载权限

通过以下属性控制如何从 LDAP 目录中的组加载权限。

  • group-search-base。定义目录树的一部分,应在该部分下执行组搜索。

  • group-role-attribute。该属性包含组条目定义的权限名称。默认为cn

  • group-search-filter。用于搜索组成员身份的过滤器。默认值为uniqueMember={0},对应于groupOfUniqueNames LDAP 类[19]。在这种情况下,替换参数是用户的完整专有名称。如果要过滤登录名,可以使用参数{1}

因此,如果我们使用以下配置

<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"
		group-search-base="ou=groups" />

并成功验证为用户“ ben”,随后的权限加载将在目录条目ou=groups,dc=springframework,dc=org下执行搜索,以查找包含值为uid=ben,ou=people,dc=springframework,dc=org的属性uniqueMember的条目。默认情况下,权限名称将带有前缀ROLE_。您可以使用role-prefix属性更改此设置。如果您不需要任何前缀,请使用role-prefix="none"。有关加载权限的更多信息,请参见DefaultLdapAuthoritiesPopulator类的 Javadoc。

29.4 实现类

与显式使用 Spring Bean 相比,我们上面使用的名称空间配置选项易于使用并且更加简洁。在某些情况下,您可能需要了解如何在应用程序上下文中直接配置 Spring Security LDAP。例如,您可能希望自定义某些类的行为。如果您对使用名称空间配置感到满意,则可以跳过本节和下一节。

主要的 LDAP 提供程序类LdapAuthenticationProvider实际上并不会做很多事情,而是将工作委托给另外两个 bean,分别是LdapAuthenticatorLdapAuthoritiesPopulator,这两个 bean 分别负责认证用户和检索用户的GrantedAuthority集合。

29.4.1 LdapAuthenticator 的实现

验证者还负责检索任何必需的用户属性。这是因为对属性的权限可能取决于所使用的身份验证类型。例如,如果以用户身份进行绑定,则可能有必要使用用户自己的权限来读取它们。

Spring Security 提供了两种身份验证策略:

  • 直接对 LDAP 服务器进行身份验证(“绑定”身份验证)。

  • 密码比较,将用户提供的密码与存储库中存储的密码进行比较。可以通过检索 password 属性的值并在本地对其进行检查来完成此操作,也可以通过执行 LDAP“比较”操作来完成,在该操作中,将提供的密码传递给服务器进行比较,并且永远不会检索到真实的密码值。

Common Functionality

在可能(通过任何一种策略)对用户进行身份验证之前,必须从提供给应用程序的登录名中获得专有名称(DN)。这可以通过简单的模式匹配(通过设置setUserDnPatterns数组属性)或通过设置userSearch属性来完成。对于 DN 模式匹配方法,使用标准 Java 模式格式,并且登录名将替换{0}参数。该模式应相对于已配置的SpringSecurityContextSource绑定到的 DN(有关此信息,请参阅连接到 LDAP 服务器的部分)。例如,如果您使用具有ldap://monkeymachine.co.uk/dc=springframework,dc=org URL 的 LDAP 服务器,并且具有uid={0},ou=greatapes模式,则登录名“大猩猩”将 Map 到 DN uid=gorilla,ou=greatapes,dc=springframework,dc=org。依次尝试每个已配置的 DN 模式,直到找到匹配项。有关使用搜索的信息,请参见下面的search objects部分。也可以使用两种方法的组合-首先检查模式,如果找不到匹配的 DN,将使用搜索。

BindAuthenticator

org.springframework.security.ldap.authentication中的类BindAuthenticator实现了绑定身份验证策略。它只是尝试以用户身份进行绑定。

PasswordComparisonAuthenticator

PasswordComparisonAuthenticator类实现密码比较身份验证策略。

29.4.2 连接到 LDAP 服务器

上面讨论的 Bean 必须能够连接到服务器。它们都必须带有SpringSecurityContextSource,这是 Spring LDAP ContextSource的扩展。除非有特殊要求,否则通常将配置一个DefaultSpringSecurityContextSource bean,该 bean 可配置为使用 LDAP 服务器的 URL,也可以使用“Management 者”用户的用户名和密码进行配置,默认情况下,绑定到服务器时将使用该用户名和密码(而不是匿名绑定)。有关更多信息,请阅读此类的 Javadoc 和 Spring LDAP 的AbstractContextSource

29.4.3 LDAP 搜索对象

在目录中定位用户条目时,通常需要比简单的 DN 匹配更复杂的策略。可以将其封装在LdapUserSearch实例中,该实例可以提供给身份验证器实现,例如,以允许他们找到用户。提供的实现是FilterBasedLdapUserSearch

FilterBasedLdapUserSearch

该 bean 使用 LDAP 过滤器来匹配目录中的用户对象。 Javadoc 中针对JDK DirContext 类上的相应搜索方法说明了该过程。如此处所述,可以为搜索过滤器提供参数。对于此类,唯一有效的参数是{0},它将替换为用户的登录名。

29.4.4 LdapAuthoritiesPopulator

成功验证用户身份后,LdapAuthenticationProvider将通过调用已配置的LdapAuthoritiesPopulator bean 尝试为用户加载一组权限。 DefaultLdapAuthoritiesPopulator是一种实现,它将通过在目录中搜索用户所属的组来加载权限(通常,这些组将是目录中的groupOfNamesgroupOfUniqueNames条目)。有关此类的更多详细信息,请查阅 Javadoc。

如果您只想使用 LDAP 进行身份验证,但从其他来源(例如数据库)加载授权,则可以提供自己的接口实现,然后注入该接口。

29.4.5 Spring Bean 配置

使用我们在这里讨论过的一些 bean 的典型配置可能如下所示:

<bean id="contextSource"
		class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
<property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
<property name="password" value="password"/>
</bean>

<bean id="ldapAuthProvider"
	class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg>
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
	<constructor-arg ref="contextSource"/>
	<property name="userDnPatterns">
	<list><value>uid={0},ou=people</value></list>
	</property>
</bean>
</constructor-arg>
<constructor-arg>
<bean
	class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
	<constructor-arg ref="contextSource"/>
	<constructor-arg value="ou=groups"/>
	<property name="groupRoleAttribute" value="ou"/>
</bean>
</constructor-arg>
</bean>

这将设置提供程序以使用 URL ldap://monkeymachine:389/dc=springframework,dc=org访问 LDAP 服务器。将通过尝试与 DN uid=<user-login-name>,ou=people,dc=springframework,dc=org绑定来执行身份验证。成功通过身份验证后,将通过使用默认过滤器(member=<user's-DN>)在 DN ou=groups,dc=springframework,dc=org下搜索来将角色分配给用户。角色名称将从每个匹配项的“ ou”属性中获取。

要配置使用过滤器(uid=<user-login-name>)代替 DN 模式(或除此之外)的用户搜索对象,您需要配置以下 bean

<bean id="userSearch"
	class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value=""/>
<constructor-arg index="1" value="(uid={0})"/>
<constructor-arg index="2" ref="contextSource" />
</bean>

并通过设置BindAuthenticator bean 的userSearch属性来使用它。然后,在尝试以该用户身份进行绑定之前,验证者将调用搜索对象以获得正确的用户 DN。

29.4.6 LDAP 属性和自定义的用户详细信息

使用LdapAuthenticationProvider进行身份验证的最终结果与使用标准UserDetailsService接口的常规 Spring Security 身份验证相同。创建一个UserDetails对象并将其存储在返回的Authentication对象中。与使用UserDetailsService一样,一个共同的要求是能够自定义此实现并添加其他属性。使用 LDAP 时,这些通常是用户条目中的属性。 UserDetails对象的创建由提供者的UserDetailsContextMapper策略控制,该策略负责将用户对象与 LDAP 上下文数据进行 Map:

public interface UserDetailsContextMapper {

UserDetails mapUserFromContext(DirContextOperations ctx, String username,
		Collection<GrantedAuthority> authorities);

void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}

仅第一种方法与身份验证有关。如果提供此接口的实现并将其注入LdapAuthenticationProvider,则可以完全控制如何创建 UserDetails 对象。第一个参数是 Spring LDAP DirContextOperations的实例,该实例使您可以访问在身份验证期间加载的 LDAP 属性。 username参数是用于认证的名称,最后一个参数是配置的LdapAuthoritiesPopulator为用户加载的权限的集合。

上下文数据的加载方式根据您所使用的身份验证类型而略有不同。使用BindAuthenticator时,将从绑定操作返回的上下文用于读取属性,否则将使用从配置的ContextSource获得的标准上下文读取数据(当配置搜索以定位用户时,这将是数据由搜索对象返回)。

29.5 Active Directory 身份验证

Active Directory 支持其自己的非标准身份验证选项,并且正常使用模式不太适合标准LdapAuthenticationProvider。通常,身份验证是使用域用户名(格式为[email protected])而不是使用 LDAP 可分辨名称来执行的。为了简化此操作,Spring Security 3.1 具有一个身份验证提供程序,该身份验证提供程序是针对典型的 Active Directory 设置而定制的。

29.5.1 ActiveDirectoryLdapAuthenticationProvider

配置ActiveDirectoryLdapAuthenticationProvider非常简单。您只需要提供域名和 LDAP URL,即可提供服务器[20]的地址。然后,示例配置如下所示:

<bean id="adAuthenticationProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
	<constructor-arg value="mydomain.com" />
	<constructor-arg value="ldap://adserver.mydomain.com/" />
</bean>
}

请注意,无需指定单独的ContextSource即可定义服务器位置-Bean 是完全独立的。例如,名为“ Sharon”的用户将能够通过 Importing 用户名sharon或完整的 Active Directory userPrincipalName(即[email protected])进行身份验证。然后将定位用户的目录条目,并返回属性以供在自定义创建的UserDetails对象时使用(可以为此目的注入UserDetailsContextMapper)。与目录的所有交互都以用户本身的身份进行。没有“Manager”用户的概念。

默认情况下,从用户条目的memberOf属性值获得用户权限。可以再次使用UserDetailsContextMapper定制分配给用户的权限。您还可以将GrantedAuthoritiesMapper注入提供程序实例以控制最终在Authentication对象中的权限。

Active Directory 错误代码

默认情况下,失败的结果将导致标准的 Spring Security BadCredentialsException。如果将属性convertSubErrorCodesToExceptions设置为true,则将解析异常消息,以尝试提取特定于 Active Directory 的错误代码并引发更特定的异常。检查类 Javadoc 以获取更多信息。


[19]请注意,这与使用member={0}的基础DefaultLdapAuthoritiesPopulator的默认配置不同。

[20]也可以使用 DNS 查找来获取服务器的 IP 地址。目前尚不支持此功能,但希望会在将来的版本中提供。