《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置)

  • Post author:
  • Post category:其他



第九章




LDAP

目录服务

在本章中,我们将会了解轻量级目录访问协议(

Lightweight Directory Access Protocol



LDAP

)以及它怎样集成到使用

Spring Security

的应用中以提供认证、授权和用户信息服务。

在本章的内容中,我们将会:


l



学习一些

LDAP

协议相关的基本概念以及服务器实现;


l





Spring Security

中配置一个嵌入式的

LDAP

服务器;


l



使用

LDAP

认证和授权;


l



理解

LDAP

查找和用户匹配背后的模型;


l



从标准的

LDAP

结构中查询额外的用户信息;


l



不同的

LDAP

授权方法,并比较它们的优劣;


l



使用

Spring Bean

声明明确配置

Spring Security LDAP


l



连接

LDAP

目录,包括

Microsoft Active Directory

进行认证。

理解

LDAP


LDAP

在逻辑目录模型方面能够追溯到超过三十年前——在概念上类似于组织机构图和地址薄。今天,

LDAP

越来越多的用来作为集中管理组织用户信息的方式,可以将成千的用户分成逻辑上的组并允许在不同的分布式系统间共享统一的用户信息。



为了安全目的,

LDAP

经常被用来帮助集中的用户名和密码认证——用户的凭证存储在

LDAP

目录中,而对于用户来说,认证请求基于目录进行。这对于管理员来说可以便于管理,因为用户的凭证——登录、密码和其它信息——都存储在

LDAP

中的一个地址。另外,组织机构信息,如组和团队的分配、地理位置以及组织的等级结构,也定义在用户在目录中的位置上。



LDAP



如果你以前从来没有使用过

LDAP

,你可能想知道它到底是什么。我们通过一个

Apache Directory Server 1.5

的截图作为

LDAP

模式的例子进行介绍。




让我们从一个特定用户

Albert Einstein

(截图中高亮显示)的条目开始,我们可以看到

Mr. Einstein

的组织成员信息可以从他的树节点往上移动看到。我们可以看到

einstein

是组织单元(

organizational unit



ou



users

的成员,而组织单元本身是域

example.com

的一部分(截屏中显示的

dc

代表的域组件“

domain component

”)。在此之前的是

LDAP

树本身的组织机构元素(

DIT



Root DSE

),这些现在与我们无关。用户

aeinstein



LDAP

结构中有其语义且意义明确——你可以想象一个巨大组织更复杂的等级结构也能够很容易的说明其组织机构和部门的边界。



一个叶子节点在树上从上到下的路径形成了包含所有参与节点的字符串,如

Mr. Einstein

的节点路径为:

uid=aeinstein,ou=users,dc=example,dc=com

这个节点路径是唯一的,并被称为节点的标识名(

distinguished name



DN

)。标识名类似于数据库里的主键,允许节点在复杂的树结构中唯一标识和定位。我们将会看到的节点的

DN

广泛应用于

Spring Security LDAP

集成的认证和查找过程。



我们可以看到还有几个其他的用户和

Mr. Einstein

在同一个等级的组织结构上。我们假设所有的这些用户都与

Mr. Einstein

在相同的组织下。尽管这个组织机构的例子相对简单,但是

LDAP

的结构是极其灵活的,使得很多层级的逻辑组织机构嵌套成为可能。


Spring Security LDAP

需要

Spring LDAP

模块的支持(

http://www.springsource.org/ldap

),它是单独于

Spring

框架核心和

Spring Security

的工程。它被认为很稳定并对标准的

Java LDAP

功能进行了有用的封装。


通用的

LDAP

属性名



树上的每个实际节点都是通过一个或多个的对象类(

object class

)来定义的。一个对象类是组织机构的一个逻辑单元,分为一系列具有语义的相关属性。通过将一个条目声明为特定对象类的实例,如

person



LDAP

目录的管理人员就能够为目录的用户提供每个条目的确切含义。


LDAP

具有很丰富的标准模式(

schema

),涵盖了可用的

LDAP

对象类和它们的可用属性(以及一些其它信息)。如果你计划广泛的使用

LDAP

,强烈建议你参考一个较好的用户手册,如

Zytrax OpenLDAP

的附录(


http://www.zytrax.com/books/ldap/ape/


),或者

Internet2

组织提供的人员相关模式(


http://middleware.internet2.edu/eduperson/


)。



在上一节中,我们了解到

LDAP

中的每个条目都会有一个标识名,它在树上唯一标识节点。

DN

有一系列的属性组成,其中一个(或更多)用来标识树上的用

DN

代表的路径。因为

DN

中路径的每一部分都代表一个

LDAP

属性,所以你能够通过定义良好的

LDAP

模式和类对象来确定

DN

中每个属性的含义。



我们在以下的表格中,列出了一些常见的属性和它们的含义。这些属性是用来作为组织相关的属性——一意味着它们一般用来定义

LDAP

树的组织机构——并按照结构上从上到下的顺序,正如你通常在

LDAP

中会见到的那样。

属性名

描述

示例


dc

域组件(

Domain Component

)——一般为

LDAP

等级结构中的最高一级组织


dc=jbcppets,dc=com


c

国家(

Country

)——一些

LDAP

等级结构中将国家作为很高的等级


c=US


o

组织名(

Organization name

)——一个

LDAP

资源分类上的业务组织


o=Sun Microsystems


ou

组织单元(

Organizational unit

)——业务组织部门,一般在组织之内


ou=Product Development


cn

通用名(

Common name

)——对象的通用名或唯一名或者为对人可读的名字。对人来说一般为人的全名,对于

LDAP

中的其它资源(电脑等等)一般为主机名。


cn=Super Visor


cn=Jim Bob


uid

用户

ID



User ID

)——尽管并不是原生作为组织相关使用,但是

Spring

一般会查找

uid

进行用户认证和搜索


uid=svisor


userPassword

用户密码(

User password

)——存储人对象相关联的密码。一般会经过

SHA

或类似的单向哈希算法。


userPassword=plaintext


userPassword={SHA}cryptval




要记住的是有上百个标准的

LDAP

属性——上面只是其中的一小部分,当你与一个完整

LDAP

集成的话会看到它们。但是表中的这些属性是目录树中组织相关的属性,当你配置

Spring Security



LDAP

交互的时候可能会用来形成各种查询表达式或匹配符。


运行一个嵌入式的

LDAP

服务



作为测试,

Spring Security

允许使用嵌入式的

LDAP

服务器。就像我们使用嵌入式数据库那样,这使得应用可以启动一个基于内存的

LDAP

服务器并插入初始化数据。当然,这样的一个配置只能用于测试的目的,但是这能够节省我们很多配置单独

LDAP

服务器的时间。



嵌入式的

LDAP

服务器功能是通过使用

Apache Directory Server (DS) 1.5

来支持的,它是一个基于

Java

、开源且完全符合规范的

LDAP

服务器。实际上,你也可以使用

Apache DS

作为独立的服务器,它很相对很容易配置并易于获取和安装。本章实例代码中的

Dependencies

目录下包含了嵌入式

LDAP

服务器所需要的

JAR

包——如果你要自己使用它的话,你要么使用

Maven

要么自己到以下地址


http://directory.apache.org/


下载

Apache DS



如同嵌入式的

HSQL

数据库允许在启动时加载

SQL

脚本,嵌入式的

LDAP

服务器提供了启动时从

LDAP

数据交换格式(

LDAP Data Interchange Format



LDIF

)文件中插入目录的方法。

LDIF

是一种简洁的且对人和机器都很易读的数据定义格式,它提供了

LDAP

对象和支持数据的灵活定义。在本章的源码中提供了几个实例性的

LDIF

文件。

配置基本的

LDAP

集成



现在让我们让

JBCP Pets

支持基于

LDAP

的认证。幸运的是,通过使用嵌入式的

LDAP

服务器和实例

LDIF

文件,这是一个相对容易的练习。在这个练习中,我们使用为本书创建的

LDIF

文件,这个文件用来进行表述

LDAP



Spring Security

的常用配置场景。我们提供了几个其它的

LDIF

文件,其中一些来自

Apache DS 1.5

,还有一个来自

SpringSecurity

的单元测试,你可能会愿意选择它们进行体验。


配置

LDAP

服务器引用



第一步是在

dogstore-security.xml

中声明嵌入式

LDAP

服务器的引用。

LDAP

服务器的声明在

<http>

元素之外,与

<authentication-manager>

相同的等级:

<ldap-server ldif="classpath:JBCPPets.ldif" id="ldapLocal" root="dc=jb
cppets,dc=com"/>

我们从

classpath

中加载

JBCPPets.ldif

,并用其为

LDAP

服务器插入数据。这意味着(如同嵌入式

HSQL

数据库启动那样)我们应该在

WEB-INF/classes

放置

JBCPPets.ldif

文件。

root

属性用特定的

DN

声明了

LDAP

目录的根。这应该与我们使用的

LDIF

文件逻辑根

DN

相对应。

【注意,对于嵌入式的

LDAP

服务器,

root

是必须的,尽管

XML

模式并没有这样声明。如果它没有指明或指明错误,你会在

Apache DS server

启动的时候看待几个奇怪的错误。】



当我们在

Spring Security

配置文件中声明

LDAP

用户服务和其它配置元素时,会重用这里定义的

bean ID

。对于嵌入式的

LDAP

模式来说,

<ldap-server>

声明的其它属性都是可选的。


启用

LDAP AuthenticationProvider



接下来,我们要配置另一个

AuthenticationProvider

,它用来用

LDAP

来检查用户凭证。简单得添加另一个

AuthenticationProvider

即可,如下:

<authentication-manager alias="authenticationManager">
<!-- Other authentication providers are here -->
  <ldap-authentication-provider server-ref="ldapLocal"
    user-search-filter="(uid={0})" 
    group-search-base="ou=Groups"  
  />
</authentication-manager>

我们稍后将会介绍这些属性——现在,回到应用并运行,使用用户名

ldapguest

和密码

password

进行登录。你应该能够登录进去了!


解决嵌入式

LDAP

的问题



很可能你在使用嵌入式

LDAP

时,调试问题很困难。

Apache DS

的出错信息并不友好,这在

SpringSecurity

嵌入模式下更严重。如果你不能让这个简单的例子正常运行,请仔细检查以下的地方:


l



确保

Apache DS

依赖的所有

JAR

都在

web

应用的

classpath

下。这会有很多——最好的方式就是包含所有的(实例代码就是这样做的);


l



确保在你的配置文件中

<ldap-server>

设置了

root

属性,且它与启动时加载的

LDIF

文件中

root

的定义相匹配。如果你遇到了找不到引用的错误,很可能要么缺少

root

元素,要么与

LDIF

文件不匹配;


l



注意的是启动嵌入式

LDAP

的错误并不会是一个致命错误。为了分析加载

LDIF

文件的错误,你需要确保适当设置了日志,包括

Apache DS

的日志启用,至少要在

ERROR

级别。

LDIF

的加载器在包下,它应该被用来启用

LDIF

加载错误的日志;


l



如果应用没有被正常关闭,为了重新启动服务,你可能会需要删除临时目录下的一些文件(

Windows

系统下为

%TEMP%

)。这个的出错信息(幸运的是)很清楚。



遗憾的是,嵌入式

LDAP

并不像嵌入式

HSQL

数据库那样简单,但是相对于需要下载和配置的很多外部

LDAP

服务器来说,已经比较简单了。



一个用于排除问题和访问

LDAP

的好工具是

Apache Directory Studio

,它提供了独立的和

Eclipse

插件的版本。免费下载地址:


http://directory.apache.org/studio/


理解

Spring LDAP

认证如何工作



我们看到可以使用

LDIF

文件定义的用户(也就会在

LDAP

目录中出现)进行登录了。一个

LDAP

用户进行登录时到底发生了什么?在

LDAP

认证过程中有三个基本的步骤:


l



将用户提供的凭证与

LDAP

目录进行认证;


l



基于

LDAP

上的信息,确定用户拥有的

GrantedAuthority


l



为了应用以后用到,从

LDAP

条目中预先加载用户信息到自定义的

UserDetails

对象中。


认证用户凭证



第一步,通过织入

AuthenticationManager

的自定义认证提供者与

LDAP

目录进行认证。

o.s.s.ldap.authentication.LdapAuthenticationProvider

将用户提供的凭证与

LDAP

目录进行校验,如下图所示:




我们可以看到

o.s.s.ldap.authentication.LdapAuthenticator

接口定义了一个代理从而允许提供者以自定义的方式认证请求。在这里我们明确配置的是

o.s.s.ldap.authentication.BindAuthenticator

,它会尝试使用用户的凭证绑定(登录)

LDAP

服务器,就像用户本身尝试建立连接。对嵌入式的服务器来说,这对于我们的认证要求是足够的,但是,外部的

LDAP

服务器在用户绑定

LDAP

目录上可能要求更严格。幸运的是,还有一种替代的认证方式,我们将会在本章稍后介绍。



正如图中所标注的那样,记住查找是在

<ldap-server>

引用的

manager-dn

属性所创建的

LDAP

上下文中进行的。对于嵌入式的服务器,我们没有使用这个信息,但是对于外部的服务器引用,除非提供

manager-dn

,否则的话将会进行匿名绑定。为了保持目录中公开访问信息的限制,通常需要合法的凭证来进行

LDAP

目录的搜索,这样的话,

manager-dn

在现实世界场景中基本上就是必需的了。

manager-dn

代表了用户的全

DN

,基于合法的访问绑定目录并进行查找。


确定用户的角色



在用户基于

LDAP

服务器成功认证之后,接下来必须要进行权限信息的确定。授权是通过安全实体的一系列角色定义的,

LDAP

认证过的用户角色确定如下图所示:




我们可以看到,用户在使用

LDAP

认证之后,

LdapAuthenticationProvider

委托给了一个

LdapAuthoritiesPopulator



DefaultLdapAuthoritiesPopulator

将会尝试在

LDAP

等级中另一个条目的同级或下级属性中查找认证用户的

DN

。(译者注:即在

LDAP

目录角色相关的条目中寻找当前用户,以确定用户的角色)



查找用户角色分配的

DN

是通过

group-search-base

属性定义的——在我们的例子中,我们这样设置

group-search-base=”ou=Groups”

。当一个用户的

DN



group-search-base DN

下面的条目中时,包含用户

DN

的条目中的一个属性将会作为这些用户的角色。

【你可能注意到我们混合使用了属性的写法——在类流程图中使用了

groupSearchBase

,在文本中使用的是

group-search-base

。这是有意的——文本中对应的是

XML

配置属性而图中指的是相关类的成员(属性)。他们的命名相似,但是在不同的上下文中(

XML



Java

)要适当调整。】


Spring Security

中的角色和

LDAP

中的用户如何关联还是有点令人迷惑,所以让我们看一下

JBCP Pets

库以及用户与角色关联是如何进行的。

DefaultLdapAuthoritiesPopulator

使用了几个

<ldap-authentication-provider>

声明的属性来管理为用户查找角色。这些属性大致按以下的顺序使用:


l




group-search-base

:它定义了基础的

DN



LDAP

集成应该基于此往下为用户查找一个或多个的匹配项。默认值会在

LDAP

根中进行查找,这可能会代价较高;


l




group-search-filter

:它定义了

LDAP

查找的过滤器,用来匹配用户的

DN



group-search-base

之下的条目属性。这个过滤器通过两个参数进行参数化设置——第一个(

{0}

)作为用户的

DN

,第二个作为(

{1}

)作为用户的名字。默认值为(

uniqueMember={0}

)。


l




group-role-attribute

:它定义了匹配条目中用来组装用户

GrantedAuthority

的属性,默认值为

cn


l




role-prefix

:要拼到在

group-role-attribute

中发现值的前缀以产生

Spring Security



GrantedAuthority

。默认值为

ROLE_

这对于新的开发人员可能会比较抽象和困难,因为这与我们基于

JDBC



UserDetailsService

实现有很大的区别。让我们以

JBCP Pets LDAP

目录中的

ldapguest

用户登录以了解其过程。

用户的

DN



uid=ldapguest,ou=Users,dc=jbcppets,dc=com



group-search-base

被配置成了

ou=Groups

。对于这个

ou



LDAP

树展现如下:




我们可以看到在

ou=Groups

之下,有两个条目(

cn=Admin



cn=User

)。每个条目都具有

objectClass: groupOfUniqueNames

(你可能会记起我们在本章前面讨论过的对象类)。这种类型的

LDAP

对象允许多个

DN

值存储在这个条目下并进行逻辑分组。条目

cn=User

的属性列在下图中:




我们可以看到

cn=User



uniqueMember

属性用来标识这个组里面的

LDAP

用户。你也可能会发现

uniqueMember

的属性值就是对应用户的

DN

现在再看角色搜索的逻辑的就很容易了。从

ou=Groups (group-search-base)

开始,

Spring Security

将会查找任何

uniqueMember

属性值与用户

DN



group-search-filter

)匹配的条目。当它找到匹配的条目,条目的

cn

值(

group-role-attribute

)——在本例中即为

User

,将会加上

ROLE_ (role-prefix)

前缀然后转换成大写字母组成用户的

GrantedAuthority

。一旦我们使用过它,再理解起来就容易一些了,不是吗?



Spring LDAP

很灵活。要记住的是尽管这是一个组织

LDAP

兼容

Spring Security

的方式,但是通常的使用场景恰恰相反——

LDAP

目录已经存在,

Spring Security

需要织入。在很多场景下,你可以重新配置

Spring Security

来处理

LDAP

的等级结构。但是,很关键的一点是你要有效规划并理解

Spring

在查询时如何与

LDAP

一起工作。开动你的大脑,勾画出用户查找和组查找以及你能想到的最优方案——让查询范围尽可能小和精确。】

如果你此时还是感到困惑,我们建议你休息一下然后尝试使用

Apache Directory Studio

来看一下运行系统配置的嵌入式

LDAP

服务器。如果你按照前面描述的算法,尝试自己查找一下目录将会有助于你了解

Spring Security LDAP

配置的流程。


匹配

UserDetails

的其它属性



最后,在通过

LDAP

查找分配给用户

GrantedAuthority

后,

o.s.s.ldap.userdetails.LdapUserDetailsMapper

将会使用

o.s.s.ldap.userdetails.UserDetailsContextMapper

来获取另外的细节信息来填充

UserDetails



使用我们到现在为止配置的

<ldap-authentication-provider>



LdapUserDetailsMapper

将会使用用户

LDAP

条目中的信息填充

UserDetails

对象。




我们稍后将会看到

UserDetailsContextMapper

怎样配置才能从标准的

LDAP person



inetOrgPerson

中获取丰富的信息。使用基本的

LdapUserDetailsMapper

,仅仅能够存储用户名、密码以及

GrantedAuthority



尽管在

LDAP

用户认证里面还有很多的结构,但是你会发现整体的流程与我们前面学习的

JDBC

认证很类似(认证用户、填充

GrantedAuthoritys

)。如同

JDBC

认证中那样,在

LDAP

集成中也有进行高级配置的能力——让我们了解的更深入一些并看看还能做什么。