Spring Security-3.0.1 中文官方文档(翻译版)

Spring Security-3.0.1 中文官方文档(翻译版)

2023年6月25日发(作者:)

Spring Security-3.0.1 中文官方文档(翻译版)

这次发布的Spring Security-3.0.1 是一个bug fix 版,主要是对3.0 中存在的一些问题进行修

正。文档中没有添加新功能的介绍,但是将之前拼写错误的一些类名进行了修正,建议开发

者以这一版本的文档为参考。

另:Spring Security 从2010-01-01 以后,版本控制从SVN 换成了GIT,我们在翻译文档的

时候,主要是根据SVN 的变化来进行文档内容的比对,这次换成GIT 后,感觉缺少了之前

那种文本比对工具,如果有对GIT 熟悉的朋友,还请推荐一下文本比对的工具,谢谢。

序言

I. 入门

1. 介绍

1.1. Spring Security 是什么?

1.2. 历史

1.3. 发行版本号

1.4. 获得Spring Security

1.4.1. 项目模块

1.4.1.1. Core -

1.4.1.2. Web -

1.4.1.3. Config -

1.4.1.4. LDAP -

1.4.1.5. ACL -

1.4.1.6. CAS -

1.4.1.7. OpenID -

1.4.2. 获得源代码

2. Security 命名空间配置

2.1. 介绍

2.1.1. 命名空间的设计

2.2. 开始使用安全命名空间配置

2.2.1. 配置

2.2.2. 最小 配置

2.2.2.1. auto-config 包含了什么?

2.2.2.2. 表单和基本登录选项

2.2.3. 使用其他认证提供器

2.2.3.1. 添加一个密码编码器

2.3. 高级web 特性

2.3.1. Remember-Me 认证

2.3.2. 添加HTTP/HTTPS 信道安全

2.3.3. 会话管理

2.3.3.1. 检测超时

2.3.3.2. 同步会话控制

2.3.3.3. 防止Session 固定攻击

2.3.4. 对OpenID 的支持

2.3.4.1. 属性交换 2.3.5. 添加你自己的filter

2.3.5.1. 设置自定义AuthenticationEntryPoint

2.4. 保护方法

2.4.1. 元素

2.4.1.1. 使用protect-pointcut 添加安全切点

2.5. 默认的AccessDecisionManager

2.5.1. 自定义AccessDecisionManager

2.6. 验证管理器和命名空间

3. 示例程序

3.1. Tutorial 示例

3.2. Contacts

3.3. LDAP 例子

3.4. CAS 例子

3.5. Pre-Authentication 例子

4. Spring Security 社区

4.1. 任务跟踪

4.2. 成为参与者

4.3. 更多信息

II. 结构和实现

5. 技术概述

5.1. 运行环境

5.2. 核心组件

5.2.1. SecurityContextHolder, SecurityContext 和Authentication 对象

5.2.1.1. 获得当前用户的信息

5.2.2. UserDetailsService

5.2.3. GrantedAuthority

5.2.4. 小结

5.3. 验证

5.3.1. 什么是Spring Security 的验证呢?

5.3.2. 直接设置SecurityContextHolder 的内容

5.4. 在web 应用中验证

5.4.1. ExceptionTranslationFilter

5.4.2. AuthenticationEntryPoint

5.4.3. 验证机制

5.4.4. 在请求之间保存SecurityContext 。

5.5. Spring Security 中的访问控制(验证)

5.5.1. 安全和AOP 建议

5.5.2. 安全对象和AbstractSecurityInterceptor

5.5.2.1. 配置属性是什么?

5.5.2.2. RunAsManager

5.5.2.3. AfterInvocationManager

5.5.2.4. 扩展安全对象模型

5.6. 国际化

6. 核心服务 6.1. The AuthenticationManager , ProviderManager 和AuthenticationProvider s

6.1.1. DaoAuthenticationProvider

6.2. UserDetailsService 实现

6.2.1. 内存认证

6.2.2. JdbcDaoImpl

6.2.2.1. 权限分组

6.3. 密码加密

6.3.1. 什么是散列加密?

6.3.2. 为散列加点儿盐

6.3.3. 散列和认证

III. web 应用安全

7. 安全过滤器链

7.1. DelegatingFilterProxy

7.2. FilterChainProxy

7.2.1. 绕过过滤器链

7.3. 过滤器顺序

7.4. 使用其他过滤器—— 基于框架

8. 核心安全过滤器

8.1. FilterSecurityInterceptor

8.2. ExceptionTranslationFilter

8.2.1. AuthenticationEntryPoint

8.2.2. AccessDeniedHandler

8.3. SecurityContextPersistenceFilter

8.3.1. SecurityContextRepository

8.4. UsernamePasswordAuthenticationFilter

8.4.1. 认证成功和失败的应用流程

9. Basic(基本)和Digest(摘要)验证

9.1. BasicAuthenticationFilter

9.1.1. 配置

9.2. DigestAuthenticationFilter

9.2.1. Configuration

10. Remember-Me 认证

10.1. 概述

10.2. 简单基于散列标记的方法

10.3. 持久化标记方法

10.4. Remember-Me 接口和实现

10.4.1. TokenBasedRememberMeServices

10.4.2. PersistentTokenBasedRememberMeServices

11. 会话管理

11.1. SessionManagementFilter

11.2. SessionAuthenticationStrategy

11.3. 同步会话

12. 匿名认证

12.1. 概述 12.2. 配置

12.3. AuthenticationTrustResolver

IV. 授权

13. 验证架构

13.1. 验证

13.2. 处理预调用

13.2.1. AccessDecisionManager

13.2.2. 基于投票的AccessDecisionManager 实现

13.2.2.1. RoleVoter

13.2.2.2. AuthenticatedVoter

13.2.2.3. Custom Voters

13.3. 处理后决定

14. 安全对象实现

14.1. AOP 联盟(MethodInvocation) 安全拦截器

14.1.1. 精确的MethodSecurityIterceptor 配置

14.2. AspectJ (JoinPoint) 安全拦截器

15. 基于表达式的权限控制

15.1. 概述

15.1.1. 常用内建表达式

15.2. Web 安全表达式

15.3. 方法安全表达式

15.3.1. @Pre 和@Post 注解

15.3.1.1. 访问控制使用@PreAuthorize 和@PostAuthorize

15.3.1.2. 过滤使用@PreFilter 和@PostFilter

16. acegi 到spring security 的转换方式

16.1. Spring Security 是什么

16.2. 目标

16.3. 步骤

16.4. 总结

V. 高级话题

17. 领域对象安全(ACLs)

17.1. 概述

17.2. 关键概念

17.3. 开始

18. 预认证场景

18.1. 预认证框架类

18.1.1. AbstractPreAuthenticatedProcessingFilter

18.1.2. AbstractPreAuthenticatedAuthenticationDetailsSource

18.1.2.1. J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource

18.1.3. PreAuthenticatedAuthenticationProvider

18.1.4. Http403ForbiddenEntryPoint

18.2. 具体实现

18.2.1. 请求头认证(Siteminder)

18.2.1.1. Siteminder 示例配置 18.2.2. J2EE 容器认证

19. LDAP 认证

19.1. 综述

19.2. 在Spring Security 里使用LDAP

19.3. 配置LDAP 服务器

19.3.1. 使用嵌入测试服务器

19.3.2. 使用绑定认证

19.3.3. 读取授权

19.4. 实现类

19.4.1. LdapAuthenticator 实现

19.4.1.1. 常用功能

19.4.1.2. BindAuthenticator

19.4.1.3. PasswordComparisonAuthenticator

19.4.1.4. 活动目录认证

19.4.2. 链接到LDAP 服务器

19.4.3. LDAP 搜索对象

19.4.3.1. FilterBasedLdapUserSearch

19.4.4. LdapAuthoritiesPopulator

19.4.5. Spring Bean 配置

19.4.6. LDAP 属性和自定义UserDetails

20. JSP 标签库

20.1. 声明Taglib

20.2. authorize 标签

20.3. authentication 标签

20.4. accesscontrollist 标签

21. Java 认证和授权服务(JAAS)供应器

21.1. 概述

21.2. 配置

21.2.1. JAAS CallbackHandler

21.2.2. JAAS AuthorityGranter

22. CAS 认证

22.1. 概述

22.2. CAS 是如何工作的

22.3. 配置CAS 客户端

23. X.509 认证

23.1. 概述

23.2. 把X.509 认证添加到你的web 系统中

23.3. 为tomcat 配置SSL

24. 替换验证身份

24.1. 概述

24.2. 配置

A. 安全数据库表结构

A.1. User 表

A.1.1. 组权限 A.2. 持久登陆(Remember-Me)表

A.3. ACL 表

A.3.1. Hypersonic SQL

A.3.1.1. PostgreSQL

B. 安全命名空间

B.1. Web 应用安全- 元素

B.1.1. 属性

B.1.1.1. servlet-api-provision

B.1.1.2. path-type

B.1.1.3. lowercase-comparisons

B.1.1.4. realm

B.1.1.5. entry-point-ref

B.1.1.6. access-decision-manager-ref

B.1.1.7. access-denied-page

B.1.1.8. once-per-request

B.1.1.9. create-session

B.1.2.

B.1.3. 元素

B.1.3.1. pattern

B.1.3.2. method

B.1.3.3. access

B.1.3.4. requires-channel

B.1.3.5. filters

B.1.4. 元素

B.1.5. 元素

B.1.5.1. login-page

B.1.5.2. login-processing-url

B.1.5.3. default-target-url

B.1.5.4. always-use-default-target

B.1.5.5. authentication-failure-url

B.1.5.6. authentication-success-handler-ref

B.1.5.7. authentication-failure-handler-ref

B.1.6. 元素

B.1.7. 元素

B.1.7.1. data-source-ref

B.1.7.2. token-repository-ref

B.1.7.3. services-ref

B.1.7.4. token-repository-ref

B.1.7.5. key 属性

B.1.7.6. token-validity-seconds

B.1.7.7. user-service-ref

B.1.8. 元素

B.1.8.1. session-fixation-protection

B.1.9. 元素 B.1.9.1. max-sessions 属性

B.1.9.2. expired-url 属性

B.1.9.3. error-if-maximum-exceeded 属性

B.1.9.4. session-registry-alias 和session-registry-ref 属性

B.1.10. 元素

B.1.11. 元素

B.1.11.1. subject-principal-regex 属性

B.1.11.2. user-service-ref 属性

B.1.12. 元素

B.1.13. 元素

B.1.13.1. logout-url 属性

B.1.13.2. logout-success-url 属性

B.1.13.3. invalidate-session 属性

B.1.14. 元素

B.2. 认证服务

B.2.1. 元素

B.2.1.1. 元素

B.2.1.2. 使用 来引用一个AuthenticationProvider Bean

B.3. 方法安全

B.3.1. 元素

B.3.1.1. secured-annotations 和jsr250-annotations 属性

B.3.1.2. 安全方法使用

B.3.1.3. 元素

B.3.2. LDAP 命名空间选项

B.3.2.1. 使用 元素定义LDAP 服务器

B.3.2.2. 元素

B.3.2.3. 元素

Part I. 入门

本指南的后面部分提供对框架结构和实现类的深入讨论,了解它们,对你进行复杂的定制是

十分重要的。在这部分,我们将介绍Spring Security 3.0,简要介绍该项目的历史,然后

看看如何开始在程序中使用框架。特别是,我们将看看命名控件配置提供了一个更加简单的

方式,在使用传统的spring bean 配置时,你不得不实现所有类。

我们也会看看可用的范例程序。它们值得试着运行,实验,在你阅读后面的章节之前- 你

可以在对框架有了更多连接之后再回来看这些例子。也请参考项目网站获得构建项目有用

的信息,另外链接到网站,视频和教程。

Chapter 1. 介绍

1.1. Spring Security是什么?

Spring Security 为基于J2EE 企业应用软件提供了全面安全服务。特别是使用领先的

J2EE 解决方案-spring 框架开发的企业软件项目。如果你没有使用Spring 开发企业软件,

我们热情的推荐你仔细研究一下。熟悉Spring-尤其是依赖注入原理-将帮助你更快的掌握

Spring Security。

人们使用Spring Security 有很多种原因, 不过通常吸引他们的是在J2EE Servlet 规范

或EJB 规范中找不到典型企业应用场景的解决方案。提到这些规范,特别要指出的是它们

不能在WAR 或EAR 级别进行移植。这样,如果你更换服务器环境, 就要,在新的目标环 境进行大量的工作,对你的应用系统进行重新配置安全。使用Spring Security 解决了这

些问题,也为你提供了很多有用的,可定制的其他安全特性。

你可能知道,安全包括两个主要操作, “认证”和“验证”(或权限控制)。这就是Spring

Security 面向的两个主要方向。“认证” 是为用户建立一个他所声明的主体的过程, (“主

体”一般是指用户,设备或可以在你系统中执行行动的其他系统)。“验证”指的一个用户能

否在你的应用中执行某个操作。在到达授权判断之前,身份的主体已经由身份验证过程建

立了。这些概念是通用的,不是Spring Security 特有的。

在身份验证层面,Spring Security 广泛支持各种身份验证模式。这些验证模型绝大多数

都由第三方提供,或正在开发的有关标准机构提供的,例如Internet Engineering Task

Force。作为补充,Spring Security 也提供了自己的一套验证功能。Spring Security

目前支持认证一体化和如下认证技术:

HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)

HTTP Digest authentication headers (一个基于IEFT RFC 的标准)

HTTP X.509 client certificate exchange (一个基于IEFT RFC 的标准)

LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)

Form-based authentication (提供简单用户接口的需求)

OpenID authentication

基于预先建立的请求头进行认证(比如Computer Associates Siteminder)

JA-SIG Central Authentication Service (也被称为CAS,这是一个流行的开源单点登

录系统)

Transparent authentication context propagation for Remote Method Invocation

(RMI) and HttpInvoker (一个Spring 远程调用协议)

Automatic "remember-me" authentication (这样你可以设置一段时间,避免在一段时

间内还需要重新验证)

Anonymous authentication (允许任何调用,自动假设一个特定的安全主体)

Run-as authentication (这在一个会话内使用不同安全身份的时候是非常有用的)

Java Authentication and Authorization Service (JAAS)

JEE Container autentication (这样,你可以继续使用容器管理认证,如果想的话)

Kerberos

Java Open Source Single Sign On (JOSSO) *

OpenNMS Network Management Platform *

AppFuse *

AndroMDA *

Mule ESB *

Direct Web Request (DWR) *

Grails *

Tapestry *

JTrac *

Jasypt *

Roller *

Elastic Plath *

Atlassian Crowd *

你自己的认证系统(向下看) (* 是指由第三方提供,查看我们的整合网页,获得最新详情的链接。)

许多独立软件供应商(ISVs, independent software vendors)采用Spring Security,

是因为它拥有丰富灵活的验证模型。这样,无论终端用户需要什么,他们都可以快速集成

到系统中,不用花很多功夫,也不用让用户改变运行环境。如果上述的验证机制都没有满

足你的需要,Spring Security 是一个开放的平台,编写自己的验证机制是十分简单的。

Spring Security 的许多企业用户需要整合不遵循任何特定安全标准的“遗留”系统,Spring

Security 在这类系统上也表现的很好。

有时基本的认证是不够的。有时你需要根据在主体和应用交互的方式来应用不同的安全措

施。比如,你可能,为了保护密码,不被窃听或受到中间人攻击,希望确保请求只通过HTTPS

到达。这在防止暴力攻击保护密码恢复过程特别有帮助, 或者简单的,让人难以复制你的

系统的关键字内容。为了帮助你实现这些目标,Spring Security 完全支持自动“信道安

全”, 整合jcaptcha 一体化进行人类用户检测。

Spring Security 不仅提供认证功能,也提供了完备的授权功能。在授权方面主要有三个

领域,授权web 请求,授权被调用方法,授权访问单个对象的实例。为了帮你了解它们之

间的区别,对照考虑授在Servlet 规范web 模式安全,EJB 容器管理安全,和文件系统安

全方面的授权方式。Spring Security 在所有这些重要领域都提供了完备的能力,我们将

在这份参考指南的后面进行探讨。

1.2. 历史

Spring Security 开始于2003年年底,““spring 的acegi 安全系统”。起因是Spring 开

发者邮件列表中的一个问题,有人提问是否考虑提供一个基于spring 的安全实现。在当

时Spring 的社区相对较小(尤其是和今天的规模比!),其实Spring 本身是从2003年初

才作为一个sourceforge 的项目出现的。对这个问题的回应是,这的确是一个值得研究的

领域,虽然限于时间问题阻碍了对它的继续研究。

有鉴于此,一个简单的安全实现建立起来了,但没有发布。几周之后,spring 社区的其他

成员询问安全问题,代码就被提供给了他们。随后又有人请求,在2004年一月左右,有

20人在使用这些代码。另外一些人加入到这些先行者中来,并建议在sourceforge 上建

立一个项目,项目在2004年3月正式建立起来。

在早期,项目本身没有自己的认证模块。认证过程都是依赖容器管理安全的,而acegi 则

注重授权。这在一开始是合适的,但随着越来越多用户要求提供额外的容器支持,基于容

器认证的限制就显现出来了。还有一个有关的问题,向容器的classpath 中添加新jar,

常常让最终用户感到困惑,又容易出现配置错误。

随后acegi 加入了认证服务。大约一年后,acegi 成为spring 的官方子项目。经过了两

年半在许多生产软件项目中的活跃使用和数以万计的改善和社区的贡献,1.0.0最终版本发

布于2006年5月。

acegi 在2007年年底,正式成为spring 组合项目,被更名为“Spring Security”。

现在,Spring Security 成为了一个强大而又活跃的开源社区。在Spring Security 支持

论坛上有成千上万的信息。有一个积极的核心开发团队专职开发,一个积极的社区定期共

享补丁并支持他们的同伴。

1.3. 发行版本号

了解spring Security 发行版本号是非常有用的。它可以帮助你判断升级到新的版本是否

需要花费很大的精力。我们使用apache 便携式运行项目版本指南,可以在以下网址查看

/。为了方便大家,我们引用页面上的一段介绍:

“版本号是一个包含三个整数的组合:主要版本号.次要版本号.补丁。基本思路是主要版本

是不兼容的,大规模升级API。次要版本号在源代码和二进制要与老版本保持兼容,补丁 则意味着向前向后的完全兼容。”

1.4. 获得Spring Security

你可以通过多种方式获得Spring Security。你可以下载打包好的发行包,从Spring 的网

站下载页,下载单独的jar(和实例WAR 文件)从Maven 中央资源库(或者SpringSource

Maven 资源库,获得快照和里程碑发布)。可选的,你可以通过源代码创建项目。参考项

目网站获得更多细节。

1.4.1. 项目模块

在Spring Security 3.0中,项目已经分割成单独的jar,这更清楚的按照功能进行分割模

块和第三方依赖。如果你在使用Maven 来构建你的项目,你需要把这些模块添加到你的

中。即使你没有使用Maven,我们也推荐你参考这个 文件, 来了

解第三方依赖和对应的版本。可选的,一个好办法是参考实例应用中包含的依赖库。

1.4.1.1. Core -

包含了核心认证和权限控制类和接口, 运程支持和基本供应API。使用Spring Security

所必须的。支持单独运行的应用, 远程客户端,方法(服务层)安全和JDBC 用户供应。

包含顶级包:

tication

ioning

ng

1.4.1.2. Web -

包含过滤器和对应的web 安全架构代码。任何需要依赖servlet API 的。你将需要它,如

果你需要Spring Security Web 认证服务和基于URL 的权限控制。主包是

1.4.1.3. Config -

包含安全命名控制解析代码(因此我们不能直接把它用在你的应用中)。你需要它, 如果使

用了Spring Security XML 命名控制来进行配置。主包是

1.4.1.4. LDAP -

LDAP 认证和实现代码,如果你需要使用LDAP 认证或管理LDAP 用户实体就是必须的。顶

级包是。

1.4.1.5. ACL -

处理领域对象ACL 实现。用来提供安全给特定的领域对象实例,在你的应用中。顶级包是

1.4.1.6. CAS -

Spring Security 的CAs 客户端集成。如果你希望使用Spring Security web 认证整合

一个CAS 单点登录服务器。顶级包是。

1.4.1.7. OpenID -

OpenID web 认证支持。用来认证用户, 通过一个外部的OpenID 服务。

。需要OpenID4Java。

1.4.2. 获得源代码

Spring Security 是一个开源项目,我们大力推荐你从subversion 获得源代码。这样你

可以获得所有的示例,你可以很容易的建立目前最新的项目。获得项目的源代码对调试也 有很大的帮助。异常堆栈不再是模糊的黑盒问题,你可以直接找到发生问题的那一行,查

找发生了什么额外难题。源代码也是项目的最终文档,常常是最简单的方法,找出这些事

情是如何工作的。

要像获得项目最新的源代码,使用如下subversion 命令:

svn checkout

/svnroot/acegisecurity/spring-security/trunk/

你可以获得特定版本的源代码

/svnroot/acegisecurity/spring-security

/tags/.

Security 命名空间配置

2.1. 介绍

从Spring-2.0开始可以使用命名空间的配置方式。使用它呢,可以通过附加xml 架构,

为传统的spring beans 应用环境语法做补充。你可以在spring

参考文档得到更多信息。命

名空间元素可以简单的配置单个bean,或使用更强大的,定义一个备用配置语法,这可以

更加紧密的匹配问题域,隐藏用户背后的复杂性。简单元素可能隐藏事实,多种bean 和

处理步骤添加到应用环境中。比如,把下面的security 命名元素添加到应用环境中,将会

为测试用途,在应用内部启动一个内嵌LDAP 服务器:

这比配置一个Apache 目录服务器bean 要简单得多。最常见的替代配置需求都可以使用

ldap-server 元素的属性进行配置,这样用户就不用担心他们需要设置什么,不用担心bean

里的各种属性。[1]。使用一个良好的XML 编辑器来编辑应用环境文件,应该提供可用的属

性和元素信息。我们推荐你尝试一下SpringSource 工具套件因为它具有处理spring 组合

命名空间的特殊功能。

要开始在你的应用环境里使用security 命名空间,你所需要的就是把架构声明添加到你的

应用环境文件里:

xmlns:security="/schema/security"

xmlns:xsi="/2001/XMLSchema-instance"

xsi:schemaLocation="/schema/beans

/schema/beans/

/schema/security

/schema/security/">

...

在许多例子里,你会看到(在示例中)应用,我们通常使用"security"作为默认的命名空间,

而不是"beans",这意味着我们可以省略所有security 命名空间元素的前缀,使上下文更

容易阅读。如果你把应用上下文分割成单独的文件,让你的安全配置都放到其中一个文件

里,这样更容易使用这种配置方法。你的安全应用上下文应该像这样开头

xmlns:beans="/schema/beans"

xmlns:xsi="/2001/XMLSchema-instance"

xsi:schemaLocation="/schema/beans

/schema/beans/ /schema/security

/schema/security/">

...

就在这一章里,我们都将假设使用这种语法。

2.1.1. 命名空间的设计

命名空间被用来设计成,处理框架内最常见的功能,提供一个简化和简洁的语法,使他们在

一个应用程序里。这种设计是基于框架内的大型依赖,可以分割成下面这些部分:

Web/HTTP

安全- 最复杂的部分。设置过滤器和相关的服务bean 来应用框架验证机制,

保护URL,渲染登录和错误页面还有更多。

业务类(方法)安全- 可选的安全服务层。

AuthenticationManager

- 通过框架的其它部分,处理认证请求。

AccessDecisionManager

- 提供访问的决定,适用于web 以及方法的安全。一个默认的

主体会被注册,但是你也可以选择自定义一个,使用正常的spring bean 语法进行声明。

AuthenticationProviders

- 验证管理器验证用户的机制。该命名空间提供几种标准选

项,意味着使用传统语法添加自定义bean。

UserDetailsService

- 密切相关的认证供应器,但往往也需要由其他bean 需要。

下一章中,我们将看到如何把这些放到一起工作。

2.2. 开始使用安全命名空间配置

在本节中,我们来看看如何使用一些框架里的主要配置,建立一个命名空间配置。我们假

设你最初想要尽快的启动运行,为已有的web 应用添加认证支持和权限控制,使用一些测

试登录。然后我们看一下如何修改一下,使用数据库或其他安全信息参数。在以后的章节

里我们将引入更多高级的命名空间配置选项。

2.2.1. 配置

我们要做的第一件事是把下面的filter 声明添加到 文件中:

springSecurityFilterChain

tingFilterProxy

springSecurityFilterChain

/*

这是为Spring Security 的web 机制提供了一个调用钩子。DelegatingFilterProxy 是

一个Spring Framework 的类,它可以代理一个application context 中定义的Spring

bean 所实现的filter。这种情况下,bean 的名字是"springSecurityFilterChain",这是

由命名空间创建的用于处理web 安全的一个内部的机制。注意,你不应该自己使用这个

bean 的名字。一旦你把这个添加到你的 中, 你就准备好开始编辑呢的

application context 文件了。web 安全服务是使用元素配置的。

2.2.2. 最小配置

只需要进行如下配置就可以实现安全配置:

这表示,我们要保护应用程序中的所有URL,只有拥有ROLE_USER 角色的用户才能访问。

元素是所有web 相关的命名空间功能的上级元素。元素定义了

pattern,用来匹配进入的请求URL,使用一个ant 路径语法。access 属性定义了请求

匹配了指定模式时的需求。使用默认的配置, 这个一般是一个逗号分隔的角色队列,一个

用户中的一个必须被允许访问请求。前缀“ROLE_”表示的是一个用户应该拥有的权限比

对。换句话说, 一个普通的基于角色的约束应该被使用。Spring Security 中的访问控制

不限于简单角色的应用(因此,我们使用不同的前缀来区分不同的安全属性).我们会在后

面看到这些解释是可变的[2]

Note

你可以使用多个元素为不同URL 的集合定义不同的访问需求, 它们会被

归入一个有序队列中,每次取出最先匹配的一个元素使用。所以你必须把期望使用的匹配

条件放到最上边。你也可以添加一个method 属性来限制匹配一个特定的HTTP

method(GET, POST, PUT 等等)。对于一个模式同时定义在定义了method 和未定义

method 的情况, 指定method 的匹配会无视顺序优先被使用。

要是想添加一些用户,你可以直接使用下面的命名空间直接定义一些测试数据:

如果你熟悉以前的版本,你很可能已经猜到了这里是怎么回事。元素会创建一个

FilterChainProxy 和filter 使用的bean。以前常常出现的,因为filter 顺序不正确产生

的问题,不会再出现了,现在这些过滤器的位置都是预定义好的。

元素创建了一个DaoAuthenticationProvider bean ,

元素创建了一个InMemoryDaoImpl。所有authentication-provider

元素必须作为 的子元素, 它创建了一个

ProviderManager,并把authentication provider 注册到它里面。你可以在命名空间附

录中找到关于创建这个bean 的更新信息。很值得去交叉检查一下这里,如果你希望开始

理解框架中哪些是重要的类以及它们是如何使用的,特别是如果你希望以后做一些自定义

工作。

上面的配置定义了两个用户,他们在应用程序中的密码和角色(用在权限控制上)。也可以

从一个标准properties 文件中读取这些信息,使用user-service 的properties 属性。参

考in-memory authentication

获得更多信息。使用元素意味

着用户信息将被认证管理用作处理认证请求。你可以拥有多个

元素来定义不同的认证数据, 每个会被需要时使用。

现在,你可以启动程序,然后就会进入登录流程了。试试这个,或者试试工程里的“tutorial”

例子。上述配置实际上把很多服务添加到了程序里,因为我们使用了auto-config 属性。

比如,表单登录和"remember-me"服务自动启动了。

2.2.2.1. auto-config包含了什么? 我们在上面用到的auto-config 属性,其实是下面这些配置的缩写:

这些元素分别与form-login,基本认证和注销处理对应。[3]

他们拥有各自的属性,来改

变他们的具体行为。

2.2.2.2. 表单和基本登录选项

你也许想知道,在需要登录的时候,去哪里找这个登录页面,到现在为止我们都没有提到任

何的HTML 或JSP 文件。实际上,如果我们没有确切的指定一个页面用来登录,Spring

Security 会自动生成一个,基于可用的功能,为这个URL 使用标准的数据,处理提交的登

录,然后在登陆后发送到默认的目标URL。然而,命名空间提供了许多支持,让你可以自

定义这些选项。比如,如果你想实现自己的登录页面,你可以使用:

注意,你依旧可以使用auto-config。这个form-login 元素会覆盖默认的设置。也要注

意我们需要添加额外的intercept-url 元素,指定用来做登录的页面的URL, 这些URL 应

该可以被匿名访问。[4]

否则,这些请求会被/**部分拦截,它没法访问到登录页面。这是

一个很常见的配置错误,它会导致系统出现无限循环。Spring Security 会在日志中发出

一个警告,如果你的登录页面是被保护的。也可能让所有的请求都匹配特定的模式,通过

完全的安全过滤器链:

主要的是意识到这些请求会被完全忽略,对任何Spring Security 中web 相关的配置,或

额外的属性,比如requires-channel, 所以你会不能访问当前用户信息,或调用被保护

方法,在请求过程中。使用access='IS_AUTHENTICATED_ANONYMOUSLY'作为一个

选择方式如果你还想要安全过滤器链起作用。

如果你希望使用基本认证,代替表单登录,可以把配置改为:

基本身份认证会被优先用到,在用户尝试访问一个受保护的资源时,用来提示用户登录。在

这种配置中,表单登录依然是可用的,如果你还想用的话,比如,把一个登录表单内嵌到其

他页面里。

2.2.2.2.1.

设置一个默认的提交登陆目标 如果在进行表单登陆之前,没有试图去访问一个被保护的资源,default-target-url 就会起

作用。这是用户登陆后会跳转到的URL , 默认是"/" 。你也可以把

always-use-default-target 属性配置成"true",这样用户就会一直跳转到这一页(无论

登陆是“跳转过来的”还是用户特定进行登陆)。如果你的系统一直需要用户从首页进入,就

可以使用它了,比如:

always-use-default-target='true' />

2.2.3. 使用其他认证提供器

现实中,你会需要更大型的用户信息源,而不是写在application context 里的几个名字。

多数情况下,你会想把用户信息保存到数据库或者是LDAP 服务器里。LDAP 命名空间会

在LDAP 章里详细讨论,所以我们这里不会讲它。如果你自定义了一个Spring Security

的UserDetailsService 实现, 在你的application context 中名叫

"myUserDetailsService",然后你可以使用下面的验证。

如果你想用数据库,可以使用下面的方式

这里的“securityDataSource”就是DataSource bean 在application context 里的名

字,它指向了包含着Spring Security

用户信息的表。另外,你可以配置一个Spring

Security JdbcDaoImpl bean,使用user-service-ref 属性指定:

class="oImpl">

你也可以使用标准的AuthenticationProvider 类,像下面

这里myAuthenticationProvider 是你的application context 中的一个bean 的名字,

它实现了AuthenticationProvider。查看Section 2.6, “验证管理器和命名空间”了解更多信 息, AuthenticationManager 使用命名空间在Spring Security 中是如何配置的。

2.2.3.1. 添加一个密码编码器

你的密码数据通常要使用一种散列算法进行编码。使用元素支持

这个功能。使用SHA 加密密码,原始的认证供应器配置,看起来就像这样:

authorities="ROLE_USER, ROLE_ADMIN" />

authorities="ROLE_USER" />

在使用散列密码时,用盐值防止字典攻击是个好主意,Spring Security 也支持这个功能。

理想情况下, 你可能想为每个用户随机生成一个盐值, 不过, 你可以使用从

UserDetailsService 读取出来的UserDetails 对象中的属性。比如,使用username 属

性,你可以这样用:

你可以通过password-encoder 的ref 属性,指定一个自定义的密码编码器bean。这应

该包含application context 中一个bean 的名字, 它应该是Spring Security 的

PasswordEncoder 接口的一个实例。

2.3. 高级web特性

2.3.1. Remember-Me认证

参考Remember-Me 章获得remember-me 命名空间配置的详细信息。

2.3.2. 添加HTTP/HTTPS信道安全

如果你的同时支持HTTP 和HTTPS 协议,然后你要求特定的URL 只能使用HTTPS,这时

可以直接使用的requires-channel 属性:

requires-channel="https"/>

...

使用了这个配置以后,如果用户通过HTTP 尝试访问"/secure/**"匹配的网址,他们会先

被重定向到HTTPS 网址下。可用的选项有"http", "https" 或"any"。使用"any"意味

着使用HTTP 或HTTPS 都可以。

如果你的程序使用的不是HTTP 或HTTPS 的标准端口,你可以用下面的方式指定端口对应

关系:

...

你可以在???找到更详细的讨论。

2.3.3. 会话管理

2.3.3.1. 检测超时

你可以配置Spring Security 检测失效的session ID, 并把用户转发到对应的URL。这

可以通过session-management 元素配置: ...

invalid-session-url="/" />

2.3.3.2. 同步会话控制

如果你希望限制单个用户只能登录到你的程序一次,Spring Security 通过添加下面简单的

部分支持这个功能。首先,你需要把下面的监听器添加到你的 文件里,让Spring

Security 获得session 生存周期事件:

ssionEventPublisher

然后,在你的application context 加入如下部分:

...

这将防止一个用户重复登录好几次-第二次登录会让第一次登录失效。通常我们更想防止第

二次登录,这时候我们可以使用

...

第二次登录将被阻止, 通过“ 注入” , 我们的意思是用户会被转发到

authentication-failure-url,如果使用了form-based 登录。如果第二次验证使用了其

他非内置的机制,比如“remember-me”,一个“未认证”(402)错误就会发送给客户端。如

果你希望使用一个错误页面替代, 你可以在session-management 中添加

session-authentication-error-url 属性。

如果你为form-based 登录使用了自定义认证, 你就必须特别配置同步会话控制。更多的

细节可以在会话管理章节找到。

2.3.3.3. 防止Session固定攻击

Session 固定攻击是一个潜在危险,当一个恶意攻击者可以创建一个session 访问一个网站

的时候,然后说服另一个用户登录到同一个会话上(比如,发送给他们一个包含了session

标识参数的链接)。Spring Security 通过在用户登录时,创建一个新session 来防止这

个问题。如果你不需要保护,或者它与其他一些需求冲突,你可以通过使用中的

session-fixation-protection 属性来配置它的行为,它有三个选项

migrateSession - 创建一个新session,把原来session 中所有属性复制到新session 中。这是默认值。

none - 什么也不做,继续使用原来的session。

newSession - 创建一个新的“干净的”session,不会复制session 中的数据。

2.3.4. 对OpenID的支持

命名空间支持OpenID

登录,替代普通的表单登录,或作为一种附加功能,只需要进行简单

的修改:

你应该注册一个OpenID 供应器(比如),然后把用户信息添加到你的内

中:

你应该可以使用 网站登录来进行验证了。也可能选择一个特定的

UserDetailsService bean 来使用OpenID,通过设置元素。查看上一节认证提供器获得

更多信息。请注意,上面用户配置中我们省略了密码属性,因为这里的用户数据只用来为

数据读取数据。内部会生成一个随机密码,放置我们使用用户数据时出现问题, 无论在你

的配置的地方使用认证信息。

2.3.4.1. 属性交换

支持OpenID 的属性交换。作为一个例子,下面的配置会尝试从OpenID 提供器中获得

email 和全名, 这些会被应用程序使用到:

required="true"

/>

每个OpenID 的“type”属性是一个URI,这是由特定的schema 决定的, 在这个例子中

是/。如果一个属性必须为了成功认证而获取,可以设置required。

确切的schema 和对属性的支持会依赖于你使用的OpenID 提供器。属性值作为认证过程

的一部分返回, 可以使用下面的代码在后面的过程中获得:

OpenIDAuthenticationToken token = (OpenIDAuthenticationToken)

text().getAuthentication();

List attributes = ributes();

OpenIDAttribute 包含的属性类型和获取的值(或者在多属性情况下是多个值)。我们将

看到更多关于SecurityContextHolder 如何使用的信息,只要我们在技术概述章节浏览核

心Spring Security 组件。

2.3.5. 添加你自己的filter

如果你以前使用过Spring Security,你就应该知道这个框架里维护了一个过滤器链,来提

供服务。你也许想把你自己的过滤器添加到链条的特定位置,或者使用一个Spring

Security 的过滤器,这个过滤器现在还没有在命名空间配置中进行支持(比如CAS)。或

者你想要使用一个特定版本的标准命名空间过滤器, 比如 创建的 UsernamePasswordAuthenticationFilter,从而获得一些额外的配置选项的优势,这些

可以通过直接配置bean 获得。你如何在命名空间配置里实现这些功能呢?过滤器链现在

已经不能直接看到了。

过滤器顺序在使用命名空间的时候是被严格执行的。当application context 创建时,过

滤器bean 通过namespace 的处理代码进行排序, 标准的spring security 过滤器都有

自己的假名和一个容易记忆的位置。

Note

在之前的版本中,排序是在过滤器实例创建后执行的,在application context 的执行后

的过程中。在3.0+版本中,执行会在bean 元元素级别被执行,在bean 实例化之前。这

会影响到你如何添加自己的过滤器,实体过滤器列表必须在解析元素的过程中了

解这些, 所以3.0中的语法变化的很明显。

有关创建过滤器的过滤器,假名和命名空间元素,属性可以在Table 2.1, “标准过滤器假名和顺

序”中找到。过滤器按照次序排列在过滤器链中。

Table 2.1. 标准过滤器假名和顺序

假名过滤器累命名空间元素或属性

CHANNEL_FILTER ChannelProcessingFilter

http/intercept-url@requir

es-channel

CONCURRENT_SESSI

ON_FILTER

ConcurrentSessionFilter

session-management/con

currency-control

SECURITY_CONTEXT

_FILTER

SecurityContextPersistenceFil

ter

http

LOGOUT_FILTER LogoutFilter http/logout

X509_FILTER X509AuthenticationFilter http/x509

PRE_AUTH_FILTER

AstractPreAuthenticatedProce

ssingFilterSubclasses

N/A

CAS_FILTER CasAuthenticationFilter N/A

FORM_LOGIN_FILTER

UsernamePasswordAuthentic

ationFilter

http/form-login

BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic

SERVLET_API_SUPPO

RT_FILTER

SecurityContextHolderAware Filter

http/@servlet-api-provisi

on

REMEMBER_ME_FILT

ER

RememberMeAuthenticationF

ilter

http/remember-me

ANONYMOUS_FILTER SessionManagementFilter http/anonymous

SESSION_MANAGEM

ENT_FILTER

AnonymousAuthenticationFilt

er

session-management

EXCEPTION_TRANSL

ATION_FILTER

ExceptionTranslationFilter http

FILTER_SECURITY_IN

TERCEPTOR

FilterSecurityInterceptor http

SWITCH_USER_FILTE

R

SwitchUserAuthenticationFilt

er

N/A

你可以把你自己的过滤器添加到队列中,使用custom-filter 元素,使用这些名字中的一个,

来指定你的过滤器应该出现的位置:

你还可以使用after 或before 属性,如果你想把你的过滤器添加到队列中另一个过滤器

的前面或后面。可以分别在position 属性使用"FIRST" 或"LAST"来指定你想让你的过

滤器出现在队列元素的前面或后面。

避免过滤器位置发生冲突

如果你插入了一个自定义的过滤器,而这个过滤器可能与命名空间自己创建的标准过滤器放

在同一个位置上,这样首要的是你不要错误包含命名空间的版本信息。避免使用

auto-config 属性,然后删除所有会创建你希望替换的过滤器的元素。

注意, 你不能替换那些 元素自己使用而创建出的过滤器, 比如

SecurityContextPersistenceFilter, ExceptionTranslationFilter 或

FilterSecurityInterceptor。

如果你替换了一个命名空间的过滤器,而这个过滤器需要一个验证入口点(比如,认证过程

是通过一个未通过验证的用户访问受保护资源的尝试来触发的),你将也需要添加一个自定

义的入口点bean。 2.3.5.1. 设置自定义AuthenticationEntryPoint

如果你没有通过命名空间,使用表单登陆,OpenID 或基本认证,你可能想定义一个认证

过滤器,并使用传统bean 语法定义一个入口点然后把它链接到命名空间里,就像我们已经

看到的那样。对应的AuthenticationEntryPoint 可以使用 元素中的

entry-point-ref 属性来进行设置。

CAS 示例程序是一个在命名空间中使用自定义bean 的好例子,包含这种语法。如果你对

认证入口点并不熟悉,可以在技术纵览章中找到关于它们的讨论。

2.4. 保护方法

从版本2.0开始Spring Security 大幅改善了对你的服务层方法添加安全。它提供对

JSR-250安全注解的支持,这与框架提供的@secured 注解相似。从3.0开始,你也可以

使用新的基于表达式的注解。你可以提供安全给单个bean,使用intercept-methods 来装

饰bean 的声明, 或者你可以控制多个bean,通过实体服务层,使用AspectJ 演示的切

点功能。

2.4.1. 元素

这个元素用来在你的应用程序中启用基于安全的注解(通过在这个元素中设置响应的属性),

也可以用来声明将要应用在你的实体application context 中的安全切点组。你应该只定

义一个元素。下面的声明同时启用Spring Security 的

@Secured 和JSR-250注解:

jsr250-annotations="enabled"/>

向一个方法(或一个类或一个接口)添加注解,会限制对这个方法的访问。Spring Security

原生注解支持为方法定义一系列属性。这些属性将传递给AccessDecisionManager,进

行决策:

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")

public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")

public Account[] findAccounts();

@Secured("ROLE_TELLER")

public Account post(Account account, double amount);

}

为了使用新的基于表达式的预付,你可以好似用

对应的代码将会是这样

public interface BankService {

@PreAuthorize("isAnonymous()")

public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")

public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")

public Account post(Account account, double amount);

}

2.4.1.1. 使用protect-pointcut添加安全切点

protect-pointcut 是非常强大的,它让你可以用简单的声明对多个bean 的进行安全声明。 参考下面的例子:

access="ROLE_USER"/>

这样会保护application context 中的符合条件的bean 的所有方法,这些bean 要在

any 包下,类名以"Service"结尾。ROLE_USER 的角色才能调用这些方

法。就像URL 匹配一样,指定的匹配要放在切点队列的最前面,第一个匹配的表达式才会

被用到。

2.5. 默认的AccessDecisionManager

这章假设你有一些Spring Security 权限控制有关的架构知识。如果没有,你可以跳过这

段,以后再来看,因为这章只是为了自定义的用户设置的,需要在简单基于角色安全的基础

上加一些客户化的东西。

当你使用命名空间配置时,默认的AccessDecisionManager 实例会自动注册,然后用来

为方法调用和web URL 访问做验证, 这些都是基于你设置的intercept-url 和

protect-pointcut 权限属性内容(和注解中的内容,如果你使用注解控制方法的权限)。

默认的策略是使用一个AffirmativeBased AccessDecisionManager ,以及RoleVoter

和AuthenticatedVoter。可以在authorization

中获得更多信息。

2.5.1. 自定义AccessDecisionManager

如果你需要使用一个更复杂的访问控制策略,把它设置给方法和web 安全是很简单的。

对于方法安全,你可以设置global-security 里的access-decision-manager-ref 属性,

用对应AccessDecisionManager bean 在application context 里的id:

access-decision-manager-ref="myAccessDecisionManagerBean">

...

web 安全安全的语法也是一样,但是放在http 元素里:

...

2.6. 验证管理器和命名空间

主要接口提供了验证服务在Spring Security 中, 是AuthenticationManager。通常

是Spring Security 中ProviderManager 类的一个实例, 如果你以前使用过框架,你可

能已经很熟悉了。如果没有,它会在稍后被提及,在#tech-intro-authentication。bean

实例被使用authentication-manager 命名空间元素注册。你不能好似用一个自定义的

AuthenticationManager 如果你使用HTTp 或方法安全,在命名空间中,但是它不应该

是一个问题, 因为你完全控制了使用的AuthenticationProvider。

你可能注册额外的AuthenticationProviderbean, 在ProviderManager 中,你可以使

做这些事情,使用ref 属性, 这个属性的值,是你希望

添加的provider 的bean 的名字,比如:

...

另一个常见的需求是,上下文中的另一个bean 可能需要引用AuthenticationManager。

你可以为AuthenticationManager 注册一个别名,然后在application context 的其他

地方使用这个名字。

...

class="FormLoginFilter">

...

[1]你可以在LDAP

的章节里,找到更多有关使用的ldap-server 的元素。

[2]access 中逗号分隔的值的解释依赖使用的AccessDecisionManager

的实现。在Spring

Security 3.0中,这个属性也可以使用EL 表达式。

[3]在3.0之前按,这里列表中还包含remember-me 功能。这是因为一些配置上容易冲突

的问题所以在3.0中被移除了。在3.0中,AnonymousAuthenticationFilter 已经成为了

默认的配置的一部分,所以 元素无论是否设置auto-config

都会被添加到配置中。

[4]

参考匿名认证章节和AuthenticatedVoter

类获得更多细节, 和

IS_AUTHENTICATED_ANONYMOUSLY 如何被处理。

示例程序

项目中包含了很多web 实例程序。为了不让下载包变得太大,我们只把"tutorial"和

"contacts"两个例子放到了zip 发布包里。你可以自己编译部署它们,也可以从Maven

中央资源库里获得单独的war 文件。我们建议你使用前一种方法。你可以按照简介里的介

绍获得源代码, 使用maven 编译它们也很简单。如果你需要的话, 可以在

/security/网站上找到更多信息。

3.1. Tutorial示例

这个tutorial 示例是带你入门的很好的一个基础例子。它完全使用了简单命名空间配置。

编译好的应用就放在zip 发布包中, 已经准备好发布到你的web 容器中

()。使用了form-based 验证机制,与

常用的remember-me

验证提供器相结合,自动使用cookie 记录登录信息。

我们推荐你从tutorial 例子开始,因为XML 非常小,也很容易看懂。更重要的是,你很

容易就可以把这个XML 文件(和它对应的 入口)添加到你的程序中。只有做基

本集成成功的时候,我们建议你试着添加方法验证和领域对象安全。

3.2. Contacts

Contacts 例子,是一个很高级的例子,它在基本程序安全上附加了领域对象的访问控制列

表,演示了更多强大的功能。

要发布它,先把Spring Security 发布中的war 文件按复制到你的容器的webapps 目录

下。这个war 文件应该叫做(后边的 版本号,很大程度上依赖于你使用的发布版本)。

在启动你的容器之后, 检测一下程序是不是加载了, 访问

localhost:8080/contacts(或是其他你把war 发布后,对应于你web 容器的

URL)。

下一步,点击"Debug"。你将看到需要登录的提示,这页上会有一些测试用的用户名和密

码。随便使用其中的一个通过认证,就会看到结果页面。它应该会包含下面这样的一段成

功信息:

Security Debug Information

Authentication object is of type:

mePasswordAuthenticatio

nToken

Authentication object as a String:

mePasswordAuthenticatio

nToken@1f127853:

Principal: @b07ed00:

Username: rod;

Password: [PROTECTED]; Enabled: true; AccountNonExpired: true;

credentialsNonExpired: true; AccountNonLocked: true;

Granted Authorities: ROLE_SUPERVISOR, ROLE_USER;

Password: [PROTECTED]; Authenticated: true;

Details:

henticationDetails@0:

RemoteIpAddress: 127.0.0.1; SessionId: 8fkp8t83ohar;

Granted Authorities: ROLE_SUPERVISOR, ROLE_USER

Authentication object holds the following granted authorities:

ROLE_SUPERVISOR (getAuthority(): ROLE_SUPERVISOR)

ROLE_USER (getAuthority(): ROLE_USER)

Success! Your web filters appear to be properly configured!

一旦你成功的看到了上面的信息,就可以返回例子程序的主页,点击"Manage"了。然后

你就可以尝试这个程序了。注意,只有当前登录的用户对应的联系信息会显示出来,而且

只有ROLE_SUPERVISOR 权限的用户可以授权删除他们的联系信息。在这场景后面,

MethodSecurityInterceptor 保护着业务对象。

陈程序允许你修改访问控制列表,分配不同的联系方式。确保自己好好试用过,看看程序

里的上下文XML 文件,搞明白它是如何运行的。

3.3. LDAP例子

LDAP 例子程序提供了一个基础配置,同时使用命名空间配置和使用传统方式bean 的配置

方式,这两种配置方式都写在application context 文件里。这意味着,在这个程序里,

其实是配置了两个定义验证提供器。

3.4. CAS例子

CAS 示例要求你同时运行CAS 服务器和CAS 客户端。它没有包含在发布包里,你应该使

用简介中的介绍来获得源代码。你可以在sample/cas 目录下找到对应的文件。这里还有

一个 文件,解释如何从源代码树中直接运行服务器和客户端,提供完全的SSL

支持。你应该下载CAS 服务器web 程序(一个war 文件)从CAS 网站,把它放到 samples/cas/server 目录下。

3.5. Pre-Authentication例子

这个例子演示了如何从pre-authentication

框架绑定bean,从J2EE 容器中获得有用的登录

信息。用户名和角色是由容器设置的。

代码在samples/preauth 目录下。

Spring Security 社区

4.1. 任务跟踪

Spring Security 使用JIRA 管理bug 报告和扩充请求。如果你发现一个bug,请使用JIRA

提交一个报告。不要把它放到支持论坛上,邮件列表里,或者直接发邮件给项目开发者。这

样做法是特设的,我们更愿意使用更正式的方式管理bug。

如果有可能,最好为你的任务报告提供一个Junit 单元测试,演示每一种不正确的行为。或

者,更好的是,提供一个不定来修正这个问题。一般来说,扩充也也可以提交到任务跟踪

系统里,虽然我们只接受提供了对应的单元测试的扩充请求。因为保证项目的测试覆盖率

是非常必要的,它需要适当的进行维护。

你可以访问任务跟踪的网址/browse/SEC。

4.2. 成为参与者

我们欢迎你加入到Spring Security 项目中来。这里有很多贡献的方式,包括在论坛上阅

读别人的帖子发表回复,写新代码,提升旧代码,帮助写文档,开发例子或指南,或简单的

提供建议。

4.3. 更多信息

欢迎大家为Spring Security 提出问题或评论。你可以使用Spring 社区论坛网址

同框架的其他用户讨论Spring Security。记得使用JIRA

提交bug,这部分在上面提到过了。

技术概述

5.1. 运行环境

Spring Security 3.0需要运行在Java 5.0或更高版本环境上。因为Spring Security 的

目标是自己容器内管理, 所以不需要为你的Java 运行环境进行什么特别的配置。特别是,

不需要特别配置一个Java Authentication and Authorization Service (JAAS)政策文

件, 也不需要把Spring Security 放到server 的classLoader 下。

相同的,如果你使用了一个EJB 容器或者是Servlet 容器,都不需要把任何特定的配置文

件放到什么地方,也不需要把Spring Security 放到server 的classloader 下。所有必

须的文件都可以配置在你的应用中。

这些设计确保了发布时的最大轻便性, 你可以简单把你的目标文件(JAR 或WAR 或EAR)

从一个系统复制到另一个系统, 它会立即正常工作。

5.2. 核心组件

在Spring Security 3.0中,spring-security-corejar 的内容已经被缩减到最小。它不

再包含任何与web 应用安全, LDAP 或命名空间相关的代码。我们会看一下这里, 看看

你在核心模块中找到的Java 类型。它们展示了框架的构建基础, 所以如果你需要超越简

单的命名空间配置, 那么理解它们就是很重要的,即便你不需要直接操作他们。

5.2.1. SecurityContextHolder, SecurityContext 和Authentication对象

最基础的对象就是SecurityContextHolder。我们把当前应用程序的当前安全环境的细

节存储到它里边了, 它也包含了应用当前使用的主体细节。默认情况下,

SecurityContextHolder 使用ThreadLocal 存储这些信息, 这意味着,安全环境在同一

个线程执行的方法一直是有效的, 即使这个安全环境没有作为一个方法参数传递到那些方 法里。这种情况下使用ThreadLocal 是非常安全的, 只要记得在处理完当前主体的请求

以后,把这个线程清除就行了。当然,Spring Security 自动帮你管理这一切了, 你就不

用担心什么了。

有些程序并不适合使用ThreadLocal, 因为它们处理线程的特殊方法。比如,swing 客户

端也许希望JVM 里的所有线程都使用同一个安全环境。SecurityContextHolder 可以使

用一个策略进行配置在启动时,指定你想让上下文怎样被保存。对于一个单独的应用系统,

你可以使用_GLOBAL 策略。其他程序可能想让一个线

程创建的线程也使用相同的安全主体。这时可以使用

_INHERITABLETHREADLOCAL 。想要修改默认的

_THREADLOCAL 模式,可以使用两种方法。第一个是

设置系统属性。另一个是调用SecurityContextHolder 的静态方法。大多数程序不需要

修改默认值, 但是如果你需要做修改,先看一下SecurityContextHolder 的JavaDoc

中的详细信息。

5.2.1.1. 获得当前用户的信息

我们把安全主体和系统交互的信息都保存在SecurityContextHolder 中了。Spring

Security 使用一个Authentication 对应来表现这些信息。虽然你通常不需要自己创建一

个Authentication 对象, 但是常见的情况是,用户查询Authentication 对象。你可以

使用下面的代码- 在你程序中的任何位置- 来获得已认证用户的名字, 比如:

Object principal =

text().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {

String username = ((UserDetails)principal).getUsername();

} else {

String username = ng();

}

调用getContext()返回的对象是一个SecurityContext 接口的实例。这个对象是保存在

thread-local 中的。如我们下面看到的,大多数Spring Security 的验证机制都返回一个

UserDetails 的实例作为主体。

5.2.2. UserDetailsService

从上面的代码片段中还可以看出另一件事,就是你可以从Authentication 对象中获得安全

主体。这个安全主体就是一个对象。大多数情况下,可以强制转换成UserDetails 对象。

UserDetails 是一个Spring Security 的核心接口。它代表一个主体,是扩展的,而且是

为特定程序服务的。想一下UserDetails 章节,在你自己的用户数据库和如何把Spring

Security 需要的数据放到SecurityContextHolder 里。为了让你自己的用户数据库起作

用,我们常常把UserDetails 转换成你系统提供的类,这样你就可以直接调用业务相关的

方法了(比如getEmail(), getEmployeeNumber()等等)。

现在,你可能想知道,我应该什么时候提供这个UserDetails 对象呢? 我怎么做呢? 我

想你说这个东西是声明式的,我不需要写任何代码,怎么办? 简单的回答是,这里有一个

特殊的接口,叫UserDetailsService。这个接口里的唯一一个方法,接收String 类型的

用户名参数,返回UserDetails:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

这是获得从Spring Security 中获得用户信息的最常用方法,你会看到它在框架中一直被

用到。当需要获得一个用户的信息的时候。

当成功通过验证时, UserDetails 会被用来建立Authentication 对象, 保存在 SecurityContextHolder 里。( 更多的信息可以参考下面的

#tech-intro-authentication-mgr)。好消息是我们提供了好几个UserDetailsService

实现,其中一个使用了内存中的map(InMemoryDaoImpl) 另一个而是用了JDBC

(JdbcDaoImpl)。虽然,大多数用户倾向于写自己的,使用这些实现常常放到已有的数据

访问对象(DAO)上,表示它们的雇员,客户或其他企业应用中的用户。记住这个优势,

无论你用什么UserDetailsService 返回的数据都可以通过SecurityContextHolder 获

得,就像上面的代码片段讲的一样。

5.2.3. GrantedAuthority

除了主体,另一个Authentication 提供的重要方法是getAuthorities()。这个方法提供

了GrantedAuthority 对象数组。毫无疑问,GrantedAuthority 是赋予到主体的权限。

这些权限通常使用角色表示, 比如ROLE_ADMINISTRATOR 或

ROLE_HR_SUPERVISOR。这些角色会在后面,对web 验证,方法验证和领域对象验证

进行配置。Spring Security 的其他部分用来拦截这些权限,期望他们被表现出现。

GrantedAuthority 对象通常使用UserDetailsService 读取的。

通常情况下,GrantedAuthority 对象是应用程序范围下的授权。它们不会特意分配给一

个特定的领域对象。因此,你不能设置一个GrantedAuthority,让它有权限展示编号54

的Employee 对象,因为如果有成千上网的这种授权,你会很快用光内存(或者,至少,

导致程序花费大量时间去验证一个用户)。当然,Spring Security 被明确设计成处理常见

的需求,但是你最好别因为这个目的使用项目领域模型安全功能。

5.2.4. 小结

简单回顾一下,Spring Security 主要是由一下几部分组成的:

SecurityContextHolder,提供几种访问SecurityContext 的方式。

SecurityContext,保存Authentication 信息,和请求对应的安全信息。

HttpSessionContextIntegrationFilter,为了在不同请求使用,把SecurityContext 保

存到HttpSession 里。

Authentication,展示Spring Security 特定的主体。

GrantedAuthority,反应,在应用程序范围你,赋予主体的权限。

UserDetails,通过你的应用DAO,提供必要的信息,构建Authentication 对象。

UserDetailsService,创建一个UserDetails,传递一个String 类型的用户名(或者证

书ID 或其他)。

现在,你应该对这种重复使用的组件有一些了解了。让我们贴近看一下验证的过程。

5.3. 验证

Spring Security 可以用在多种不同的验证环境下。我们推荐人们使用Spring Security

进行验证,而不是与现存的容器管理验证相结合, 然而这种方式也是被支持的- 作为与你

自己的验证系统相整合的一种方式。

5.3.1. 什么是Spring Security的验证呢?

让我们考虑一种标准的验证场景,每个人都很熟悉的那种。

一个用户想使用一个账号和密码进行登陆。

系统(成功的)验证了密码对于这个用户名是正确的。

这个用户对应的信息呗获取(他们的角色列表以及等等)。

为用户建立一个安全环境。

用户会执行一些操作,这些都是潜在被权限控制机制所保护的,通过对操作的授权, 使用

当前的安全环境信息。

前三个项目执行了验证过程,所以我们可以看一下Spring Security 的作用。 用户名和密码被获得,并进行比对, 在一个UsernamePasswordAuthenticationToken

的实例中(它是Authentication 接口的一个实例, 我们在之前已经见过了)。

这个标志被发送给一个AuthenticationManager 的实例进行校验。

AuthenticationManager 返回一个完全的Authentication 实例, 在成功校验后。

安全环境被建立, 通过调用

text().setAuthentication(...), 传递到返回的验证对

象中。

从这一点开始,用户已经通过校验了。让我们看一些代码作为例子。

import tication.*;

import .*;

import dAuthorityImpl;

import tyContextHolder;

public class AuthenticationExample {

private static AuthenticationManager am = new SampleAuthenticationManager();

public static void main(String[] args) throws Exception {

BufferedReader in = new BufferedReader(new InputStreamReader());

while(true) {

n("Please enter your username:");

String name = ne();

n("Please enter your password:");

String password = ne();

try {

Authentication request = new UsernamePasswordAuthenticationToken(name,

password);

Authentication result = ticate(request);

text().setAuthentication(result);

break;

} catch(AuthenticationException e) {

n("Authentication failed: " + sage());

}

}

n("Successfully authenticated. Security context contains: " +

text().getAuthentication());

}

}

class SampleAuthenticationManager implements AuthenticationManager {

static final List AUTHORITIES = new

ArrayList();

static {

(new GrantedAuthorityImpl("ROLE_USER"));

}

public Authentication authenticate(Authentication auth) throws

AuthenticationException {

if (e().equals(dentials())) { return new UsernamePasswordAuthenticationToken(e(),

dentials(), AUTHORITIES);

}

throw new BadCredentialsException("Bad Credentials");

}

}

这里我们写了一些程序,询问用户输入一个用户名和密码, 然后执行上面的顺序。我们实

现的AuthenticationManager 会验证所有用户名和密码一样的用户。它为每个永固分配

一个单独的角色。上面输出的信息将会像这样:

Please enter your username:

bob

Please enter your password:

password

Authentication failed: Bad Credentials

Please enter your username:

bob

Please enter your password:

bob

Successfully authenticated. Security context contains:

mePasswordAuthenticationToken@441d0230:

Principal: bob; Password: [PROTECTED];

Authenticated: true; Details: null;

Granted Authorities: ROLE_USER

注意,你没必要写这些代码。这些处理都是发生在内部的, 比如在一个web 验证过滤器中。

我们只是使用了这些代码, 来演示真实情况下的问题,Spring Security 提供了一个简单

的答案。一个用户被验证,当SecurityContextHolder 包含了完整的Authentiation

对象。

5.3.2. 直接设置SecurityContextHolder的内容

实际上, Spring Security 不知道你怎么把Authentication 对象放到

SecurityContextHolder 里。唯一关键的要求是SecurityContextHolder 包含了一个

Authentication 表示了一个主体, 在AbstractSecurityInterceptor 之前(我们以后会

看到更多) 需要验证一个用户操作。

你可以(许多人都这样做)写自己的过滤器,或MVC 控制器来提供验证系统,不基于Spring

Security。比如你可能使用容器管理的验证,让当前用户有效在TheadLocal 或JNDI

位置。或者你可能为一个公司工作,你没有什么控制力。这种情况下,使用Spring Security

很简单, 还是提供验证功能。你需要做的是些一个过滤器(或什么设备) 从一个地方读

取第三方用户信息, 构建一个Spring Security 特定的Authentication 对象, 把它放到

SecurityContextHolder 里。

如果你想知道AuthenticationManager 是如何实现的, 我们会在***看到。

5.4. 在web应用中验证

现在让我们研究一下情景,当我们在web 应用中使用Spring Security (不使用

安全)。一个用户如何验证,安全环境如何创建? 考虑一个典型的web 应用的验证过程:

你访问主页,点击链接。

一个请求发送给服务器,服务器决定你是否在请求一个被保护的资源。

如果你还没有授权,服务器发回一个相应,提示你必须登录。响应会是一个HTTP 响应代

码, 或重定向到特定的web 页面。

基于验证机制,你的浏览器会重定向到特殊的web 页面, 所以你可以填写表单,或者浏览

器会验证你的身份(通过一个BASIC 验证对话框,一个cookie,一个X.509 验证,等

等)。

浏览器会发送回一个响应到服务器。这会是一个HTTP POST 包含你填写的表单中的内容,

或者一个HTTP 头部包含你的验证细节。

下一步,服务器决定,当前证书是否有效。如果它们有效,下一步会执行。如果它们无效,

通常你的浏览器会被询问再试一次(所以initial 返回上两步)。

你的原始请求会引发验证过程。希望你验证了获得了授予的权限来访问被保护的资源。如

果你完允许访问,请求会成功。否则,你会收到一个HTTP 错误代码403,意思是“拒绝访

问”。

Spring Security 拥有不同的泪,对应很多常用的上面所说的步骤。主要的部分(使用的

次序)是ExceptionTranslationFilter, 一个AuthenticationEntryPoint 和一个“验证

机制”, 对应着AuthenticationManager 的调用我们在上一章见过。

5.4.1. ExceptionTranslationFilter

ExceptionTranslationFilter 是一个Spring Security 过滤器负责检测任何一个Spring

Security 抛出的异常。这些异常会被AbstractSecurityInterceptor 抛出, 这是一个验

证服务的主要提供器。我们会在下一章讨论AbstractSecurityInterceptor, 而现在我们

需要知道它产生Java 异常, 不知道HTTP,也不知道如何验证一个主体。对应的

ExceptionTranslationFilter 负责这个服务, 特别负责返回错误代码403(如果主体已经

通过授权,但是权限不足- 像上面的第七步),或者启动一个AuthenticationEntryPoint

(如果主体还没有授权,因此我们会进入上面的第三步)。

5.4.2. AuthenticationEntryPoint

AuthenticationEntryPoint 负责上面的步骤三。像你想的那样,每个web 应用会有一个

默认的验证策略(好,这可能像其他东西一样在Spring Security 里配置, 但现在让我

们保持简单)。每个主要的验证系统会有他们自己的AuthenticationEntryPoint 实现, 典

型的执行一个动作,描述在第三步。

5.4.3. 验证机制

一旦你的浏览器提交了你的验证证书(像HTTP 表单POST 或者HTTP 头) 这些需要一些

服务器的东西保存这些权限信息。但是现在我们进入上面的第六步。在Spring Security

中我们有一个特定的名称,为了收集验证信息的操作。从一个用户代码中(通常是浏览器),

引用它作为一个“验证机制”。例子是基于表单的登录和BASIC 验证。一旦验证细节被从

用户代理处收集到, 一个Authentication 请求对象就会被建立, 然后放到

AuthenticationManager。

在验证机制获得完全的Authentication 后,它会认为请求合法, 把Authentication 放

到SecurityContextHolder 里,然后让原始请求重试(上面第七步)。如果,其他可能,

AuthenticationManager 拒绝了请求, 请求机制会让用户代理重试(上面第二步)。

5.4.4. 在请求之间保存SecurityContext。

依照应用类型,这里需要一个策略, 在用户操作之间保存安全环境。在一个典型的web

应用中, 一个用户日志,一次或顺序被它的session id。服务器缓存主体信息在session 整个过程中, 在Spring Security 中, 保存SecurityContext , 从请求失败

SecurityContextPersistenceFilter,默认保存到HttpSession 里的一个属性,在HTTP

请求之间。它重新保存环境到SecurityContextHolder,为每个请求。然后为每个请求

清空SecurityContextHolder。你不应该为了安全目的,直接操作HttpSession。这里

有简单的方法实现- 一直使用SecurityContextHolder 代替。

很多其他类型的应用(比如,一个无状态的REST web 服务)不会使用HTTP 会话,会在

每次请求时,重新验证。然而,这对SecurityContextPersistenceFilter 也很重要,确保

包含在SecurityContextHolder 中,在每次请求后清空。

Note

在一个单一会话接收同步请求的应用里,相同的SecurityContext 实例会在线程之间共

享。即使使用一个ThreadLocal,也是使用了来自HttpSession 的相同实例。如果你希望

暂时改变一个线程的上下文就会造成影响。如果你只是使用

text().setAuthentication(anAuthentication), 然后

Authentication 对象会反应到所有并发线程,共享相同的SecurityContext 实例。你可

以自定义SecurityContextPersistenceFilter 的行为来创建完全新的一个线程避免影响

其他的。还可以选择的是,你可以创建一个新实例,只在你暂时修改上下文的时候。这个

方法EmptyContext()总会返回一个新的上下文实例。

5.5. Spring Security中的访问控制(验证)

主要接口,负责访问控制的决定,在Spring Security 中是AccessDecisionMananger。

它有一个decide 方法,可以获得一个Authentication 对象。展示主体的请求权限, 一

个“secure object”(看下边)和一个安全元数据属性队列, 为对象提供了(比如一个角

色列表,为访问被授予的请求)。

5.5.1. 安全和AOP建议

如果你熟悉AOP 的话,就会知道有几种不同的拦截方式:之前,之后,抛异常和环绕。其

中环绕是非常有用的,因为advisor 可以决定是否执行这个方法,是否修改返回的结果,

是否抛出异常。Spring Security 为方法调用提供了一个环绕advice,就像web 请求一

样。我们使用Spring 的标准AOP 支持制作了一个处理方法调用的环绕advice,我们使

用标准filter 建立了对web 请求的环绕advice。

对那些不熟悉AOP 的人,需要理解的关键问题是Spring Security 可以帮助你保护方法的

调用,就像保护web 请求一样。大多数人对保护服务层里的安全方法非常按兴趣。这是

因为在目前这一代J2EE 程序里,服务器放了更多业务相关的逻辑(需要澄清,作者不建议

这种设计方法,作为替代的,而是应该使用DTO,集会,门面和透明持久模式压缩领域对

象,但是使用贫血领域对象是当前的主流思路,所以我们还是会在这里讨论它)。如果你只

是需要保护服务层的方法调用,Spring 标准AOP 平台(一般被称作AOP 联盟)就够了。

如果你想直接保护领域对象,你会发现AspectJ 非常值得考虑。

可以选择使用AspectJ 还是Spring AOP 处理方法验证,或者你可以选择使用filter 处理

web 请求验证。你可以不选,选择其中一个,选择两个,或者三个都选。主流的应用是

处理一些web 请求验证,再结合一些在服务层里的Spring AOP 方法调用验证。

5.5.2. 安全对象和AbstractSecurityInterceptor

所以,什么是“secure object”? Spring Security 使用应用任何对象,可以被安全控制

(比如一个验证决定)提供到它上面。最常见的例子是方法调用和web 请求。

Spring Security 支持的每个安全对象类型都有它自己的类型, 它们都是

AbstractSecurityInterceptor 的子类。很重要的是,如果主体是已经通过了验证,在

AbstractSecurityInterceptor 被调用的时候,SecurityContextHolder 将会包含一个有 效的Authentication。

AbstractSecurityInterceptor 提供了一套一致的工作流程,来处理对安全对象的请求,

通常是:

查找当前请求里分配的"配置属性"。

把安全对象,当前的Authentication 和配置属性,提交给AccessDecisionManager,来

进行以此认证决定。

有可能在调用的过程中,对Authentication 进行修改。

允许安全对象进行处理(假设访问被允许了)。

在调用返回的时候执行配置的AfterInvocationManager。

5.5.2.1. 配置属性是什么?

一个"配置属性"可以看做是一个字符串,它对于AbstractSecurityInterceptor 使用的类

是有特殊含义的。它们通过框架中的ConfigAttribute 接口表现。它们可能是简单的角

色名称或拥有更复杂的含义,这就与AccessDecisionManager 实现的先进程度有关了。

AbstractSecurityInterceptor 和配置在一起的SecurityMetadataSource 用来为一个安

全对象搜索属性。通常这个属性对用户是不可见的。配置属性将以注解的方式设置在受保

护方法上,或者作为受保护URL 的访问属性。比如,当我们查看一些像

pattern='/secure/**' access='ROLE_A,ROLE_B'/> 在命名空间介绍里,这就是在说

这些配置属性ROLE_A 和ROLE_B 应用到web 请求匹配到指定的模式中。实际上,使

用默认的AccessDecisionManager 配置,这意味着任何人拥有GrantedAuthority 匹

配任何这些两个属性中的一个会被允许访问。严格意义上,他们只是树形,解释是基于

AccessDecisionManager 实现的。前缀ROLE_的使用标记了这些属性是角色,会被

Spring Security 的RoleVoter 处理。它只与基于角色的AccessDecisionManager 有关。

我们会在验证章节看到AccessDecisionManager 是如何实现的。

5.5.2.2. RunAsManager

假设AccessDecisionManager 决定允许执行这个请求,AbstractSecurityInterceptor

会正常执行这个请求。话虽如此,罕见情况下,用户可能需要把SecurityContext 的

Authentication 换成另一个Authentication,通过访问RunAsManager。这也许在,

有原因,不常见的情况下有用,比如,服务层方法需要调用远程系统,表现不同的身份。因

为Spring Security 自动传播安全身份,从一个服务器到另一个(假设你使用了配置好的

RMI 或者HttpInvoker 远程调用协议客户端),就可以用到它了。

5.5.2.3. AfterInvocationManager

按照下面安全对象执行和返回的方式-可能意味着完全的方法调用或过滤器链的执行。这种

状态下AbstractSecurityInterceptor 对有可能修改返回对象感兴趣。你可能想让它发

生, 因为验证决定不能“ 关于如何在” 一个安全对象调用。高可插拔性,

AbstractSecurityInterceptor 通过控制AfterInvocationManager,实际上在需要的时

候,修改对象。这里类实际上可能替换对象,或者抛出异常,或者什么也不做。

AbstractSecurityInterceptor 和相关对象展示在Figure 5.1, “关键"secure object"模型”中。

Figure 5.1. 关键"secure object"模型

5.5.2.4. 扩展安全对象模型

只有开发者才会关心使用全心的方法,进行拦截和验证请求,将直接使用安全方法。比如,

可能新建一个安全方法,控制对消息系统的权限。安全需要的任何事情,也可以提供一种

拦截的方法(好像AOP 的环绕advice 语法那样)有可能在安全对象里处理。这样说的话,

大多数Spring 应用简单拥有三种当前支持的安全类型(AOP 联盟的MethodInvocation,

AspectJ JoinPoint 和web 请求FilterInterceptor)完全透明的。 5.6. 国际化

Spring Security 支持异常信息的国际化,最终用户希望看到这些信息。如果你的应用被

设计成给讲英语的用户的,你不需要做任何事情, 因为默认情况下Spring Security 的信

息都是引用的。如果你需要支持其他语言。你所需要做的事情都包含在这一章节中的。

所有的异常信息都支持国际化,包括验证失败和访问被拒绝的相关信息(授权失败)。应该

被开发者和系统开发者关注(包括不正确的属性,接口契约,使用非法构造方法, 开始时

间校验,调试级日志等等)的异常和日志没有被国际化,而是使用英语硬编码到Spring

Security 的代码中。

从 中,你可以找到ty 包下,

包含了一些ties 文件,这应该引用到你的ApplicationContext 中,

因为Spring Security 的类都实现了spring 的MessageSourceAware 接口, 期待的

信息处理器会在application context 启动的时候注入进来。通常所有你需要做的就是在

你的application context 中注册一个bean 来引用这些信息。下面是一个例子:

class="ableResourceBundleMessageSource">

ties 是按照标准资源束命名的, 里边包括了Spring security 所使用

的默认语言的信息。默认的文件是英文的。如果你没有注册一个信息源,Spring Security

也会正常工作, 并使用硬编码的英文版本的信息。

如果你想自定义ties 文件,或者支持其他语言, 你需要复制这个文件,

正确的把它重新命名,再把它注册到bean 定义中。这个文件中并没有太多的信息。所以

国际化应该不是很繁重的工作。如果你国际化了这个文件,请考虑一下把你的工作和社区

分享,通过记录一个JIRA 任务把你翻译的ties 版本作为一个附件发

送上去。

围绕国际化的讨论, spring 的ThreadLocal 是

ContextHolder 。你应该把

LocaleContextHolder 设置成为每个用户对应的Locale。Spring Security 会尝试从信

息源中寻找信息,根据ThreadLocal 中获得的Locale。请参考Spring 的文档,来获得

更多使用LocaleContextHolder 的信息。

Chapter 6. 核心服务

现在,我们对Spring Security 的架构和核心类有了高层次的了解了, 让我们近距离看看

这些核心接口和他们的实现, 特别是AuthenticationManager, UserDetailsService

和AccessDecisionManager。它们的信息都在这个文档的后面,所以重要的是我们要知

道如何配置,如何操作。

6.1. The AuthenticationManager, ProviderManager 和

AuthenticationProviders

AuthenticationManager 只是一个接口,所以呢,它的实现可以让我们随便选择,但是

实际上它是如何工作的呢? 如果我们需要检查多个授权数据库或者将不同的授权服务结合

起来,比如数据库和lDAP 服务器?

在Spring Security 中的默认实现是ProviderManager 不只是处理授权请求自己,它委

派了一系列配置好的AuthenticationProvider, 每个按照顺序查看它是否可以执行验证。

每个供应器会跑出一个异常,或者返回一个完整的Authentication 对象。要记得我们的 好朋友,UserDetails 和、UserDetailsService。如果不记得了,返回到前面的章节刷

新一下你的记忆。最常用的方式是验证一个授权请求读取对应的UserDetails,并检查用

户录入的密码。这是通过DaoAuthenticationProvider 实现的(见下面), 加载的

UserDetails 对象- 特别是包含的GrantedAuthority - 会在建立Authentication 时使

用,这回返回一个成功验证,保存到SecurityContext 中。

如果你使用了命名空间,一个ProviderMananger 的实例会被创建并在内部进行维护,

你可以使用命名空间验证元素, 或给一个bean 添加一个

元素。(参考命名空间章节)。在这里,你不应该

在你的application context 中声明一个ProviderManager bean。然而,如果你没有使

用命名空间,你应该像下面这样进行声明:

class="erManager">

在上面的例子中,我们有三个供应器。它们按照顺序显示(使用List 实现), 每个供应器

能够尝试进行授权, 或通过返回null 跳过授权。如果所有的实现都返回null 。

ProviderManager 会跑出一个ProviderNotFoundException 异常。如果你对链状供应

器感兴趣,请参考ProviderManager 的javadoc。

验证机制,比如表单登陆处理过滤器被注入一个ProviderManager, 会被用来处理它们

的认证请求。你需要的供应器有时需要被认证机制内部改变的,当在其他时候, 他们会以

来一个特定的认证机制, 比如DaoAuthenticationProvider 和

LdapAuthenticationProvider 可疑对应任何一个提交简单username/password 的认

证请求,所以可以和基于表单登陆和HTTP 基本认证一起工作。其他时候,一些认证机制创

建了一个认证请求对象,只可以被单个类型的AuthenticationProvider 拦截。一个例子

就是JA-SIG CAS , 它使用一个提醒的服务票据, 所以只可以被

CasAuthenticationProvider 认证。你不需要很了解这些, 因为如果你忘记了注册合适的

供应器,你会得到一个ProviderNotFoundException 当这个验证尝试起作用的时候。

6.1.1. DaoAuthenticationProvider

spring security 中最简单的AuthenticationProvider 实现是

DaoAuthenticationProvider , 这也是框架中最早支持的功能之一。它是

UserDetailsService 的杠杆( 作为DAO ), 为了获得username, password 和

GrantedAuthority 。它认证用户, 通过简单比较密码, 在

UsernamePasswordAuthenticationToken 中,和UserDetailsService 中加载的信息。

配置供应器十分简单:

class="henticationProvider">

PasswordEncoder 和SaltSource 都是可选的,一个PasswordEncoder 提供了编码和

解码密码, 在UserDetails 对象中,被返回自配置好的UserDetailsService。一个

SaltSource 可以让密码使用"盐值"生成,这可以提高授权仓库中密码的安全性。更多的细

节会在下面进行讨论。

6.2. UserDetailsService实现

像在前面提及的一样,大多数认证供应器都是用了UserDetails 和UserDetailsService

接口。调用UserDetailsService 中的单独的方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

返回的UserDetails 是一个接口,它提供了获得保证非空的认证信息,比如用户名,密码,

授予的权限和用户账号是可用还是禁用。大多数认证供应器会使用UserDetailsService,

即使username 和password 没有实际用在这个认证决策中。它们可以使用返回的

UserDetails 对象,获得它的GrantedAuthority 信息,因为一些其他系统(比如LDAP

或者X.509或CAS 等等) 了解真实验证证书的作用。

这里的UserDetailsService 也很简单实现, 它应该为用户简单的获得认证信息,使用它

们选择的持久化策略。这样说,Spring Security 包含了很多有用的基本实现,下面我们

会看到。

6.2.1. 内存认证

创建一个自定义的UserDetailsService 的实现是很容易的, 可以从选择的持久化引擎中

获得信息,但是许多应用没有那么复杂。尤其是如果你建立一个原型应用或只是开始集成

Spring Security 的时候, 当我们不是真的需要耗费时间配置数据库或者写

UserDetailsService 实现。为了这些情况, 一个简单的选择是使用安全命名空间中的

user-service 元素:

/>

也支持使用外部的属性文件:

属性文件需要包含下面格式的内容

username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]

比如

jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled

bob=bobspassword,ROLE_USER,enabled

6.2.2. JdbcDaoImpl

Spring Security 也包含了一个UserDetailsService, 它包含从一个JDBC 数据源中获

得认证信息。内部使用了Spring JDBC,所以它避免了负责的功能完全的对象关系映射

(ORM)只用来保存用户细节。如果你的应用使用了一个ORM 工具, 你应该写一个自己

的UserDetailsService 重用你已经创建了的映射文件。返回到JdbcDaoImpl, 一个配

置的例子如下所示:

class="oImpl">

你可以使用不同的关系数据库管理系统,通过修改上面的DriverManagerDataSource。

你也可以使用通过JNDI 获得的全局数据源, 使用其他的Spring 配置。

6.2.2.1. 权限分组

默认情况下,JdbcDaoImpl 会假设用户的权限都保存在authorities 表中。(参考数据库

结构附录). 还有一种选择是把权限分组,然后让用户加入这些用户组。一些人更喜欢使用

这种方法来管理用户的权限。参考JdbcDaoImpl 的Javadoc 以获得更多的信息,了

ijeruhe 启用权限分组。用户组使用的数据库结构也包含在附录中。

6.3. 密码加密

Spring Security 的PasswordEncoder 接口用来支持对密码通过一些方式进行加密,并

保存到媒介中。这通常意味着密码被“散列加密”,使用一个加密算法,比如MD5或者SHA。

6.3.1. 什么是散列加密?

密码加密不是Spring Security 唯一的,但这对一个不了解这个概念的用户来说还是一个

很容易搞混的来源。一个散列(或摘要)算法是一个单向方法提供了一小段固定长度的输

出数据(散列)从一些输入数据中,比如一个密码。作为一个例子, 字符串“password”

的MD5散列(16进制)是

5f4dcc3b5aa765d61d8327deb882cf99

散列是“单向的” 在这种情况下,很难(基本上不可能)根据给出的散列值获得原始输入,

或是找出任何可能的输入将生成散列值。这个特点让散列值对权限方面很有用。它们可以

保存在你的用户数据库中作为原始明文密码的替换,假设这些值被泄露了也无法立即盗取

登录的密码。注意这也意味着你没有办法把编码后的密码还原。

6.3.2. 为散列加点儿盐

使用密码加密的一个潜在的问题是,因为散列是单向的,如果输入是一个常用的单词的话找

到输入值就相对容易很多了。比如, 如果我们查找散列值

5f4dcc3b5aa765d61d8327deb882cf99 通过google 。我们会很快找到原始词是

“password”。简单的方法,一个攻击者可以建立一个散列值的字典把标准单词排列,使

用它来查找原始密码。一个方法来帮助防止这种问题是使用高强度的密码策略来防止使用

常用单词。另一个是在计算散列时使用“盐值”。这是一个对每个用户都知道的附加字符串,

它会结合到密码中,在计算散列之前。注意这个数值应该是尽可能的随机数,但是实际中

任何盐值通常都是不可取的。Spring Security 有一个SaltSource 接口, 可以被验证供

应器用来为特定的用户生成一个盐值。使用盐值,意味着攻击者必须创建单独的散列字典,

为不同的盐值, 这让攻击更难了(但不是不可能)。

6.3.3. 散列和认证

当一个认证供应器(比如Spring Security 的DaoAuthenticationProvider) 需要检验

密码,在提交认证请求中,与用户知道的数据进行比较,保存的密码通过一些方式进行了加 密,然后提交的数据必须也使用相同的算法进行加密。这要求你去检查兼容性,因为Spring

Security 对持久化的没有任何控制。如果你在Spring Security 的认证配置中添加了密码

散列功能, 你的数据库包含原始明文密码,那么认证就绝对不可能成功。如果你在数据库

中使用MD5 对密码加密, 比如, 你的应用配置为使用Spring Security 的

Md5PasswordEncoder,这也有其他可能的问题。数据库可能用Base 64进行了加密,

比如当加密器使用16进制的字符串(默认)

[5]。可以选择,你的数据库可能使用了大写,

当编码器输出的是小写。确定你编写了一个测试来检测从你的密码编码器的输出,使用一

个知道的密码和盐值结合检测它是否与数据库值匹配,在更深入之前,尝试通过你的系统

认证。要想获得更多信息, 在默认的方法从结合盐值和密码, 查看

BasePasswordEncoder 的Javadoc。如果你希望直接通过java 生成密码, 为你的用户

数据库保存,然后你可以使用PasswordEncoder 的encodePassword 方法。

[5]你可以配置编码器使用Base 64替换16进制,通过设置encodeHashAsBase64为

true。参考MessageDigestPasswordEncoder 的Javadoc 和它的超类获得更多信息。

Part III. web 应用安全

大多数Spring Security 的用户使用框架是基于HTTP 和Servlet API 的。在这一章和以

后的章节中,我们会看一下如何使用Spring Security 提供验证和权限控制特性, 用于应

用的web 层。我们会介绍命名空间外观的背后,看看哪些类和接口实际上用于提供web

层的安全。在一些情况下,必须使用以前的bean 配置来提供对配置的完全孔子和,所以

我们也会看到如何直接配置这些类,不使用命名空间。

安全过滤器链

Spring Security 的web 架构是完全基于标准的servlet 过滤器的。它没有在内部使用

servlet 或任何其他基于servlet 的框架(比如spring mvc), 所以它没有与任何特定的

web 技术强行关联。它只管处理HttpServletRequest 和HttpServletResponse,不关

心请求时来自浏览器,web 服务客户端,HttpInvoker 还是一个AJAX 应用。

Spring Security 维护了一个过滤器链,每个过滤器拥有特定的功能,过滤器需要服务也会

对应添加和删除。过滤器的次序是非常重要的,它们之间都有依赖关系。如果你已经使用

了命名空间配置,过滤器会自动帮你配置, 你不需要定义任何Spring Bean,但是有时候你

需要完全控制Spring 过滤器链, 因为你使用了命名空间没有提供的特性,或者你需要使

用你自己自定义的类。

7.1. DelegatingFilterProxy

当使用servlet 过滤器时,你很需要在你的 中声明它们, 它们可能被servlet

容器忽略。在Spring Security,过滤器类也是定义在xml 中的spring bean, 因此可以

获得Spring 的依赖注入机制和生命周期接口。spring 的DelegatingFilterProxy 提供了

在 和application context 之间的联系。

当使用DelegatingFilterProxy,你会看到像 文件中的这样内容:

myFilter

tingFilterProxy

myFilter

/*

注意这个过滤器其实是一个DelegatingFilterProxy,这个过滤器里没有实现过滤器的任何

逻辑。DelegatingFilterProxy 做的事情是代理Filter 的方法,从application context

里获得bean。这让bean 可以获得spring web application context 的生命周期支持,

使配置较为轻便。bean 必须实现 接口,它必须和filter-name 里

定义的名称是一样的。查看DelegatingFilterProxy 的javadoc 获得更多信息。

7.2. FilterChainProxy

现在应该清楚了,你可以声明每个Spring Security 过滤器bean,你在application

context 中需要的。把一个DelegatingFilterProxy 入口添加到, 确认它们的

次序是正确的。这是一种繁琐的方式,会让 显得十分杂乱,如果我们配置了太

多过滤器的话。我们最好添加一个单独的入口,在 中,然后在application

context 中处理实体, 管理我们的web 安全bean。这就是FilterChainProxy 所做的事

情。它使用DelegatingFilterProxy (就像上面例子中那样),但是对应的class 是

ChainProxy。过滤器链是在application

context 中声明的。这里有一个例子:

class="ChainProxy">

你可能注意到FilterSecurityInterceptor 声明的不同方式。命名空间元素

filter-chain-map 被用来设置安全过滤器链。它映射一个特定的URL 模式,到过滤器链

中,从bean 名称来定义的filters 元素。它同时支持正则表达式和ant 路径,并且只使用

第一个出现的匹配URI。在运行阶段FilterChainProxy 会定位当前web 请求匹配的第一

个URI 模式,由filters 属性指定的过滤器bean 列表将开始处理请求。过滤器会按照定

义的顺序依次执行,所以你可以对处理特定URL 的过滤器链进行完全的控制。

你可能注意到了,我们在过滤器链里声明了两个SecurityContextPersistenceFilter(ASC

是allowSessionCreation 的简写,是SecurityContextPersistenceFilter 的一个属性)。

因为web 服务从来不会在请求里带上jsessionid , 为每个用户代理都创建一个

HttpSession 完全是一种浪费。如果你需要构建一个高等级最高可扩展性的系统,我们推

荐你使用上面的配置方法。对于小一点儿的项目, 使用一个

HttpSessionContextIntegrationFilter(让它的allowSessionCreation 默认为true)

就足够了。

在有关声明周期的问题上, 如果这些方法被FilterChainProxy 自己调用,

FilterChainProxy 会始终根据下一层的Filter 代理init(FilterConfig)和destroy()方法。 这时,FilterChainProxy 会保证初始化和销毁操作只会在Filter 上调用一次, 而不管它在

过滤器链中被声明了多少次)。你控制着所有的抉择,比如这些方法是否被调用或

targetFilterLifecycle 初始化参数DelegatingFilterProxy。默认情况下,这个参数是

false,servlet 容器生命周期调用不会传播到DelegatingFilterProxy。

当我们了解如何使用命名控制配置构建web 安全。我们使用一个

DelegatingFilterProxy,它的名字是“springSecurityFilterChain”。你应该现在可以看

到FilterChainProxy 的名字,它是由命名空间创建的。

7.2.1. 绕过过滤器链

通过命名空间,你可以使用filters = "none",来提供一个过滤器bean 列表。这会朝向

请求模式,使用安全过滤器链整体。注意任何匹配这个模式的路径不会有任何授权或校验的

服务起作用,它们是可以自由访问的。

7.3. 过滤器顺序

定义在 里的过滤器的顺序是非常重要的。不论你实际使用的是哪个过滤器,

的顺序应该像下面这样:

ChannelProcessingFilter,因为它可能需要重定向到其他协议。

ConcurrentSessionFilter,因为它不使用SecurityContextHolder 功能,但是需要更新

SessionRegistry 来从主体中放映正在进行的请求。

SecurityContextPersistenceFilter,这样SecurityContext 可以在web 请求的开始阶

段通过SecurityContextHolder 建立,然后SecurityContext 的任何修改都会在web

请求结束的时候(为下一个web 请求做准备)复制到HttpSession 中。

验证执行机制- UsernamePasswordAuthenticationFilter, CasAuthenticationFilter,

BasicAuthenticationFilter 等等- 这样SecurityContextHolder 可以被修改,并包含

一个合法的Authentication 请求标志。

SecurityContextHolderAwareRequestFilter,如果,你使用它,把一个Spring Security

提醒HttpServletRequestWrapper 安装到你的servlet 容器里。

RememberMeAuthenticationFilter , 这样如果之前的验证执行机制没有更新

SecurityContextHolder , 这个请求提供了一个可以使用的remember-me 服务的

cookie,一个对应的已保存的Authentication 对象会被创建出来。

AnonymousAuthenticationFilter , 这样如果之前的验证执行机制没有更新

SecurityContextHolder,会创建一个匿名Authentication 对象。

ExceptionTranslationFilter,用来捕捉Spring Security 异常,这样,可能返回一个HTTP

错误响应,或者执行一个对应的AuthenticationEntryPoint。

FilterSecurityInterceptor,保护web URI。

7.4. 使用其他过滤器—— 基于框架

如果你在使用SiteMesh,确认Spring Security 过滤器在SiteMesh 过滤器之前调用。这

可以保证SecurityContextHolder 为每个SiteMesh 渲染器及时创建。

Chapter 8. 核心安全过滤器

这儿有几个在web 应用中一直会用到的Spring Security 关键过滤器, 所以我们回来看

看它们,和支持的类和接口。我们不会覆盖所有功能,所以确认参考它们的javadoc,如

果你想要获得完全的信息。

8.1. FilterSecurityInterceptor

我们已经简要了解了FilterSecurityInterceptor,在简要讨论访问控制的时候(见

#tech-intro-access-control),我们也已经在命名空间中使用过它, 元素 的结合在内部对它进行了配置。现在我们会看如何精确的配置它,使用FilterChainProxy,

通过结合ExceptionTranslationFilter 过滤器。一个典型的配置例子如下:

class="SecurityInterceptor">

access="ROLE_SUPERVISOR,ROLE_TELLER"/>

FilterSecurityInterceptor 负责处理HTTP 资源的安全。它需要一个

AuthenticationManager 和AccessDecisionManager 的引用。它也需要不同HTTP

URL 请求的配置属性。引用回#tech-intro-config-attributes

这里可以看到原始信息。

FilterSecurityInterceptor 可是通过两种方式定义配置属性。第一种,向上面演示的,使

命名空间元素。这和用来配置

FilterChainProxy 的一样,但是使用的是子元

素,只使用pattern 和access 属性。逗号用来分隔不同的配置属性,对于每个HTTP URL。

第二个选择是编写你自己的SecurityMetadataSource, 但是这超越了我们的文档的范

围。根据使用的方式, SecurityMetadataSource 负责返回一个包含了所有配置属性的

List,它分配给一个单独的安全HTTP URL。

应该注意的是urityMetadataSource()方法其实需

要一个FilterSecurityMetadataSource 实例。这是一个标记接口, 它是

SecurityMetadataSource 的子类。它只标记了SecurityMetadataSource 需要

FilterInvocation。对于相似感兴趣,我们会继续引用FilterInvocationDefinitionSource

作为一个SecurityMetadataSource,作为区别大多数用户的微笑相关不同。

SecurityMetadataSource 通过命名空间获得配置属性, 为一个特定的

FilterInvocation,通过匹配请求URL,对于配置好的pattern 属性。这个行为和它在命

名空间中配置的一样。默认使用apache ant path 的方式来处理所有表达式,也支持正

则表达式来支持更复杂的请求。这个path-type 属性用来指定模式使用的类型。它不可

能在同一个定义中使用多个表达式语法的复合结构。作为一个例子,上面的配置使用正则表

达式替换了ant path,可以写成下面这样:

class="SecurityInterceptor">

access="ROLE_WE_DONT_HAVE"/>

access="ROLE_SUPERVISOR,ROLE_TELLER"/>

模式总是根据他们定义的顺序进行执行。因此很重要的是,把更确定的模式定义到列表的上

面。这会反映在你上面的例子中,更确定的/secure/super/模式放在,没那么确定的

/secure/模式的上面。如果它们被反转了。/secure/会一直被匹配,/secure/super/就

永远也不会执行。

8.2. ExceptionTranslationFilter

ExceptionTranslationFilter 处在FilterSecurityInterceptor 的上面。它不执行任何真

正的安全控制,但是处理安全监听器抛出的一场, 提供对应的HTTP 响应。

class="ionTranslationFilter">

class="rlAuthenticationEntryPoint">

class="DeniedHandlerImpl">

8.2.1. AuthenticationEntryPoint

AuthenticationEntryPoint 会被调用, 在用户请求一个安全HTTP 资源,但是他们还没

有被认证。一个对应的AuthenticationException 或AccessDeniedException 会被抛

出, 由一个安全监听器在下面的调用栈中,触发入口点的commence 方法。这执行展示

对应的响应给用户, 这样认证可以开始。我们这里使用的是

LoginUrlAuthenticationEntryPoint,它会把请求重定向到另一个不同的URL(一般是

一个登陆页面)。实际的使用使用会依赖你希望使用到的认证机制。

8.2.2. AccessDeniedHandler

如果一个用户已经认证了,他再去访问一个被保护的资源时会怎么样呢? 正常情况下,这

不会发生,因为应用工作流应该限制哪些资源用户可以访问。比如一个HTML 连接到管理

员页面,可能对没有管理权限的用户隐藏。你不能对一个隐藏的链接点击,因为安全的原

因,这总有可能用户直接输入了URL 尝试越过限制。或者他们可能修改了RESTful URL

来改变一些参数的值。你的应用必须保护这些场景, 或者它们会变的不安全。你将使用简

单的web 层安全在你的服务层接口上, 来确认哪些是被允许的。

如果一个AccessDeniedException 被抛出了, 一个用户已经被认证,然后这意味着操作

已经被尝试了,而他们没有足够的权限, 这种情况下,ExceptionTranslationFilter 会调

用第二个策略, AccessDeniedHandler。默认下,一个AccessDeniedHandlerImpl

会被使用,它只发送一个403(拒绝访问) 响应到客户端。可选的,你可以确切配置一个 实例(像上面的例子)然后设置一个错误页URL 它会把请求跳转到错误页。[6]。这可能

是一个简单的“access denied”页面,比如一个JSP,或者它可能是一个更加复杂的处理

器,比如一个MVC 控制器。当然,我们可以实现自己的接口, 使用你自己的实现。

也可能提供一个自定义的AccessDeniedHandler 然后使用命名空间配置你的应用,参考

#nsa-access-denied-handler.

8.3. SecurityContextPersistenceFilter

我们介绍了所有重要的过滤器,在技术概述一章, 所以你可能希望重新阅读以下这些章节。

让我们首先看一下如何使用FilterChainProxy 配置它们。一个基本的配置需要bean 自

class="tyContextPersistenceFilter"/>

像我们之前看到的,这个过滤器有两个主要的任务,它负责保存在不同的HTTP 请求之间

SecurityContext,负责清理在请求完成时SecurityContextHolder。清理上下文中保

存的ThreadLocal 是很基本的,因为它可能是servlet 容器线程池中的一个替换的

thread,这样spring security 对应一个特定用户可能出现冲突。这个线程可能被下一个

阶段使用,执行操作的时候就会使用错误的证书。

8.3.1. SecurityContextRepository

对于Spring Security 3.0,读取和保存安全上下文的任务被委托给一个单独的策略接口:

public interface SecurityContextRepository {

SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);

void saveContext(SecurityContext context, HttpServletRequest request,

HttpServletResponse response);

}

HttpRequestResponseHolder 是一个简单的容器,为了进入的请求和相应对象, 允许使

用封装类替换它们。返回的内容会被发送给过滤器链。

默认的实现是HttpSessionSecurityContextRepository, 它会把安全上下文保存为一

个HttpSession 的属性。[7]. 这个实现中最重要的配置参数是allowSessionCreation 属

性, 默认是true,因此允许类创建会话,如果它需要保存spring context 为一个认证用

户(它不会创建一个除非认证已经执行,security context 的内容发生改变)。如果你不

想要创建会话,你可以把这个属性设置为false:

class="tyContextPersistenceFilter">

class='ssionSecurityContextRepository'>

可选的是,你可以提供一个SecurityContextRepository 接口的null 实现,这就可以防

止安全上下文被保存,即使一个session 已经在请求期间被创建了。 8.4. UsernamePasswordAuthenticationFilter

我现在看到了三个主要的过滤器,它们会一直在spring security 的web 配置中起作用。它

们也是由命名空间元素自动创建的三个,它们是无法修改的。唯一丢失的是一个

真正的认证机制,它们会执行一个用户的认证。这个过滤器是最常用的认证过滤器, 这个

过滤器也通常需要自定义。[8]. 它也提供了使用元素的实现,来自命名空

间。有三个阶段需要配置它。

配置一个LoginUrlAuthenticationEntryPoint 使用一个登陆页URL,就像我们上面那样,

把它配置到ExceptionTranslationFilter 中。

实现一个登陆页面(使用JSP 或MVC 控制器)。

配置一个UsernamePasswordAuthenticationFilter 的实例,放在application context

中。

把过滤器bean 添加到你的过滤器链代理中(确认你注意了顺序)。

登陆表单简单包含了j_username 和j_password 输入框, 提交由过滤器管理的URL,

(默认为:/j_spring_security_check). 基本的过滤器配置看起来像这样:

"mePasswordAuthenticationFilter">

8.4.1. 认证成功和失败的应用流程

过滤器调用了配置的AuthenticationManager 处理每个认证请求。认证成功或失败的目

的地是由AuthenticationSuccessHandler 和AuthenticationFailureHandler 策略接

口各自控制的。过滤器的属性允许我们设置这些,这样你可以随心所欲的自定义他们的行

为。[9]. 我们提供了一些标准实现,比如SimpleUrlAuthenticationSuccessHandler,

SavedRequestAwareAuthenticationSuccessHandler ,

SimpleUrlAuthenticationFailureHandler 和

ExceptionMappingAuthenticationFailureHandler。参考这些类的javadoc,了解他

们是如何工作的。

如果认证成功,结果Authentication 对象会被放到SecurityContextHolder 中。配置

的AuthenticationSuccessHandler 会被调用, 进行重定向会把用户跳转到合适的目的

地。默认情况,会使用SavedRequestAwareAuthenticationSuccessHandler。这意

味着,用户会被重定向到原始的目标,他们在登录之前请求的页面。

Note

ExceptionTranslationFilter 缓存了一个用户的原始请求。当用户认证时,请求处理器从

这个缓存的请求中获得原始的URL,并重定向到它。原始请求然后重新构造,作为一个可

选项使用。

如果认证失败,配置好的AuthenticationFailureHandler 会被调用。

[6]我们使用forward,这样SecurityContextHolder 会包含主体的信息, 这可能对现实

用户信息很有帮助。在老版本的Spring Security 中,我们让servlet 容器处理一个403

错误信息,这可能丢失了有用的上下文信息。

[7]

在spring security 2.0 以及以前, 这个过滤器叫做

HttpSessionContextIntegrationFilter,它会执行保存上下文的所有工作。如果你对这

个类很熟悉, 然后大多数的配置属性现在都可以在 HttpSessionSecurityContextRepository 中找到。

[8]

因为一些历史原因, 在Spring Security 3.0 之前, 这个过滤器被称为

AuthenticationProcessingFilter , 入口点被称为

AuthenticationProcessingFilterEntryPoint。因为这个框架现在支持了很多不同的认证

表单,它们都需要在3.0中给与更确切的名字。

[9]在版本3.0之前,这一点的应用流程被当做一个状态,通过这个类的一系列属性和策略

插件进行处理。这个决定让3.0重构了代码,让两个策略完全负责。

Basic(基本)和Digest(摘要)验证

Basic(基本)和Digest(摘要)验证都是web 应用中很受欢迎的可选机制。Basic 验证

一般用来处理无状态的客户端,它们在每次请求都附带它们的证书。很常见的用法是把它

和基于表单的验证一起使用,这里的应用会同时使用基于浏览器的用户接口和web 服务。

然而,basic 验证使用原文传送密码,所以应该只通过加密的传输途径发送,比如HTTPS。

9.1. BasicAuthenticationFilter

BasicAuthenticationFilter 负责处理通过HTTP 头部发送来的basic 验证证书。它可以

用来像对待普通用户代理一样(比如IE 和Navigator)认证由Spring 远程协议的调用(比

如Hessian 和Burlap )。HTTP 基本认证的执行标准定义在RFC 1945 , 11 章,

BasicAuthenticationFilter 符合这个RFC。基本认证是一个极具吸引力的认证方法,因

为它在用户代理发布很广泛, 实现也特别简单(只需要对username:password 进行

Base64编码,再放到HTTP 头部里)。

9.1.1. 配置

要实现HTTP 基本认证,要先在过滤器链里定义BasicAuthenticationFilter。还要在

application context 里定义BasicAuthenticationFilter 和协作的类:

class="uthenticationFilter">

class="uthenticationEntryPoint">

配置好的AuthenticationManager 会处理每个认证请求。如果认证失败,配置好的

AuthenticationEntryPoint 会用来重试认证过程。通常你会使用

BasicAuthenticationEntryPoint,它会返回一个401响应,使用对应的头部重试HTTP

基本验证。如果验证成功,就把得到的Authentication 对象放到SecurityContextHolder

里。

如果认证事件成功,或者因为HTTP 头部没有包含支持的认证请求所以没有进行认证,过滤

器链会像通常一样继续下去。唯一打断过滤器的情况是在认证失败并调用

AuthenticationEntryPoint 的时候,向上面段落里讨论的那样。

9.2. DigestAuthenticationFilter

Spring Security 提供了一个DigestAuthenticationFilter,它可以处理HTTP 头部中的

摘要认证证书。摘要认证在尝试着解决许多基本认证的缺陷,特别是保证不会通过纯文本 发送证书。许多用户支持摘要式认证,包括FireFox 和IE。HTTP 摘要式认证的执行标

准定义在RFC 2617,它是对RFC 2069这个早期摘要式认证标准的更新。Spring

Security DigestAuthenticationFilter 会保证"auth"的安全质量(qop),它订明在RFC

2617中,并与RFC 2069提供了兼容。如果你需要使用没有加密的HTTP(比如没有

TLS/HTTP),还希望认证达到最大的安全性的时候,摘要式认证便具有很高吸引力。事实

上,摘要式认证是WebDAV 协议的强制性要求,写在RFC 2518的17.1章,所以我们应

该期望看到更多的代替基本认证。

摘要式认证,是表单认证,基本认证和摘要式认证中最安全的选择,不过更安全也意味着更

复杂的用户代理实现。摘要式认证的中心是一个“nonce”。这是由服务器生成的一个值。

Spring Security 的nonce 采用下面的格式:

base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))

expirationTime: The date and time when the nonce expires, expressed in

milliseconds

key: A private key to prevent modification of the nonce token

这个DigestAuthenticationEntryPoint 有一个属性,通过指定一个key 来生成nonce 标

志,通过nonceValiditySeconds 属性来决定过期时间(默认300,等于5分钟)。只要

nonce 是有效的,摘要就会通过串联字符串计算出来,包括用户名,密码, nonce,请求

的URI,一个客户端生成nonce(仅仅是一个随机值,用户代理每个请求生成一个),realm

名称等等,然后执行一次MD5散列。服务器和用户代理都要执行这个摘要计算,如果他们

包含的值不同(比如密码),就会生成不同的散列码。在Spring Security 的实现中,如

果服务器生成的nonce 已经过期(但是摘要还是有效),DigestAuthenticationEntryPoint

会发送一个"stale=true"头信息。这告诉用户代理,这里不再需要打扰用户(像是密码和

用户其他都是正确的),只是简单尝试使用一个新nonce。

DigestAuthenticationEntryPoint 的nonceValiditySeconds 参数,会作为一个适当的

值依附在你的程序上。对安全要求很高的用户应该注意,一个被拦截的认证头部可以用来

假冒主体,直到nonce 达到expirationTime。在选择合适的配置的时候,这是一个必须

考虑到的关键性条件,但是在对安全性要求很高的程序里,第一次请求都会首先运行在

TLS/HTTPS 之上。

因为摘要式认证需要更复杂的实现,这里常常有用户代理的问题。比如,IE 不能在同一个

会话的请求进程里阻止"透明"标志。因此Spring Security 把所有状态信息都概括到

"nonce"标记里。在我们的测试中,Spring Security 在FireFox 和IE 里都可以工作,正

确的处理nonce 超时等等。

9.2.1. Configuration

现在我们重新看一下理论,让我们看看如何使用它。为了实现HTTP 摘要认证,必须在过

滤器链里定义DigestAuthenticationFilter 。application context 还需要定义

DigestAuthenticationFilter 和它需要的合作伙伴:

"AuthenticationFilter">

需要配置一个UserDetailsService,因为DigestAuthenticationFilter 必须直接访问用

户的纯文本密码。如果你在DAO 中使用编码过的密码,摘要式认证就没法工作。DAO

合作者,与UserCache 一起,通常使用DaoAuthenticationProvider 直接共享。这个

AuthenticationEntryPoint 属性必须是DigestAuthenticationEntryPoint , 这样

DigestAuthenticationFilter 可以在进行摘要计算时获得正确的realmName 和key。

像BasicAuthenticationFilter 一样,如果认证成功,会把Authentication 请求标记放到

SecurityContextHolder 中。如果认证事件成功,或者认证不需要执行,因为HTTP 头

部没有包含摘要认证请求,过滤器链会正常继续。过滤器链中断的唯一情况是,如果认证

失败,就会像上面讨论的那样调用AuthenticationEntryPoint。

摘要式认证的RFC 要求附加功能范围,来更好的提升安全性。比如,nonce 可以在每次

请求的时候变换。但是,Spring Security 的设计思路是最小复杂性的实现(毫无疑问,

用户代理会出现不兼容),也避免保存服务器端的状态。如果你想研究这些功能的更多细节,

我们推荐你看一下RFC 2617。像我们知道的那样,Spring Security 实现类遵守了RFC

的最低标准。

Remember-Me 认证

10.1. 概述

记住我(remember-me)或持久登录(persistent-login)认证,指的是网站可以在不同

会话之间记忆验证的身份。通常情况是发送一个cookie 给浏览器,在以后的session 里

检测cookie,进行自动登录。Spring Security 为remember-me 实现提供了必要的调

用钩子,并提供了两个remember-me 的具体实现。其中一个使用散列来保护基于cookie

标记的安全性,另一个使用了数据库或其他持久化存储机制来保存生成的标记。

注意,两个实现方式,都需要UserDetailsService。如果你使用了认证提供器,没有使

用UserDetailsService(比如LDAP 供应器),那它就没法工作,除非你在application

context 里设置了一个UserDetailsService。

10.2. 简单基于散列标记的方法

这种方法使用散列来完成remember-me 策略。本质上,在成功进行认证的之后,把一

个cookie 发送给浏览器,使用的cookie 组成结构如下:

base64(username + ":" + expirationTime + ":" +

md5Hex(username + ":" + expirationTime + ":" password + ":" + key))

username: As identifiable to the UserDetailsService

password: That matches the one in the retrieved UserDetails

expirationTime: The date and time when the remember-me token expires,

expressed in milliseconds

key: A private key to prevent modification of the remember-me token

这个remember-me 标记只适用于指定范围,提供用户名,密码和关键字都不会改变。值

得注意,这里有一个潜在的安全问题,来自任何一个用户代理的remember-me 标记,直

到标记过期都是可用的。这个问题和摘要式认证相同。如果一个用户发现标记已经设置了,

他们可以轻易修改他们的密码,并且立即注销所有的remember-me 标记。如果需要更 好的安全性,你应该使用下一章描述的方法。或者不应该使用remember-me 服务。

如果你还记得在命名空间配置中讨论的主题,你只要添加元素就可以使用

remember-me 认证:

...

这个UserDetailsService 会自动选上。如果你在application context 中配置了多个,

你需要使用user-service-ref 属性指定应该使用哪一个, 这里的值要放上你的

UserDetailsService bean 的名字。

10.3. 持久化标记方法

这个方法是基于这篇文章

/improved_persistent_login_cookie_best_practice

进行了一些小修改

[10]。要用在命名空间配置里使用这个方法,你应该提供一个datasource 引用:

...

数据应该包含一个persistent_logins 表,可以使用下面的SQl 创建(或等价物):

create table persistent_logins (username varchar(64) not null, series varchar(64)

primary key,

token varchar(64) not null, last_used timestamp not null)

10.4. Remember-Me接口和实现

Remember-me 认证不能和基本认证一起使用,因为基本认证往往不使用HttpSession。

Remember-me 使用在UsernamePasswordAuthenticationFilter 中,通过在它的超类

AbstractAuthenticationProcessingFilter 里实现的一个调用钩子。这个钩子会在合适的

时候调用一个具体的RememberMeServices。这个接口看起来像这样:

Authentication autoLogin(HttpServletRequest request, HttpServletResponse

response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,

Authentication successfulAuthentication);

请参考JavaDocs 获得有关这些方法的完整讨论, 不过注意在这里,

AbstractAuthenticationProcessingFilter 只调用loginFail() 和loginSuccess()方法。

当SecurityContextHolder 没有包含Authentication 的时候,

RememberMeProcessingFilter 才去调用autoLogin()。因此,这个接口通过使用完整

的认证相关事件的提醒提供了下面remember-me 实现,然后在可能包含一个cookie 希

望被记得的申请web 请求中调用这个实现。这个设计允许任何数目的remember-me 实

现策略。我们在下面看看上面介绍过的两个Spring Security 提供的实现。

10.4.1. TokenBasedRememberMeServices

这个实现支持在Section 10.2, “ 简单基于散列标记的方法”

里描述的简单方法。

TokenBasedRememberMeServices 被RememberMeAuthenticationProvider 执行

的时候生成一个RememberMeAuthenticationToken 。认证提供器和

TokenBasedRememberMeServices 之间共享一个key 。另外 TokenBasedRememberMeServices 需要一个UserDetailsService,用它来获得用户名

和密码, 进行比较, 然后生成RememberMeAuthenticationToken 来包含正确的

GrantedAuthority[]。如果用户请求注销,让cookie 失效,就应该使用系统提供的一系

列注销命令。TokenBasedRememberMeServices 也实现Spring Security 的

LogoutHandler 接口,这样可以使用LogoutFilter 自动清除cookie。

这些bean 要求在application context 里启用remember-me 服务,像下面一样:

class="erMeAuthenticatio

nFilter">

"asedRememberMeService

s">

class="erMeAuthenticatio

nProvider">

不要忘记把你的RememberMeServices 实现添加到

emberMeServices()属性中,包括

把RememberMeAuthenticationProvider 添加到你的

viders() 队列中, 把

RememberMeProcessingFilter 添加到你的FilterChainProxy 中( 要放到

AuthenticationProcessingFilter 后面)。

10.4.2. PersistentTokenBasedRememberMeServices

这个类可以像TokenBasedRememberMeServices 一样使用,但是它还需要配置一个

PersistentTokenRepository 来保存标记。这里有两个标准实现。

InMemoryTokenRepositoryImpl 最好是只用来测试。

JdbcTokenRepositoryImpl 把标记保存到数据库里。

数据库表结构在Section 10.3, “持久化标记方法”.

[10]基本上,为了防止暴露有效登录名,用户名没有包含在cookie 里。在这个文章的评论里

有一个相关的讨论。

会话管理

HTTP 会话相关的功能是由SessionManagementFilter 和

SessionAuthenticationStrategy 接口联合处理的, 过滤器会代理它们。典型的应用包

括会话伪造攻击预防,检测会话超时, 限制已登录用户可以同时打开多少会话。 11.1. SessionManagementFilter

SessionManagementFilter 会检测SecurityContextRepository 的内容,比较当前

SecurityContextHolder,决定用户在当前请求中是否已经登录, 通常被一个非内部认证

机制,比如预验证或remember-me[11]

如果资源库中包含一个安全上下文,过滤器什么

也不会做。如果没有包含,thread-local 中SecurityContext 包含一个(非匿名)

Authentication 对象,过滤器就会假设他们已经在过滤器栈中的前一个过滤器中被认证过

了。它会调用配置好的SessionAuthenticationStrategy。

如果用户当前没有认证,过滤器会检测是否有无效的session ID 被请求了(比如,因为超

时) 并会重定向到配置好的invalidSessionUrl,如果设置了。最简单的配置方法是通过

命名空间,如前面描述的。

11.2. SessionAuthenticationStrategy

SessionAuthenticationStrategy 被SessionManagementFilter 和

AbstractAuthenticationProcessingFilter 都是用了, 所以如果你使用了一个自定义的

formlogin 类,比如,你需要把它注入到两者中。比如,在典型配种,结合命名空间和自

定义bean 看起来像这样:

class="mePasswordAuthenticationFilter"

>

class="rentSessionControlStrateg

y">

11.3. 同步会话

Spring Security 可以防止一个主体同时被一个相同的应用授权多于一个特定的次数。许

多ISV 可以利用这一点来加强协议,网络管理员就很喜欢这点功能,因为它可以防止人们

共享登陆账号。你可以,比如,停止在两个不同的会话中的登陆web 应用的用户

“Batman”。你可以选择让上一次登录过期,或者当他们尝试重复登录时报告一个错误,

防止第二次登录。注意,如果你使用第二种方式,一个用户如果没有使用注销(而是仅仅

关闭了他们的浏览器,比如) 就不能再次登陆了,直到他们之前的会话失效之前。

这个功能在命名空间中已经支持了,所以请参考前面介绍命名空间的章节, 来了解简便的

配置方式。有时你需要做些一次自定义的工作。

实现使用了特定版本的SessionAuthenticationStrategy , 称作

ConcurrentSessionControlStrategy。

发布者:admin,转转请注明出处:http://www.yc00.com/news/1687692088a32131.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信