lengyun3566的博客文章 作者: lengyun3566 http://lengyun3566.iteye.com 《Spring Security 3》翻译文档汇总,如转载请注明出处 本人新浪微博:http://weibo.com/1920428940 http://www.iteye.com - 做最棒的软件开发交流社区 第 1 / 350 页 本书由ITeye提供的电子书DIY功能自动生成于 2012-03-12 目 录 1. Spring Security 1.1 关于对《Spring Security3》一书的翻译说明 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2 《Spring Security3》第一章第一部分翻译 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.3 《Spring Security3》第一章第二部分翻译 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.4 《Spring Security3》第二章第一部分翻译 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15 1.5 《Spring Security3》第二章第二部分翻译 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .19 1.6 《Spring Security3》第二章第三部分翻译(上) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25 1.7 《Spring Security3》第二章第三部分翻译(中) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .36 1.8 《Spring Security3》第二章第三部分翻译(下)附前两章doc文档 . . . . . . . . . . . . . . . . . . . . . . . . .41 1.9 《Spring Security3》第三章第一部分翻译 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .46 1.10 《Spring Security3》第三章第二部分翻译(退出功能的实现) . . . . . . . . . . . . . . . . . . . . . . . . . . .50 1.11 《Spring Security3》第三章第三部分翻译上(Remember me功能实现) . . . . . . . . . . . . . . . . . .54 1.12 《Spring Security3》第三章第三部分翻译下(Remember me安全吗?) . . . . . . . . . . . . . . . . . .60 1.13 《Spring Security3》第三章第四部分翻译(修改密码) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .68 1.14 《Spring Security3》第四章第一部分翻译上(数据库管理信息) . . . . . . . . . . . . . . . . . . . . . . . . .74 1.15 《Spring Security3》第四章第一部分翻译下(自定义的UserDetailsService) . . . . . . . . . . . . . .79 1.16 《Spring Security3》第四章第二部分翻译(JdbcDaoImpl的高级配置) . . . . . . . . . . . . . . . . . . .84 1.17 《Spring Security3》第四章第三部分翻译上(配置安全的密码) . . . . . . . . . . . . . . . . . . . . . . . . .90 1.18 《Spring Security3》第四章第三部分翻译下(密码加salt) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .95 http://lengyun3566.iteye.com 第 2 / 350 页 1.19 《Spring Security3》第四章第四部分翻译(Remember me后台存储和SSL)附前四章doc文 件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .104 1.20 《Spring Security3》第五章第一部分翻译(重新思考应用功能和安全) . . . . . . . . . . . . . . . . . . .110 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法——页面级权限) . . .114 1.22 《Spring Security3》第五章第二部分翻译下(实现授权精确控制的方法——页面级权限) . . .119 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .122 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小结) . . . . . . . . . . . . . . . . .130 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) . . . . . . . . . . . . . . . . . . . . . . . .139 1.26 《Spring Security3》第六章第二部分翻译(自定义AuthenticationProvider) . . . . . . . . . . . . .144 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) . . . . . . . . . . . . . . . . . . . . .152 1.28 《Spring Security3》第六章第四部分翻译(异常处理) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .162 1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设施的bean) . . . . . . . .167 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean的高级配置) . . . . . . . .175 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) . . . . . . . . . . . . . . . . . . . . . .185 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) . . . . . . . . . . . . . . . . . . . . . . . .191 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) . . . . . . . . . . . . . . . . . . . . . . . . . .204 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) . . . . . . . . . . . . . . . . . . . . . . . . . .212 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) . . . . . . . . . . . . . . . . . . . . . . . . . .220 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) . . . . . . . . . . . . . . . . .225 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) . . . . . . . . . . . . . . . . . . . . . . .230 1.38 《Spring Security3》第八章第三部分翻译(属性交换) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .239 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) . . . . . . . . . . . . . . . . . .246 http://lengyun3566.iteye.com 第 3 / 350 页 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) . . . . . . . . . . . . . . . . . .257 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) . . . . . . . . . . . . . . . . . .266 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) . . . . . . . . . . . . . . . . . . . . .273 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) . . . . . . . . . . . . . . . . . . . . .282 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 . . . . . . . . . . . . . . . . . . . . . . . .295 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 . . . . . . . . . . . . . . . . . . . . . . . .304 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) . . . . . . . . . . . . . . . . . . . . . . . . . . . .315 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) . . . . . . . . . . . . . . . . . . . . . . . .332 1.48 《Spring Security3》附录翻译(参考资料) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .342 http://lengyun3566.iteye.com 第 4 / 350 页 1.1 关于对《Spring Security3》一书的翻译说明 发表时间: 2011-06-02 关键字: Spring 最近阅读了《Spring Security3》一书,颇有收获(封面见图片)。因此将其部分内容翻译成中文,对于电子版 内容,本人放弃一切权利。 在翻译之前,本人曾发邮件征询原作者的意见,如今已是半月有余,未见其回复。因为非盈利为目的,所以斗 胆将内容发布于博客之上。 这是我第一次翻译东西,有很多地方觉得翻译的很生硬,希望阅读到的朋友们踊跃拍砖。 我的新浪微博:http://weibo.com/1920428940 本书源代码的地址:http://www.packtpub.com/support?nid=4435 http://lengyun3566.iteye.com 1.1 关于对《Spring Security3》一书的翻译说明 第 5 / 350 页 1.2 《Spring Security3》第一章第一部分翻译 发表时间: 2011-06-02 关键字: Spring, Web, MVC, Security, 设计模式 第一章 一个不安全应用的剖析 毫无疑问,安全是任何一个写于21世纪的web工程中最重要的架构组件之一。在这样一个时代,计算机病 毒、犯罪以及不合法的员工一直存在并且持续考验软件的安全性试图有所收益,因此对你负责的项目综合合理 地使用安全是至关重要的一个元素。 本书的写作遵循了这样的一个开发模式,这个模式我们感觉提供了一个有用的前提来解决复杂的话题—— 即使用一个基于Spring3的web工程作为基础,以理解使用Spring Security3使其保证安全的概念和策略。 不管你是不是已经使用Spring Security还是只是对这个软件有兴趣,就都会在本书中得到有用的信息。 在本节的内容中,你能够: l 检查一个虚拟安全审计的结果 l 讨论web应用通常的一些安全问题 l 学习软件安全中的几个核心词汇和概念 如果你已经熟悉基本的安全术语,你可以直接跳到第二章:Spring Security起步,在哪里我们将涉及这个 框架的细节。 安全审计 这是你作为软件开发人员在Jim Bob Circle Pant Online Pet Store(JBCPPets.com)工作的一个清晨,你 正在喝你的第一杯咖啡的时候,收到了你主管的以下邮件: To: Star Developer From: Super Visor Subject: 安全审计 Star, http://lengyun3566.iteye.com 1.2 《Spring Security3》第一章第一部分翻译 第 6 / 350 页 今天,有一个第三方的安全公司要审计我们的电子商务网站。尽管我知道你在设计网站的时候已经把 安全考虑在内了,但请随时解决他们可能发现的问题。 Super Visor 什么?你在设计应用的时候并没有过多考虑安全的问题?似乎你有许多的东西要向安全审计人员学习。首 先,让我们花一点时间检查一下要审计的应用吧。 关于样例应用 尽管在本书的后续内容中我们要模拟虚拟的场景,但这个应用的设计以及我们对其进行的改造都是基于现 实世界中真实使用Spring的工程。 这个应用的设计很简单,使得我们能够关注于重要的安全方面而不会过多关注ORM的细节和复杂的UI技 术。我们期望你能够参考其他的资料来掌握示例代码中所涉及功能的技术。 代码是基于Spring和Spring Security3编写而成的,但是它很容易改变为Spring Security2的例子。关于 Spring Security 2和3的细节差异,可以参照第十三章:迁移至Spring Security,可以作为将示例代码转换成 Spring Security2的帮助材料。 不要以本应用作为基础去构建真实的Pet Store在线应用。本应用已经有意识的构建地简单并关注于本书 所要阐述的概念和配置。 JBCP Pets应用的架构 本应用遵循标准的三层结构,包括web层、服务层和数据访问层,如下图所示: web层封装了MVC的代码和功能。在示例代码中,我们使用了Spring MVC框架,但是我们可以一样容易的使 用Spring Web Flow,Struts甚至是一个对Spring友好的web stack如Apache Wicket。 在一个典型使用Spring Security的web应用中,大量配置和参数代码位于web层。所以,如果你没有web 应用开发,尤其是Spring MVC的经验,在我们进入更复杂的话题前,你最好仔细看一下基础代码并确保你能理 解。再次强调,我们已经尽力让我们的应用简单,把它构建成一个pet store只是为了给它一个合理的名字和轻 量级的结构。可以将其与复杂的Java EE Pet Clinic示例作为对比,那个示例代码展现了很多技术的使用指导。 http://lengyun3566.iteye.com 1.2 《Spring Security3》第一章第一部分翻译 第 7 / 350 页 服务层封装了应用的业务逻辑。在示例应用中,我们在数据访问层前做了一个很薄的façade用来描述如何 在特殊的点周围保护应用的服务方法。 在典型的web工程中,这一层将会包括业务规则校验,组装和分解BO以及交叉的关注点如审计等。 数据访问层封装了操作数据库表的代码。在很多基于Spring的工程中,这将会在这里发现使用了ORM技术 如hibernate或JPA。它为服务层暴露了基于对象的API。在示例代码中,我们使用基本的JDBC功能完成到内存 数据库HSQL的持久化。 在典型的web工程中,将会使用更为复杂的数据访问方式。因为ORM,即数据访问,开发人员对其很迷 惑。所以为了更清晰,这一部分我们尽可能的对其进行了简化。 http://lengyun3566.iteye.com 1.2 《Spring Security3》第一章第一部分翻译 第 8 / 350 页 1.3 《Spring Security3》第一章第二部分翻译 发表时间: 2011-06-08 关键字: Spring, Security, Eclipse, 企业应用, 应用服务器 应用所使用的技术 我们使用了一些每个Spring程序员都会遇到的技术和工具,以使得示例应用很容易的运行起来。尽管如 此,我们还是提供了补充的起步资料信息在附录:参考资料。 我们建立使用如下的IDE以提高开发的效率并使用本书的示例代码: l Eclipse 3.4或3.5 Java EE版本可以在以下地址获得: http://www.eclipse.org/downloads/ l Spring IDE2.2(2.2.2或更高)可以在以下地址获得: http://springide.org/blog 在本书的示例和截图中,你会看到Eclipse和Spring IDE,所以我们建议你使用它们。 你可能希望使用免费的Spring Tool Suite(STS)Eclipse插件,它作为Eclipse的一个插件由Spring Source开发,其包含了下一代的Spring IDE功能(可以在以下地址下载:http://www.springsource.com/ products/springsource-tool-suite-download)。一些用户不喜欢它的侵入性和SpringSource的标示,但是 你如果从事Spring相关的开发,它提供了很多有用的功能。 我们提供了Eclipse3.4兼容的工程以允许你在Eclipse中构建和部署代码到Tomcat6.x的服务器上。鉴于大多 数开发人员熟悉Eclipse,所以我们觉得这是最直接的方式来打包示例代码。我们为这些例子提供了Apache Ant 的脚本以及Apache Maven的modules。不管你熟悉开发环境,我们希望你能够在阅读本书的时候能够运行示 例代码。 另外,在阅读过程中,你可能会愿意去下载Spring 3和Spring Security 3的源码版本。如果你有不明白的 地方或想获取更多的信息,他们的JavaDoc和源码是最好的参考资料,他们提供的示例也能够提供额外的帮助 并消除你的疑惑。 查看审计结果 让我们回到e-mail并看一下审计的进展。哦,结果貌似并不好啊: http://lengyun3566.iteye.com 1.3 《Spring Security3》第一章第二部分翻译 第 9 / 350 页 To: Star Developer From: Super Visor Subject: FW: Security Audit Results Star, 看一下审计结果并制定一个计划来解决这些问题。 Super Visor 审计结果: 本应用存在如下的不安全隐患: l 缺少URL保护和统一的认证造成的权限扩散; l 授权不合理甚至缺失; l 数据库认证信息不安全且很容易获取; l 个人的识别信息和敏感数据很容易获取或没有加密; l 不安全的传输层保护,没有使用SSL加密; l 危险等级:高 我们希望本应用在解决这些问题前能够下线。 哦,这些结果看起来对我们公司很不利,我们最好尽快将这些问题解决。 公司(或他们的合作伙伴、顾客)会雇佣第三方的安全专家来审计他们软件的安全性。审计过程会联合使用 破解,代码检查以及与开发人员和架构师的正式或非正式交流。 http://lengyun3566.iteye.com 1.3 《Spring Security3》第一章第二部分翻译 第 10 / 350 页 安全审计的通常目标是保证基本的安全开发措施被遵守以实现客户数据和系统功能的完整性和安全性。依靠 软件业所追求的工业化目标,审计人员可能会使用工业化的标准或特定的方式来进行这些测试。 收到这样的安全审计结果可能是一件令人很吃惊的事情,但是,如果你按照被要求去做,这将会是一个绝佳 的机会来学习和提升软件质量,同时,这将会引领你去实现一些常规的策略来保证软件的安全性。 让我们来看一下审计人员发现的问题并制定一个计划去解决他们。 认证 缺少URL保护和统一的认证造成的权限扩散 认证是在开发安全应用中,你必须记住的两个关键词之一(另外一个是授权)。认证识别系统中的某一个用 户,并将其与一个可信任的(即安全的)实体关联。一般来讲,软件系统会被分为两个层次的访问范围,如 未认证通过的(或匿名的)和认证通过的,如下图所示: 匿名可访问的应用功能是用户无关的(如一个在线商店的产品列表)。 匿名区域不会: l 要求用户登录系统; l 展示敏感信息如人名、地址、信用卡号、订单等; l 提供管理系统全局的状态或数据。 未认证的系统区域可供任何人使用,即使该用户没有被系统所识别。但是,有可能对于已认证的用 户会看到更多的信息(如随处可见的欢迎{名字}文本)。Spring Security的标签库可以完全支持对登录 用户进行有区别的显示,这部分内容将在第五章:精确的访问控制中介绍。 http://lengyun3566.iteye.com 1.3 《Spring Security3》第一章第二部分翻译 第 11 / 350 页 我们将在第二章中解决这个发现的问题并使用Spring Security的自动配置功能实现一个基于表单的 认证。更复杂的表单认证(通常用来用在与企业系统集成或外部的认证存储)将在本书的第二部分进行 讲解,这部分从第八章:对OpenID开放开始。 授权 授权不合理甚至缺失 授权是两个重要安全概念中的第二个,它对实现和理解应用安全很重要。授权保证授权过的用户能 够对功能和数据进行恰当的访问。构建应用的安全模块的主要任务是拆分应用的功能和数据并将权限、 功能和数据、用户结合起来,以实现对这些内容的访问能够被很好的控制。在审计中,我们的应用在这 一点的失败表明应用的功能没有按照用户角色进行限制。试想一下,你正在运行一个在线购物系统,查 看、取消或修改订单以及用户的信息对所有的用户可见!(这将是多么恐怖的事情) 我们将会在第二章通过使用Spring Security的授权模块解决基本的授权问题,接着会有关于授权更 高级的知识介绍,其中在第五章介绍web层,在第六章:高级配置和扩展中介绍业务层。 数据库认证安全 数据库认证信息不安全且很容易获取 通过检查应用的源码和配置文件,审计人员指出用户的密码在配置文件中以明文的显示存储,这会 导致恶意的用户很容易通过访问到服务器进而访问应用。 因为应用中包含了个人和财务的信息,一个恶意的用户如果能够非法访问任何数据都会导致公司信 息的泄露。保护能够进入系统的凭证信息对我们来时是最重要的事情,最重要的第一步是保证在安全上 一个点的失败不会连累整个系统。 我们将会在第四章:凭证安全存储中检查Spring Security的数据访问层以实现凭证存储的安全配 置,那里将会用到JDBC连接。在第四章中,我们同时也会学习一些内置的技术以增强保存在数据库中 密码的安全。 敏感信息 个人的识别信息和敏感数据很容易获取或没有加密 审计人员指出一些重要和敏感的数据,包括信用卡号,在系统中的任何地方都没有加密或混淆。这 除了是缺乏数据设计的一种典型表现外,对信用卡号缺少保护也是违反PCI标准的。 幸运的是,基于Spring Security的注解式AOP支持,我们可以使用一些简单的设计模式和工具来 安全的保护这些信息。我们将会在第五章中阐述在数据访问层保护这种数据的一些技术。 http://lengyun3566.iteye.com 1.3 《Spring Security3》第一章第二部分翻译 第 12 / 350 页 数据传输层保护 不安全的传输层保护,没有使用SSL加密 在现实世界中,很难想象一个在线的商业网站不使用SSL保护;不幸的是,我们的JBCP Pet正是这样。SSL保护 能够保证在客户端浏览器和web应用服务器之间的通信是安全的,可以防护多种的信息篡改和窥探。 在第五章中,我们将会介绍如何在应用的安全架构中使用传输层安全配置,包括规划应用的那些部分要 使用强制的SSL加密。 使用Spring Security解决安全问题 Spring Security提供了丰富的资源,许多安全的通用问题都可以用非常简单的声明或配置来解决。 在接下来的章节中,我们将会通过源代码和应用配置的不断改进来解决安全审计人员提出的所有问题(甚至更 多),同时会对我们应用的安全性树立信心。 使用Spring Security 3,我们将会通过以下的变化来提高应用的安全性: l 细分系统的用户类别; l 为用户的角色授予不同级别的权限; l 为不同的用户类别授予不同的角色; l 为应用全局的资源使用认证规则; l 为应用所有层的结构使用授权规则; l 阻止常用的攻击手段以控制或窃取一个用户的session。 为什么使用Spring Security? Spring Security的存在填补了众多java第三方类库的一个空白,正如Spring框架第一次出现时那样。 一些业界的标注如JAAS或Java EE Security确实提供了一些方式来解决认证和授权问题,但是Spring Security是一个胜出者,因为它以一种简明和合理的方式封装了各个层的安全解决方案。 除此以外,Spring Security提供了与很多通用企业认证系统的内置集成支持。所以,对开发者来说, 它通过很少的努力就能适应绝大多数的场景。 因为没有任何一款主流的框架与之匹敌,所以它被广泛的使用。 http://lengyun3566.iteye.com 1.3 《Spring Security3》第一章第二部分翻译 第 13 / 350 页 小结 在本章中,我们: l 检查了一个不安全的web工程的风险点; l 查看了示例在线商务应用的基本架构; l 讨论了使示例工程安全的策略。 在第二章中,我们将会介绍Spring Security的整体架构,主要侧重于其如何扩展和配置以支持我们的 应用的需要。 http://lengyun3566.iteye.com 1.3 《Spring Security3》第一章第二部分翻译 第 14 / 350 页 1.4 《Spring Security3》第二章第一部分翻译 发表时间: 2011-06-11 关键字: Spring, Security, 配置管理, 企业应用, 应用服务器 第二章 Spring Security起步 在本章中,我们将要学习Spring Security背后的核心理念,包括重要的术语和产品架构。我们将会关 注配置Spring Security的一些方式以及对应用的作用。 最重要的是为了解决工作中的问题,我们要开始使得JBCP Pets的在线商店系统变得安全。我们将会通 过分析和理解认证如何保护在线商店的适当区域来解决在第一章:一个不安全应用的剖析中审计人员发现的 第一个问题,即缺少URL保护和统一的认证造成的权限扩散。 在本章的内容中,我们将会涉及: l了解应用中安全的重要概念; l使用Spring Security的快速配置功能,为JBCP Pets在线商店实现基本层次的安全; l理解Spring Security的全貌; l探讨认证和授权的标准配置和选项; l在Spring Security访问控制中使用Spring的表达式语言(Spring Expression Language) 安全的核心概念 由于安全审计结果的启示作用,你研究了Spring Security并确定它能够提供一个坚实的基础,以此可以构 建一个安全的系统来解决在安全审计JBCP Pet在线商店中发现的问题,而那个系统是基于Spring Web MVC开 发的。 为了Spring Security的使用更高效,在开始评估和提高我们应用的安全状况之前,先了解一些关键的概念 和术语是很重要的。 http://lengyun3566.iteye.com 1.4 《Spring Security3》第二章第一部分翻译 第 15 / 350 页 认证 正如我们在第一章所讨论的那样,认证是鉴别我们应用中的用户是他们所声明的那个人。你可能在在线或 线下的日常生活中,遇到不同场景的认证: l 凭据为基础的认证:当你登录e-mail账号时,你可能提供你的用户名和密码。E-mail的提供商会将你的用户名 与数据中的记录进行匹配,并验证你提供的密码与对应的记录是不是匹配。这些凭证(用户名和密码,译者 注)就是e-mail系统用来鉴别你是一个合法用户的。首先,我们将首先使用这种类型的认证来保护我们JBCP Pet在线商店的敏感区域。技术上来说,e-mail系统能够检查凭证信息不一定非要使用数据库而是各种方式,如 一个企业级的目录服务器如Microsoft Active Directory。一些这种类型的集成方式将在本书的第二部分讲解。 l 两要素认证:当你想从自动柜员机取钱的时候,你在被允许取钱和做其他业务前,你必须先插卡并输入你的密 码。这种方式的认证与用户名和密码的认证方式很类似,与之不同的是用户名信息被编码到卡的磁条上了。联 合使用物理磁卡和用户输入密码能是银行确认你可能有使用这个账号的权限。联合使用密码和物理设备(你的 ATM卡)是一种普遍存在的两要素认证形式。专业来看,在安全领域,这种类型的设备在安全性要求高的系统 中很常见,尤其是处理财务或个人识别信息时。硬件设备如RSA的SecurId联合使用了基于时间的硬件和服务端 的认证软件,使得这样的环境极难被破坏。 l 硬件认证:早上当你启动汽车时,你插入钥匙并打火。尽管和其他的两个例子很类似,但是你的钥匙和打火装 置的匹配是一种硬件认证的方式。 其实会有很多种的认证方式来解决硬件和软件的安全问题,它们各自也有其优缺点。我们将会在本书的后面章 节中介绍它们中的一些,因为它们适用于Spring Security。事实上,本书的后半部分基本上都是原来介绍很多 通用的认证方式用Spring Security的实现。 Spring Security扩展了java标准概念中的已认证安全实体(对应单词principal)(java.security.Principal), 它被用来唯一标识一个认证过的实体。尽管一个典型的安全实体通常一对一的指向了系统中的一个用户,但它 也可能对应系统的各种客户端,如web service的客户端、自动运行的feed聚合器(automated batch feed) 等等。在大多数场景下,在你使用Spring Security的过程中,一个安全实体(Principal)只是简单地代表一个 用户(user),所以我当我们说一个安全实体的时候,你可以将其等同于说用户。 授权 授权通常涉及到两个不同的方面,他们共同描述对安全系统的可访问性。 第一个是已经认证的安全实体与一个或多个权限(authorities)的匹配关系(通常称为角色)。例如,一 个非正式的用户访问你的网站将被视为只有访问的权限而一个网站的管理员将会被分配管理的权限。 http://lengyun3566.iteye.com 1.4 《Spring Security3》第二章第一部分翻译 第 16 / 350 页 第二个是分配权限检查给系统中要进行安全保护的资源。通常这将会在系统的开发过程中进行,有可能会 通过代码进行明确的声明也可能通过参数进行设置。例如,在我们应用中管理宠物商店详细目录的界面只能对 具有管理权限的用户开放。 【要进行安全保护的资源可以是系统的任何内容,它们会根据用户的权限进行有选择的可访问控制。web 应用中的受保护资源可以是单个的页面、网站的一个完整部分或者一部分界面。相反的,受保护的业务资源可 能会是业务对象的一个方法调用或者单个的业务对象。】 你可能想象的出对一个安全实体的权限检查过程,查找它的用户账号并确定它是不是真的为一个管理员。如 果权限检查确定这个试图访问受保护区区域的安全实体实际上是管理员,那么这个请求将会成功,否则,这个 安全实体的请求将会因为它缺少足够的权限而被拒绝。 我们更近距离的看一个特定的受保护资源——产品目录的编辑界面。目录的编辑界面需要管理员才能访问 (毕竟,我们不希望普通的用户能够调整我们的目录层次),因此当一个安全实体访问它的时候会要求特定等 级的权限。 当我们思考一个网站的管理员试图访问受保护的资源时,权限控制决定是如何做出的时候,我们猜想对受保 护资源的权限的检查过程可以用集合理论很简明的进行表述。我们将会使用维恩图来展现对管理用户的这个决 策过程: 对这个页面来说,在用户权限(普通用户和管理员)和需要权限(管理员)之间有一个交集,所以在交集中 的用户将能够进行访问。 可以与没有授权的访问者进行对比: 权限集合没有交集,没有公共的元素。所以,用户将会被拒绝访问这个界面。至此,我们已经介绍了对资源 授权的简单原理。 http://lengyun3566.iteye.com 1.4 《Spring Security3》第二章第一部分翻译 第 17 / 350 页 实际上,会有真正的代码来决定用户是允许还是被拒绝访问受保护的资源。下面的图片在整体上描述了这个 过程,正如Spring Security所使用的那样: 我们可以看到,有一个名为访问决策管理器(access decision manager)的组件来负责决定一个安全实体 是不是有适当的访问权限,判断基于安全实体具备的权限与被请求资源所要求资源的匹配情况。 安全访问控制器对访问是否被允许的判断过程可能会很简单,就像查看安全实体所拥有的权限集合与被访问 资源所要求的资源集合是不是有交集。 让我们在JBCP Pets应用中简单使用Spring Security,并将会更详细的阐述认证和授权。 http://lengyun3566.iteye.com 1.4 《Spring Security3》第二章第一部分翻译 第 18 / 350 页 1.5 《Spring Security3》第二章第二部分翻译 发表时间: 2011-06-15 关键字: Spring, Security, XML, Web, 配置管理 三步之内使我们的应用变得安全 尽管Spring Security的配置可能会很难,但是它的作者是相当为我们着想的,因为他们为我们提供了一种 简单的机制来使用它很多的功能并可以此作为起点。以这个为起点,额外的配置能够实现应用的分层次详细的 安全控制。 我们将从我们不安全的在线商店开始,并且使用三步操作将它变成一个拥有基本用户名和密码安全认证的 站点。这个认证仅仅是为了阐述使用Spring Security使我们应用变得安全的步骤,所以你可能会发现这样的方 式会有明显不足,这将会引领我们在以后的配置中不断进行改良。 实现Spring Security的XML配置文件 起点配置的第一步是创建一个XML的配置文件,用来描述所有需要的Spring Security组件,这些组件将 会控制标准的web请求。 在WEB-INFO目录下建立一个名为dogstore-security.xml的XML文件。文件的内容如下所示: http://lengyun3566.iteye.com 1.5 《Spring Security3》第二章第二部分翻译 第 19 / 350 页 这是我们应用中获得最小安全配置的唯一一个Spring Security配置文件。这个配置文件的格式使用了Spring Security特定的XML语法,一般称之为security命名空间。它在XML的命名空间中声明 (http://www.springframework.org/schema/security)并与XML配置元素关联。我们将在在第六章:高级 配置与扩展中讨论一种替代的配置方式,即使用传统的Spring Bean设置方式。 【讨厌Spring XML配置的用户可能会失望了,因为Spring Security没有像Spring那样提供可替代的注解机 制。仔细想一下也是可以理解的,因为Spring Security关注的是整个系统而不是单个对象或类。但未来,我们 可能能够在Spring MVC的控制器上使用安全注解,而不是在一个配置文件中指明URL模式!】 尽管在Spring Security中注解并不普遍,但正如你所预料的那样,对类或方法进行的配置是可以通过注解 来完成的。我们将在第五章:精确的访问控制中介绍。 添加Spring DelegatingFilterProxy到web.xml文件 Spring Security对我们应用的影响是通过一系列的ServletRequest过滤器实现的(我们将会在介绍 Spring Security架构的时候进行阐述)。可以把这些过滤器想成位于每个到我们应用的请求周围的具有安全功 能的三明治。(这个比喻够新鲜,不过Spring Security的核心确实就是一系列介于真正请求之间的过滤器,译 者注)。 Spring Security使用了o.s.web.filter.DelegatingFilterProxy这个servlet过滤器来包装所有的应用请求, 从而保证它们是安全的。 【DelegatingFilterProxy实际上是Spring框架提供的,而不是安全特有的。这个过滤器一般在Spring构建的 web应用工程中使用,并将依赖于servlet过滤器的Spring Bean与Servle过滤器的生命周期结合起来。】 通过在web.xml部署描述文件中添加如下的代码,就可以配置这样一个过滤器。这段代码位于Spring MVC的之后: springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain http://lengyun3566.iteye.com 1.5 《Spring Security3》第二章第二部分翻译 第 20 / 350 页 /* 我们所做的是使用一个ServletRequest过滤器并将它配置成处理匹配给定URL模式(/*)的请求。因为我们配 置的这个通配符模式匹配所有的URL,所以这个过滤器将会应用于每个请求。 如果你有心的话,可能会发现这与我们的Spring Security配置文件即dogstore-security.xml没有任何的 关系。为了实现这个,我们需要添加一个XML配置文件的应用到web应用的部署描述文件web.xml中。 添加Spring Security XML配置文件的应用到web.xml 取决于你如何配置你的Spring web应用,不知你是否已经在web.xml中有了对XML 配置文件的明确引 用。Spring web的ContextLoaderListener的默认行为是寻找与你的Spring web servlet同名的XML配置文 件。让我们看一下如何添加这个新的Spring Security XML配置文件到已经存在的JBCP Pet商店站点中。 首先,我们需要看一下这个应用是否使用了Spring MVC的自动查找XML配置文件的功能。我们看一下在 web.xml中servlet的名字,以原始进行标识: dogstore org.springframework.web.servlet.DispatcherServlet 1 Servlet的名字是()是dogstore,所以Spring的约定胜于配置(Convention over Configuration,CoC)将会在WEB-INF目录下寻找名为dogstore-servelt.xml的配置文件。我们没有覆 盖这种默认行为,你能在WEB-INF目录下找到这个文件,它包含了一些Spring MVC相关的配置。 【很多Spring Web Flow和Spring MVC的用户并不明白这些CoC规则是如何生效的以及Spring的代码中在何 处声明了这些规则。o.s.web.context.support.XmlWebApplicationContext和它的父类是了解这些的一个很好 的起点。JavaDoc在讲解基于Spring MVC框架的web工程的一些参数配置方面也做得不错。】 也可以声明额外的Spring ApplicationContext配置文件,它将会先于Spring MVC servle关联的配置文 件加载。这通过Spring的o.s.web.context.ContextLoaderListener创建一个独立于Spring MVC ApplicationContext的另一个ApplicationContext来实现。这是通常的非Spring MVC beans配置的方式,也 为Spring Security相关的配置提供了一个好地方。 http://lengyun3566.iteye.com 1.5 《Spring Security3》第二章第二部分翻译 第 21 / 350 页 在web应用的部署描述文件中,用来配置ContextLoaderListener的XML文件地址是在元素中给出的: contextConfigLocation /WEB-INF/dogstore-base.xml dogstore-base.xml文件中包含了一些标准的Spring bean的配置,如数据源、服务层bean等。现在,我们能 够添加一个包含Spring Security的XML配置文件了,下面是更新后的元素: contextConfigLocation /WEB-INF/dogstore-security.xml /WEB-INF/dogstore-base.xml 在我们添加完新的Spring Security配置文件到web部署文件并重启web工程后,尝试访问应用的首页地址: http://localhost:8080/JBCPPets/home.do,你将会看到如下的界面: 漂亮!我们已经使用Spring Security在三步之内实现了一个简单的安全层。在这里,你可以使用guest作为用 户名和密码进行登录。接着你将能够看到JBCP Pets应用的一个简单首页。 为了简便起见,本章的源码只包含了全部JBCP Pets整个应用的很小一部分(只有一个页面)。我们这么做 是为了更简洁,同时也能让构建和部署应用时不必考虑我们将在后续章节中涉及到的附加功能。这也提供了一 个很好的起点让你快速的体验参数的配置并重新部署以查看它们是否生效。 注意这些不足之处 到此为止,思考一下我们所做的事情。你可能已经意识到在产品化之前应用存在很多很明显的问题,这需 要很多后续的工作和对Spring Security产品的了解。尝试列举一下在将这个实现安全功能的站点上线前还需要 什么样的完善。 http://lengyun3566.iteye.com 1.5 《Spring Security3》第二章第二部分翻译 第 22 / 350 页 实现Spring Security起点级别的配置是相当迅速的,它已经提供了一个登录界面,用户名密码认证以及自 动拦截我们在线商店的URL。但是在自动配置给我们提供的功能与我们的最终目标之间有很大的差距: l 我们将用户名、密码以及角色信息硬编码到XML配置文件中。你是否 还记得我们添加的这部分XML内容: 你可以看到用户名和密码在这个文件中。你不可能愿意为每一个系统的用户都在这个XML文件中添加一个声 明。要解决这个问题,你需要使用一个基于数据库的认证提供者(authentication provider)来替代它(我们 将第四章:凭证安全存储完成它)。 l 我们对所有的页面都进行了安全控制,包括一些潜在客户可能想匿名 访问的界面。我们需要完善系统的角色以适应匿名的、认证过的以及管理权限的用户(这也将在第四章中讨 论)。 l 登录界面非常应用,但是它太基础了而且与我们JBCP商店风格一点 也不一致。我们需要添加一个登录的form界面,并与我们应用的外观和风格一致(我们将在下一章解决这个问 题)。 常见问题 很多用户在初次使用Spring Security时会遇到一些问题。我们列出了几个常见的问题和建议。我们希望你 能够一直跟随着本书的讲解,运行我们示例代码。 l 确保你的应用在添加Spring Security之前是可以编译和部署的。必 要的时候看一些你所使用的servlet容器的入门级例子和文档。 l 通常使用一个IDE如Eclipse会极大地简化你使用的servlet容器。不仅 能够保证部署准确无误,它所提供的控制台日志也很易读可用来检查错误。你还可以在关键的位置添加断点, 运行的时候会触发从而简化分析错误的过程。 l 如果你的XML配置文件不正确,你会得到这样的提示信息(或类似 的):org.xml.sax.SAXParseException: cvc-elt.1: Cannot find the declaration of element 'beans'。为实 http://lengyun3566.iteye.com 1.5 《Spring Security3》第二章第二部分翻译 第 23 / 350 页 现Spring Security的正确配置需要各种各样的XML命名空间引用,这可能会使很多用户感到迷惑。重新检查一 下这个例子,仔细查看一下schame声明的部分,并使用XML校验器来保证你没有不合法的XML。 l 确保你所使用的Spring和Spring Security版本匹配,并确保没有你 的应用中不存在没用的Spring jar包。 http://lengyun3566.iteye.com 1.5 《Spring Security3》第二章第二部分翻译 第 24 / 350 页 1.6 《Spring Security3》第二章第三部分翻译(上) 发表时间: 2011-06-24 关键字: Spring, Security, Servlet, Web, 应用服务器 安全的复杂之处:安全web请求的架构 借助于Spring Security的强大基础配置功能以及内置的认证功能,我们在前面讲述的三步配置是很快就能 完成的;它们的使用是通过添加auto-config属性和http元素实现的。 但不幸的是,应用实现的考量、架构的限制以及基础设施集成的要求可能使你的Spring Security实现远较 这个简单的配置所提供的复杂。很多用户一使用比基本配置复杂的功能就会遇到麻烦,那是因为他们不了解这 个产品的架构以及各个元素是如何协同工作以实现一个整体的。 理解web请求的整体流程以及它们是如何穿越实现功能的拦截器链,对我们成功了解Spring Security的高 级话题至关重要。记住认证和授权的基本概念,因为它们贯穿我们要保护的系统架构的始终。 请求是怎样被处理的? Spring Security的架构在很大程度上依赖代理(delegates)和servlet过滤器,来实现环绕在web应用请 求前后的功能层。 Servlet过滤器(Servlet Filter,实现javax.servlet.Filter接口的类)被用来拦截用户请求来进行请求之前 或之后的处理,或者干脆重定向这个请求,这取决于servlet过滤器的功能。在JBCP Pets在线商店中,最终的目 标servlet是Spring MVC 分发servlet,但是在理论上它可能是任何一个web servlet。下面的图描述了一个 servlet过滤器是如何封装一个用户请求的: Spring Security在XML配置文件中的自动配置属性,建立了十个servlet过滤器,它们通过使用Java EE的 servlet过滤器链按顺序组合起来。Filter chain是Java EE Servlet API的一个概念,通过接口 javax.servlet.FilterChain进行定义,它允许在web应用中的一系列的servlet过滤器能够应用于任何给定的请 求。 与生活中金属制定的链类似,每一个servelt过滤器代表链上的一环,它会进行方法的调用以处理用户的请 求。请求穿过整个过滤器链,按顺序调用每个过滤器。 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 25 / 350 页 正如你能从链这个词汇中推断出的那样,servlet请求按照一定的顺序从一个过滤器到下一个穿过整个过滤器 链,最终到达目标servlet。与之相对的是,当servelt处理完请求并返回一个response时,过滤器链按照相反的 顺序再次穿过所有的过滤器。 Spring Security使用了过滤器链的概念并实现了自己抽象,提供了VirtualFilterChain,它可以根据 Spring Security XML配置文件中设置的URL模式动态的创建过滤器链(可以将它与标准的Java EE过滤器链进 行对比,后者需要在web应用的部署描述文件中进行设置)。 【Servlet过滤器除了能够如它的名字所描述的那样进行过滤功能(或阻止请求)以外,还可以用于很多其他的 目的。实际上,很多的servlet过滤器的功能类似于在web运行的环境中对请求进行AOP式的代理拦截,因为它 们可以允许一些功能在任何发往servelt容器的请求处理之前或之后执行。过滤器能实现的多功能在Spring Security中页得到了体现,因为很多过滤器实际上并不直接影响用户的请求。】 自动配置的选项为你建立了十个Spring Security的过滤器。理解这些过滤器的默认行为以及它们在哪里以及如 何配置的,对使用Spring Security的高级功能至关重要。 这些过滤器以及它们使用的顺序,在下面的表格中进行了描述。大多数这些过滤器在我们完善JBCP Pets在线商 店的过程中都会被再次提到,所以如果你现在不明白它们的确切功能也不必担心。 过滤器名称 描述 o.s.s.web.context.SecurityContextPersistenceFilter 负责从SecurityContextRepository获取或存储 SecurityContext。SecurityContext代表了用户安全和认证过 的session。 o.s.s.web.authentication.logout.LogoutFilter 监控一个实际为退出功能的URL(默认为 /j_spring_security_logout),并且在匹配的时候完成用户的 退出功能。 o.s.s.web.authentication.UsernamePasswordAuthenticationFilter 监控一个使用用户名和密码基于form认证的URL(默认为 /j_spring_security_check),并在URL匹配的情况下尝试认证 该用户。 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 26 / 350 页 o.s.s.web.authentication.ui.DefaultLoginPageGeneratingFilter 监控一个要进行基于forn或OpenID认证的URL(默认为 /spring_security_login),并生成展现登录form的HTML o.s.s.web.authentication.www.BasicAuthenticationFilter 监控HTTP 基础认证的头信息并进行处理 o.s.s.web.savedrequest. RequestCacheAwareFilter 用于用户登录成功后,重新恢复因为登录被打断的请求。 o.s.s.web.servletapi. SecurityContextHolderAwareRequest Filter 用一个扩展了HttpServletRequestWrapper的子类 (o.s.s.web. servletapi.SecurityContextHolderAwareRequestWrapper) 包装HttpServletRequest。它为请求处理器提供了额外的上下 文信息。 o.s.s.web.authentication. AnonymousAuthenticationFilter 如果用户到这一步还没有经过认证,将会为这个请求关联一个 认证的token,标识此用户是匿名的。 o.s.s.web.session. SessionManagementFilter 根据认证的安全实体信息跟踪session,保证所有关联一个安全 实体的session都能被跟踪到。 o.s.s.web.access. ExceptionTranslationFilter 解决在处理一个请求时产生的指定异常 o.s.s.web.access.intercept. FilterSecurityInterceptor 简化授权和访问控制决定,委托一个AccessDecisionManager 完成授权的判断 Spring Security拥有总共大约25个过滤器,它们能够根据你的需要进行适当的应用以改变用户请求的行为。当 然,如果需要的话,你也可以添加你自己实现了javax.servlet.Filter接口的过滤器。 请记住,如果你在XML配置文件中使用了auto-config属性,以上表格中列出的过滤器自动添加的。通过使用一 些额外的配置指令,以上列表中的过滤器能够精确的控制是否被包含,在后续的章节章将会进行介绍。 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 27 / 350 页 你可能会完全从头做起来配置过滤器链。尽管这会很单调乏味,因为有很多的依赖关系要配置,但是它为配置 和应用场景的匹配方面提供了更高层次的灵活性。我们将在第六章讲述在启动的过程中所依赖的Spring Bean的 声明。 你可能想知道DelegatingFilterProxy是怎样找到Spring Security配置的过滤器链的。让我们回忆一下,在 web.xml文件中,我们需要给DelegatingFilterProxy一个过滤器的名字: springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy 这个过滤器的名字并不是随意配置的,实际上会跟根据这个名字把Spring Security织入到 DelegatingFilterProxy。除非明确配置,否则DelegatingFilterProxy会在Spring WebApplicationContext中 寻找同名的配置bean(名字是在filter-name中指明的)。更多配置DelegatingFilterProxy的细节可以在这个 类对应的Javadoc中找到。 在auto-config场景下,发生了什么事情? 在Spring Security 3中,使用auto-config会自动提供以下三个认证相关的功能: l HTTP基本认证 l Form登录认证 l 退出 值得注意的是,也可以使用配置元素实现这三个功能,能够实现比使用auto-config提供的功能更精确。我们将 在随后的章节中看到它们的使用以提供更高级的功能。 【auto-config和以前不一样了!在Spring Security3之前的版本中,auto-config属性提供了比现在更多的启 动项。在Spring Security2中通过auto-config配置的功能,现在可以使用security命名空间样式的配置很容易 的实现。请参考第13章:迁移至Spring Security 3来获取更多从Spring Security2迁移到3的详细信息。】 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 28 / 350 页 除了以上认证相关的功能,其它过滤器链的配置是通过使用元素来实现的。 用户是怎样认证的? 在我们的安全系统中,当一个用户在我们的登录form中提供凭证后,这些凭证信息必须与凭证存储中的数据进 行校验以确定下一步的行为。凭证的校验涉及到一系列的逻辑组件,它们封装了认证过程。 我们将会深入讲解我们例子中的用户名和密码登录form,与之对应的接口和实现都是特定于用户名和密码认证 的。但是,请记住,整体的认证是相同的,不管你是使用基于form的登录请求,或者使用一个外部的认证提供 者如集中认证服务(CAS),抑或用户的凭证信息存在一个数据库或在一个LDAP目录中。在本书的第二部分, 我们将会看到在基于form登录中学到的概念是如何应用到更高级的认证机制中。 涉及到认证功能的重要接口在下边的图标中有一个概览性的描述: 站在一个较高层次上看,你可以看到有三个主要的组件负责这项重要的事情: 接口名 描述/角色 AbstractAuthenticationProcessingFilter 它在基于web的认证请求中使用。处理 包含认证信息的请求,如认证信息可能 是form POST提交的、SSO信息或者其 他用户提供的。创建一个部分完整的 Authentication对象以在链中传递凭证 信息。 AuthenticationManager 它用来校验用户的凭证信息,或者会抛 出一个特定的异常(校验失败的情况) 或者完整填充Authentication对象,将 会包含了权限信息。 AuthenticationProvider 它为AuthenticationManager提供凭证 校验。一些AuthenticationProvider的 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 29 / 350 页 实现基于凭证信息的存储,如数据库, 来判定凭证信息是否可以被认可。 有两个重要接口的实现是在认证链中被这些参与的类初始化的,它们用来封装一个认证过(或还没有认证过 的)的用户的详细信息和权限。 o.s.s.core.Authentication是你以后要经常接触到的接口,因为它存储了用户的详细信息,包括唯一标识(如用 户名)、凭证信息(如密码)以及本用户被授予的一个或多个权限(o.s.s.core. GrantedAuthority)。开发人员通常会使用Authentication对象来获取用户的详细信息,或者使用自定义的认 证实现以便在Authentication对象中增加应用依赖的额外信息。 以下列出了Authentication接口可以实现的方法: 方法签名 描述 Object getPrincipal() 返回安全实体的唯一标识(如,一个用 户名) Object getCredentials() 返回安全实体的凭证信息 List getAuthorities() 得到安全实体的权限集合,根据认证信 息的存储决定的。 Object getDetails() 返回一个跟认证提供者相关的安全实体 细节信息 你可能会担心的发现,Authentication接口有好几个方法的返回值是简单的java.lang.Object。这可能会导致在 编译阶段很难知道调用Authentication对象的方法返回值是什么类型的对象。 需要注意的一点是AuthenticationProvider并不是直接被AuthenticationManager接口使用或引用的。但是 Spring Security只提供了AuthenticationManager的一个具体实现类,即 o.s.s.authentication.ProviderManager,它会使用一个或更多以上描述的AuthenticationProvider实现类。因 为AuthenticationProvider的使用非常普遍并且被很好的集成在ProviderManager中,所以理解它在最常见的 基本配置下是如何工作的就非常重要了。 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 30 / 350 页 让我们更仔细的看看在基于web用户名和密码认证的请求下,这些类的处理过程: 让我们看一下在较高层次示意图中反映出的抽象工作流程,并将其细化到这个基于表单认证的具体实现。你可以看到 UsernamePasswordAuthenticationFilter负责(通过代理从它的抽象父类中)创建UsernamePasswordAuthenticationToken对象 (Authentication接口的一个实现),并部分填充这个对象依赖的信息,这些信息来自HttpServletRequet。但是它是从哪里获取用户名和 密码的呢? spring_security_login是什么?我们怎么到达这个界面的? 你可能已经发现,当你试图访问我们JBCP Pets商店的主页时,你被重定向到http://localhost:8080/ JBCPPets/spring_security_login: URL的spring_security_login部分表明这是一个默认的登录的页面并且是在 DefaultLoginPageGeneratingFilter中命名的。我们可以使用配置属性来修改这个页面的名字从而使得它对于 我们应用来说是唯一的。 【建议修改登录页URL的默认值。修改后不仅能够对应用或搜索引擎更友好,而且能够隐藏你使用Spring Security作为安全实现的事实。通过这种方式来掩盖Spring Security能够使得万一Spring Security被发现存在 安全漏洞时,恶意黑客寻找你应用漏洞的难度。尽管通过这种方式的安全掩盖不会降低你应用的脆弱性,但是 它确实能够使得一些传统的黑客工具很难确定你的应用能够承担的住什么类型的攻击。需要注意的是,这里并 不是“spring”名称在URL中出现的唯一地方。我们将在后面的章节详细阐述。】 让我们看一下这个form的HTML源码(忽略布局信息),来看一下UsernamePasswordAuthenticationFilter 期望得到的信息: 你可以看到用户名和密码对应的form文本域有独特的名字((j_username和j_password),并且form的action 地址j_spring_security_check也并不是我们配置的。它们是怎么来的呢? 文本域的名字是UsernamePasswordAuthenticationFilter规定的,并借鉴了Java EE Servlet 2.x的规范(在 SRV.12.5.3章节中),规范要求登录的form使用特定的名字并且form的action要为特定的j_security_check 值。这样的实际模式目标是允许基于Java EE servlet-based的应用能够与servlet容器的安全设施以标准的方式 连接起来。 因为我们的应用没有使用到servlet容器的安全组件,所以可以明确设置 UsernamePasswordAuthenticationFilter以使得文本域有不同的名字。这种特定的配置变化可能会比你想象的 复杂。现在,我们将要回顾一下UsernamePasswordAuthenticationFilter的生命周期,看一下它是如何进入我 们配置的(尽管我们将会在第六章再次讲述这个配置)。 UsernamePasswordAuthenticationFilter是通过配置指令的子元素来进行配置的。正 如在本章前面讲述的,我们设置的auto-config元素将会在你没有明确添加的情况下包含了功 能。正如你可能猜测的那样,j_spring_security_check并不对应任何应用中的物理资源。它只是 UsernamePasswordAuthenticationFilter监视的一个基于form登录的URL。实际上,在Spring Security中有 好几个这样的特殊的URL来实现特定的全局功能。你能在附录:参考资料中找到这些URL的一个列表。 用户的凭证信息是在哪里被校验的? 在我们的简单的三步配置文件中,我们使用了一个基于内存的凭证存储实现快速的部署和运行: http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 32 / 350 页 我们没有将AuthenticationProvider与任何具体的实现相关联,在这里我们再次看到了security命名空间默认为 我们做了许多机械的配置工作。但是需要记住的是AuthenticationManager支持配置一个或多个 AuthenticationProvider。声明默认谁实例化一个内置的实现,即 o.s.s.authentication.dao.DaoAuthenticationProvider。声明会自动的将这个 AuthenticationProvider对象织入到配置的AuthenticationManager中,当然在我们这个场景中 AuthenticationManager是自动配置的。 DaoAuthenticationProvider是AuthenticationProvider的简单封装实现并委托 o.s.s.core.userdetails.UserDetailsService接口的实现类进行处理。UserDetailsService负责返回 o.s.s.core.userdetails.UserDetails的一个实现类。 如果你查看UserDetails的Javadoc,你会发现它与我们前面讨论的Authentication接口非常类似。尽管它们在 方法名和功能上有些重叠的部分,但是请不要混淆,它们有着截然不同的目的: 接口 目的 Authentication 它存储安全实体的标识、密码以及认证 请求的上下文信息。它还包含用户认证 后的信息(可能会包含一个UserDetails 的实例)。通常不会被扩展,除非是为 了支持某种特定类型的认证。 UserDetails 为了存储一个安全实体的概况信息,包 含名字、e-mail、电话号码等。通常会 被扩展以支持业务需求。 我们对子元素的声明将会触发对o.s.s.core.userdetails.memory.InMemoryDaoImpl的配置, 它是UserDetailsService的一个实现。正如你可能期望的那样,这个实现将在安全XML文件中配置的用户信息 放在一个内存的数据存储中。这个service的实现支持其它属性的设置,从而实现账户的禁用和锁定。 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 33 / 350 页 让我们更直观的看一下DaoAuthenticationProvider是如何交互的,从而AuthenticationManager提供认证支 持: 正如你可能想象的那样,认证是相当可配置化的。大多数的Spring Security例子要么使用基于内存的用户凭证 存储要么使用JDBC(在数据库中)的用户凭证存储。我们已经意识到修改JBCP Pets应用以实现数据库存储用 户凭证是一个好主意,我们将会在第四章来处理这个配置变化。 什么时候校验不通过? Spring Security很好的使用应用级异常(expected exceptions)来表示处理各种的结果情况。你可能在使用 Spring Security的日常工作中不会与这些异常打交道,但是了解它们以及它们为何被抛出将会在调试问题或理 解应用流程中非常有用。 所有认证相关的异常都继承自o.s.s.core.AuthenticationException基类。除了支持标准的异常功能, AuthenticationException包含两个域,可能在提供调试失败信息以及报告信息给用户方面很有用处。 l authentication:存储关联认证请求的Authentication实例; l extraInformation:根据特定的异常可以存储额外的信息。如 UsernameNotFoundException在这个域上存储了用户名。 我们在下面的表格中,列出了常见的异常。完整的认证异常列表可以在附录:参考资料中找到: 异常类 何时抛出 extraInformation内容 BadCredentialsException 如何没有提供用户名或者 密码与认证存储中用户名 对应的密码不匹配 UserDetails LockedException 如果用户的账号被发现锁 定了 UserDetails UsernameNotFoundException 如果用户名不存在或者用 户没有被授予的 GrantedAuthority String(包含用户名) http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 34 / 350 页 这些以及其他的异常将会传递到过滤器链上,通常将会被request请求的过滤器捕获并处理,要么将用户重定向 到一个合适的界面(登录或访问拒绝),要么返回一个特殊的HTTP状态码,如HTTP 403(访问被拒绝)。 http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 35 / 350 页 1.7 《Spring Security3》第二章第三部分翻译(中) 发表时间: 2011-06-24 关键字: Spring, Access, Bean, Security, 配置管理 请求是怎样被授权的? 在Spring Security的默认过滤器链中,最后一个servelt过滤器是FilterSecurityInterceptor,它的作用是判断一 个特定的请求是被允许还是被拒绝。在FilterSecurityInterceptor被触发的时候,安全实体已经经过了认证,所 以系统知道他们是合法的用户。(其实也有可能是匿名的用户,译者注)。请记住的一点是,Authentication 提供了一个方法((List getAuthorities()),将会返回当前安全实体的一系列权限列表。授权的过程将使用这个方法提供的信息来决定 一个特定的请求是否会被允许。 需要记住的是授权是一个二进制的决策——一个用户要么有要么没有访问一个受保护资源的权限。在授权中, 没有模棱两可的情景。 在Spring Security中,良好的面向对象设计随处可见,在授权决策管理中也不例外。回忆一下我们在本章前面 的讨论,一个名为访问控制决策器(access decision manager)的组件负责作出授权决策。 在Spring Security中,o.s.s.access.AccessDecisionManager接口定义了两个简单而合理的方法,它们能够用 于请求的决策判断流程: l supports:这个逻辑操作实际上包含两个方法,它们允许 AccessDecisionManager的实现类判断是否支持当前的请求。 l decide:基于请求的上下文和安全配置,允许 AccessDecisionManager去核实访问是否被允许以及请求是否能够被接受。decide方法实际上没有返回值,通 过抛出异常来表明对请求访问的拒绝。 与AuthenticationException及其子类在认证过程中的使用很类似,特定类型的异常能够表明应用在授权决策中 的不同处理结果。o.s.s.access.AccessDeniedException是在授权领域里最常见的异常,因此值得过滤器链进行 特殊的处理。我们将在第六章中详细介绍它的高级配置。 AccessDecisionManager是能够通过标准的Spring bean绑定和引用实现完全的自定义配置。 AccessDecisionManager的默认实现提供了一个基于AccessDecisionVoter接口和投票集合的授权机制。 http://lengyun3566.iteye.com 1.7 《Spring Security3》第二章第三部分翻译(中) 第 36 / 350 页 投票器(voter)是在授权过程中的一个重要角色,它的作用是评估以下的内容: l 要访问受保护资源的请求所对应上下文(如URL请求的IP地址); l 用户的凭证信息(如果存在的话); l 要试图访问的受保护资源; l 系统的配置以及要访问资源本身的配置参数。 AccessDecisionManager还会负责传递要请求资源的访问声明信息(在代码中为ConfigAttribute接口的实现 类)给投票器。在web URL的请求中,投票器将会得到资源的访问声明信息。如果看一下我们配置文件中非常 基础的拦截声明,我们能够看到ROLE_USER被设置为访问配置并用于用户试图访问的资源: 投票器将会对用户是否能够访问指定的资源做出一个判断。Spring Security允许过滤器在三种决策结果中做出 一种选择,它们的逻辑定义在o.s.s.access.AccessDecisionVoter接口中通过常量进行了定义。 决策类型 描述 Grant (ACCESS_GRANTED) 投票器允许对资源的访问 Deny (ACCESS_DENIED) 投票器拒绝对资源的访问 Abstain (ACCESS_ABSTAIN) 投票器对是否能够访问做了弃权处理 (即没有做出决定)。可能在多种原因 下发生,如: l l l l l 如果此时重启应用,你将会发现: l 现在访问登录页和账号页需要HTTPS,浏览器将会为用户自动从不安全的(HTTP)URL重定向到安全的URL。例如,尝试访问 http://localhost:8080/JBCPPets/login.do将会被定向到https://localhost:8443/JBCPPets/login.do; l 如果用户被切换到了安全的HTTPS URL,如果他访问一个不必要使用HTTPS的URL,他能继续保留在HTTPS状态。 我们可以想象这种配置对于安全的好处——大多数的现代应用服务器使用一个secure标识session的cookie,所以强制要求登录页 是安全的(如果这是应用的session被首次分配的地方)能够保证session的cookie能够被安全的传输,所以出现session劫持的可能性也 更小。另外,直接将SSL加密配置在安全声明上的做法,能够很容易的保证应用中所有敏感的页面被适当和完整的保护。 为用户自动切换适当协议(HTTP或HTTPS)的功能,通过Spring Security过滤器链上的另外一个servlet过滤器来实现的(它的位 置很靠前,在SecurityContextPersistenceFilter后面)。如果任何URL用requires-channel属性声明使用特定类型的协议, o.s.s.web.access.channel.ChannelProcessingFilter将会自动添加到过滤器链上 ChannelProcessingFilter在请求时的交互过程如下图所示: 如果你的应用需要超出内置功能的复杂逻辑,ChannelProcessingFilter的设计可以进行扩展和增强。注意我们尽管只在图中说明了 SecureChannelProcessor和RetryWithHttpsEntryPoint的实现,但是有类似的类去校验和处理声明为要求HTTP的URL。 http://lengyun3566.iteye.com 1.19 《Spring Security3》第四章第四部分翻译(Remember me后台存储和 SSL)附前四章doc文件 第 108 / 350 页 注意,ChannelEntryPoint使用了HTTP 302的URL重写,这就不能使用这种技术去重定向POST的URL(尽管典型的POST请求不 应该在安全协议和不安全协议间传递,因为大多数的应用都会对这种行为提出警告)。 安全的端口映射 在一些特定的环境中,可能不会使用标准的HTTP和HTTPS端口,其默认为80/443或8080/8443。在这种情况下,你必须配置你的 应用包含明确的端口映射,这样ChannelEntryPoint的实现能够确定当重定向用户到安全或不安全的URL时,使用什么端口。 这仅需要增加额外的配置元素,它能够指明除了默认的端口以外,额外的HTTP 的HTTPS端口: 如果你的应用服务器在反向代理后的话,端口映射将会更加的重要。 小结 在本章中,我们: l 介绍了把安全数据存储在支持JDBC的数据库中是如何配置的; l 配置JBCP Pets使用数据库来进行用户认证以及高安全性的密码存储,这里我们使用了密码加密和salting技术; l 管理JDBC持久化到数据中的用户; l 配置用户到安全组中。组被授予角色,而不是直接对用户进行角色的指定。这提高了站点和用户功能的可管理性; l 介绍了Spring Security使用遗留的(非默认的)数据库schema; l 讲解了HTTPS技术的配置及应用,它能够提高数据在访问应用敏感内容时的安全性。 在接下来的章节中,我们将会介绍Spring Security一些高级的授权功能,并引入Spring Security的JSP标签以实现良好的授权。 附件下载: • Spring_Security3.zip (1.5 MB) • dl.iteye.com/topics/download/ecbe1861-5607-34ef-92b6-566261d3668d http://lengyun3566.iteye.com 1.19 《Spring Security3》第四章第四部分翻译(Remember me后台存储和 SSL)附前四章doc文件 第 109 / 350 页 1.20 《Spring Security3》第五章第一部分翻译(重新思考应用功能和安全) 发表时间: 2011-08-25 第五章 精确的访问控制 到目前为止,我们已经为JBCP Pets站点添加了用户友好的一些功能,包括自定义的登录页以及修改密码、 remember me功能。 在本章中,我们将要学习规划应用安全的技术以及用户/组的划分。其次,我们学习两种实现精确访问控制 的实现方式——这会影响应用中页面的授权。然后,我们会了解Spring Security如何通过使用方法注解和AOP 的方式来实现业务层安全。最后,我们将会了解通过基于注解的配置实现按照角色过滤集合数据这一比较有趣 的功能。 在本章中,我们会学到: l 规划web应用安全的基本技术和组管理,这会使用到现成的工具和批判思考(critical thinking); l 基于用户请求的上下文,配置和实验在页面级别进行授权检查以显示内容的不同方式; l 通过配置和代码注解pre-authorization的方式使得调用应用中关键部分是安全的; l 几种实现方法级别安全的可选方式,并介绍各种方式的优劣; l 通过使用方法级别的注解,实现基于Collections和Arrays数据的过滤器。 因为本章涉及到的概念超过了前面的一些孤立技术点,为了扩大站点的范围在源代码上做了一定数量的 修改,并将其分成了真正三层的系统。你可能对这些变化感兴趣,但是它们与Spring Security没有直接的 关系,所以我们将会忽略这些修改的细节。当你发现本章的源代码总添加了许多的文件,不要被吓倒。 http://lengyun3566.iteye.com 1.20 《Spring Security3》第五章第一部分翻译(重新思考应用功能和安全) 第 110 / 350 页 重新思考应用功能和安全 现在,我们要再看一下JBCP Pets应用的授权模型和流程。我们感觉已经得到了一个很安全的应用,但是 应用的流程并不特别适合与公开的电子商务站点。我们还需要做很多的事情,因为对应用中每个页面(除去登 录界面)的请求,都需要用户有一个合法的账号并登录——这无助于用户的浏览和购买。 规划应用安全 通常情况下,需要产品管理领域的人员和安全专员联合工程师来评估用户社区和需求的功能。规划过程 ——如果能够高效执行——使用工作表和图表来彻底分析应用包含的角色和组。我们会花一点时间简单介绍对 JBCP Pets的扩展功能来阐明这个过程是如何进行的。在任何项目中对安全规划的思考过程将会对开发过程很有 好处——尝试对你应用中的每个页面和业务服务构建安全状况。 规划用户角色 对于JBCP Pets,我们将会使用下边的表格匹配用户分类到角色(Spring Security的GrantedAuthority 值)中。它们中有一些是新的角色,用来对用户进行不同的分类。 用户分类 描述 角色 Guest 不是记住或认证过的用户 None (anonymous) Consumer / Customer 用户已经建立的账号,在 站点上可能已完成也可能 未完成购买交易 ROLE_CUSTOMER ROLE_USER Customer w/ Completed Purchase 用户至少在站点上完成了 一笔交易 ROLE_PURCHASER ROLE_USER Administrator 负责用户账号管理等功能 的管理员 ROLE_ADMIN ROLE_USER Supplier 产品供货商,允许管理其 产品目录 ROLE_SUPPLIER ROLE_USER http://lengyun3566.iteye.com 1.20 《Spring Security3》第五章第一部分翻译(重新思考应用功能和安全) 第 111 / 350 页 使用这些声明的用户分类和角色,我们能够将角色粗略得匹配到站点的功能设计上。有很多方式能够完成这 项任务——以下是我们在过去发现很有用的办法: l 使用Microsoft Visio和韦恩图来标示功能和用户组的重叠交叉(我们在第二章:Spring Security起步中曾经 使用过其很有限的功能)。这种技术对小型的应用和粗略的分析能够非常直观。 l 个性化的图表页面,并注明能够访问每个页面的用户分类和角色。尽管不能直接可视化的访问,但这种方式能 够非常精确。我们将会在下面的章节阐述一个这样的例子。 l 使用便条和白板或草图板建模。在这种类型的练习中,产品的规划人员在白板上勾画出一些区域来代表不同的 用户角色,并在白板的每个区域上添加便签以代表产品功能。 通常来说,使用非数字的方式进行安全的初期规划是很容易的,因为经常见到组内讨论会产生对安全功能的 较大修正,而使用非数字的工具很容易进行调整。典型情况下,这种层次的安全规划不会涉及到单个页面的和 页面中某些部分的层次,而是应用中的功能“块”(即应用整体上的安全功能规划——译者注)。 规划页面级权限 下一层次的安全规划就是页面级元素的安全。首先,规划整个站点范围的页面特性是很重要的,这能够保 证用户在使用可见功能并切换页面时,保证界面的一致性。大多数的站点已经有了整个站点层次的模板功能, 最简单的就是jsp:include指令(正如我们在JBCP Pets中使用的),或者更复杂的,如Apache Tiles 2。 页面级别的安全规划通常与站点的用户体验规划结合起来——很多公司使用Microsoft Visio或者Adobe Dreamweaver进行站点的低保真设计,或者使用更复杂的工具如Axure RP。不管使用什么工具,需要保证的 是在规划安全相关的功能时要与站点的最初设计功能相融合。你的UI设计师或界面设计师可能会愿意讨论基于 用户的角色,那些元素会显示或不显示。理解页面元素的可能选项能够使得好的UI规划人员设计出合理灵活的 布局,从而保证不管用户是什么样的权限,页面都能展现得很好。 【使用正确的工作工具。我有一个UI设计师朋友,他为Visio做了很棒的形状集合,这使我产生了很大的 兴趣,这些通过http://www.guuui.com/issues/02_07.ph 可以得到。对于熟悉Visio的人来说,这是一种不错 的方式来开发精确、低保真的模型,对于这个工具许多人已经很了解。尽管现在没有兼容Visio的开源产品,类 似的应用如Dia (http://projects.gnome.org/dia/)或OpenOffice Draw (http://www.openoffice.org/ product/draw.html)对大多数的平台都是支持的。】 一个注明了安全信息的Visio图可能会如下所示: http://lengyun3566.iteye.com 1.20 《Spring Security3》第五章第一部分翻译(重新思考应用功能和安全) 第 112 / 350 页 可以看到我们并不需要很多安全相关信息的细节,但是这个图所表达的意思对于查看的每个人(即使不是技术 人员)都很容易理解。 http://lengyun3566.iteye.com 1.20 《Spring Security3》第五章第一部分翻译(重新思考应用功能和安全) 第 113 / 350 页 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法 ——页面级权限) 发表时间: 2011-09-11 关键字: Spring Security, java EE, 翻译 实现授权精确控制的方法 精确的授权指的是基于用户特定的请求进行授权的应用功能特性。不同于我们在第二章:Spring Security起 步、第三章增强用户体验和第四章凭证安全存储中的粗粒度的授权,精确的授权一般指的是对页面中的部分进 行选择性显示的功能,而不是限制访问一个完整的页面。现实世界中的应用将会花费可观的时间用在规划精确 授权的细节上。 Spring Security为我们提供了两种方式来实现选择性显示的功能: l Spring Security的JSP标签库允许通过标准的JSP标签库语法在页面本身添加条件访问声明; l 在MVC应用的控制层,检查用户的授权从而使得控制层做出能否访问的判断并将决定的结果绑定到模型数据 提供给视图层。 这种方式依赖于标准的JSTL条件实现界面渲染和数据绑定,这种方式比Spring Security JSP标 签库复杂一些,但是,它与标准的web应用MVC逻辑设计更吻合。 在开发精确控制授权的web应用时,这两种方法都能很好的实现功能。让我们通过JBCP Pets用例来介绍没 种方法的实现。 我们希望使用安全规划的结果来保证在网站范围内的菜单栏上“退出”和“我的订单”链接只能对登录过的 或已购买的用户(分别为ROLE_USER和ROLE_CUSTOMER)显示。我们还会保证“登录”链接只对浏览站点 的未认证访客(不具备ROLE_USER的用户)可见。我们将会介绍这两种添加该功能的方式,首先从Spring Security的JSP标签库开始。 使用Spring Security的标签库有选择地渲染内容 我们在第三章中见到过,可以使用Spring Security的标签库访问存在于Authentication对象中的数据,这 里我们将会见识到标签库的一些其它强大功能。Spring Security标签库最常用的功能就是基于授权规则有条件 地渲染页面的各部分。这是通过标签来实现的,它与JSTL核心库的标签类似,在标签体中的 内容是否显示由标签属性的条件结果来确定。让我们使用标签按条件显示页面的部分。 http://lengyun3566.iteye.com 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法 ——页面级权限) 第 114 / 350 页 基于URL访问规则进行有条件渲染 Spring Security的标签库提供了按照已有的URL授权规则进行内容渲染的功能,而URL授权规则已经在应 用安全的配置文件中进行了定义。这是通过使用标签的属性来达到的。 例如,我们要保证“My Account”链接只能对实际登录站点的用户显示——回忆一下我们在前面定义的如 下访问规则: 所以,JSP中条件显示“My Account”链接的代码如下所示: My Account 这能够保证除非用户拥有足够的权限来访问指定的URL,否则tag中的内容不会显示。还可以通过HTTP方法实 现更高质量的检查,这要通过method属性来设置。 My Account (with 'url' attr) 使用url属性对代码块定义授权检查的方法是很方便的,因为它对JSP中的实际授权检查进行了抽象并将其保存 在安全配置文件中。 【注意的是,HTTP方法应该与安全声明中的一致,否则它们将不会按照你预期的进行匹配。 另外,注意URL应该是对于web应用上下文根的相对路径(如同URL访问规则一样)。】 http://lengyun3566.iteye.com 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法 ——页面级权限) 第 115 / 350 页 对于很多场景来说,使用标签能够保证只有用户允许看见的前提下,正确地渲染链接或action 相关的内容。需要记住的是,这个标签不仅能够包在一个链接外面,如果用户没有权限提交这个form的时候, 它还能包在整个form外边。 基于Spring 表达式语言进行有条件渲染 另外,可以联合使用标签和Spring表达式语言(SpEL)更灵活地显示JSP内容。 回忆一下在第二章中我们初次体验SpEL提供的强大表达式语言,Spring Security对其进行了更强的扩展, 从而能够对当前安全的请求构建表达式。如果我们对前面的例子使用SpEL进行重构的话,在标签 中限制访问“My Account”链接的代码应该如下: My Account (with 'access' attr) 对SpEL进行求值计算的代码与所定义的访问规则(假设配置了表达式)背后所使用的代 码是一样的。所以,同样的内置函数和属性在标签中都是可以通过表达式使用的。 以上的两种使用的方式都可以实现基于安全授权规则对页面显示内容进行精确控制渲染的强 大功能。 使用Spring Security2的方式进行有条件渲染 以上提到的两种使用Spring Security标签库的方法实际上是Spring Security3新增的功能,并且这也是按 照授权规则实现页面级安全的推荐方法;但是,同样是标签支持其他的操作方法,这可能会在遗 留代码中遇到,也可能在一定场景下,这样的方式能够更好的满足你的需求。 基于缺失某角色有条件显示内容 “Log In”链接应该只能对匿名的用户显示,也就是没有ROLE_USER角色的用户。标签通过 ifNotGranted属性支持这种类型的规则: http://lengyun3566.iteye.com 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法 ——页面级权限) 第 116 / 350 页 Log In 如果你现在以匿名用户试图访问站点,将会看到一个指向登录form的链接。 基于拥有列表中的某一个角色有条件显示内容 如同上一步那样,“Log Out”链接应该对拥有账号且已经登录的用户进行显示。ifAnyGranted属性在渲 染内容前,要求用户拥有几个特定角色中的任何一个。我们用“Log Out”链接的方式来展示其使用: Log Out 注意的是ifAnyGranted属性允许是以逗号分隔的角色集合来确定适当的匹配结果,用户只需要拥有角色中的任 意一个标签中的内容就会渲染。 基于拥有列表中的所有个角色有条件显示内容 最后,使用ifAllGranted属性要求用户拥有标签中定义的所有角色: My Orders 我们能够看到authorize标签的多种语法,以在不同的环境下使用。注意的是我们在前面讲到的三个属性可以组 合使用。如ifNotGranted和ifAnyGranted属性能够联合使用以提供稍微复杂的Boolean等式。 http://lengyun3566.iteye.com 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法 ——页面级权限) 第 117 / 350 页 使用JSP表达式 以上的三种页面授权方法((ifNotGranted,ifAnyGranted,ifAllGranted)支持JSP EL表达式,它将会执 行并返回授权的GrantedAuthority(角色等)。如果授权要求的列表会根据页面计算结果而变化的话,这将会 提供一定的灵活性。 http://lengyun3566.iteye.com 1.21 《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法 ——页面级权限) 第 118 / 350 页 1.22 《Spring Security3》第五章第二部分翻译下(实现授权精确控制的方法 ——页面级权限) 发表时间: 2011-09-11 关键字: Spring Security, java EE, 翻译 使用控制器逻辑进行有条件渲染内容 现在,让我们将刚刚用标签实现的例子改成用java代码的方式。为了简洁起见,我们只实现 一个例子,但实现基于控制器检查的其它例子是很简单直接的。 添加有条件显示的Log In链接 为了替代Spring Security的标签,我们假设在模型数据中有一个Boolean变量来标示是否显 示显示“Log in”链接,而这个变量可以在视图上得到。在传统的MVC模式下,视图不了解模型为何拥有这个 值,它只需要使用即可。我们使用Java Standard Tag Library (JSTL)的if标签,连同JSP EL表达式来实现有条件 显示页面的部分。 Log In 现在看一下如下的代码以了解我们如何在控制器中将数据填充到模型中。 基于用户的凭证提供模型数据 我们控制器的对象模型是符合面向对象模式的,所有的Spring MVC控制器扩展自一个简单的基类 com.packtpub.springsecurity.web.controller.BaseController。这使得我们能够将通用的代码放在 BaseController中,从而应用中所有的控制器都能够访问。首先,我们加入一个方法以从当前的request中得到 Authentication的实现类: http://lengyun3566.iteye.com 1.22 《Spring Security3》第五章第二部分翻译下(实现授权精确控制的方法 ——页面级权限) 第 119 / 350 页 protected Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } 接下类,我们添加一个方法 填充showLoginLink模型数据,此处使用Spring MVC的注解方式。 @ModelAttribute("showLoginLink") public boolean getShowLoginLink() { for (GrantedAuthority authority : getAuthentication(). getAuthorities()) { if(authority.getAuthority().equals("ROLE_USER")) { return false; } } return true; } 这个方法添加了@ModelAttribute注解,任何实现BaseController的控制器触发时,Spring MVC将会自动执 行此方法。针对authorize标签方式的其它显示/隐藏功能,我们能够很简单地使用模型数据指令重复这种设计 模式。 配置页面内授权的最好方式是什么? 与以前版本的标签库相比,Spring Security 3 标签的主要优势在于移除了很多使用中的困 扰之处。在很多场景下,使用标签的url属性隔离了授权规则变化对JSP代码的影响。可以在以下场景下使用url 属性: l标签限制显示的功能能够通过一个简单url明确的声明; l标签的内容能够明确的与URL隔离。 但是在典型的应用中,使用标签url属性的可能性会很低。现实情况下应用会比这个复杂的多,需要更多的 相关逻辑才能确定如何渲染页面的各部分。 尽管使用Spring Security标签库来声明渲染页面部分的方法很诱人,这种方式基于sping的语法以及一些其 它的方式(包括if...Granted和access方法),但是有很多情况下使用它并不是一个好主意: http://lengyun3566.iteye.com 1.22 《Spring Security3》第五章第二部分翻译下(实现授权精确控制的方法 ——页面级权限) 第 120 / 350 页 l Tag标签并不支持比角色成员更复杂的条件。例如,如果我们的应用的UserDetails实现包含了自定义的属 性,如IP过滤、地理位置等,这些情况使用标准的都不能支持。 但是,这些可以通过自定义的JSP标签或使用SpEL表达式来支持。即使如此,JSP也会直接绑定业务逻 辑,而这并不是推荐做法。 l 标签必须在每一个使用的页面中引用。这可能会导致本来通用的规则产生潜在的不一致性,而这 会在不同的物理界面文件中存在。好的面向对象系统设计建议条件规则只在一个地方存在,并在使用的地方对 其进行引用。 可以通过封装并且重用JSP页面的部分来减少这类问题的发生(我们通过使用通用的JSP头文件已经对此 进行了阐述),但是在一个复杂的应用中这个问题在所难免。 l 不能在编译阶段校验规则的正确性。编译期常量能够在典型的基于java对象系统中使用,JSP tag标签需要(典 型情况下)硬编码角色名字,而简单的拼写错误很难被察觉。 公平来讲,这样的拼写错误能够很容易地在运行应用的功能测试中发现,但是使用标准的Java组件单元 测试这样的问题更容易被发现。 我们可以看到,尽管基于JSP方式的内容有条件渲染很便利,但是也有一些明显的不足。 所有的这些问题都能够通过在控制器中使用代码推送数据到视图层来解决。另外,在代码中进行高级的授 权决定能够享受到重用、编译器检查以及适当分离模型、视图、控制器所带来的好处。 http://lengyun3566.iteye.com 1.22 《Spring Security3》第五章第二部分翻译下(实现授权精确控制的方法 ——页面级权限) 第 121 / 350 页 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 发表时间: 2011-09-11 保护业务层 到目前为止,在本书中我们的关注点都主要在JBCP Pets 应用web层面的安全。但是,在实际的安全系统 规划中,对服务方法应该给予同等的重视,因为它们能够访问系统中最重要的部分——数据。 Spring Security支持添加授权层(或者基于授权的数据处理)到应用中所有Spring管理的bean中。尽管 很多的开发人员关注层的安全,其实业务层的俄安全同等主要,因为恶意的用户可能会穿透web层,能够通过 没有UI的前端访问暴露的服务,如使用web service。 让我们查看下面的图以了解我们将要添加安全层的位置: Spring Security有两个主要技术以实现方法的安全: l 事先授权(Pre-authorization)保证在执行一个方法之前需要满足特定的要求——例如,一个用户要拥有特 定的GrantedAuthority,如ROLE_ADMIN。不能满足声明的条件将会导致方法调用失败; l事后授权(Post-authorization)保证在方法返回时,调用的安全实体满足声明的条件。这很少被使用,但是 能够在一些复杂交互的业务方法周围提供额外的安全层。 事先和事后授权在面向对象设计中提供了所谓的前置条件和后置条件(preconditions and postconditions)。前置条件和后置条件允许开发者声明运行时的检查,从而保证在一个方法执行时特定的条 件需要满足。在安全的事前授权和事后授权中,业务层的开发人员需要对特定的方法确定明确的安全信息,并 在接口或类的API声明中添加期望的运行时条件。正如你可能想象的那样,这需要大量的规划以避免不必要的影 响。 保护业务层方法的基本知识 让我们以JBCP Pets中业务层的几个方法为例阐述怎样为它们应用典型的规则。 http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 122 / 350 页 我们对JBCP Pets的基础代码进行了重新组织以实现三层的设计,作为修改的一部分我们抽象出了前面章 节已经介绍到的修改密码功能到业务层。不同于用web MVC的控制器直接访问JDBC DAO,我们选择插入一个 业务服务以提供要求的附加功能。下图对此进行了描述: 我们能够看到在例子中com.packtpub.springsecurity.service.IuserService接口代表了应用架构的业务层,而 这对我们来说,是一个合适位置来添加方法级的安全。 添加@PreAuthorize方法注解 我们第一个的设计决策就是要在业务层上添加方法安全,以保证用户在修改密码前已经作为系统的合法用 户进行了登录。这通过为业务接口方法定义添加一个简单的注解来实现,如下: public interface IUserService { @PreAuthorize("hasRole('ROLE_USER')") public void changePassword(String username, String password); } 这就是保证合法、已认证的用户才能访问修改密码功能所要做的所有事情。Spring Security将会使用运行时的 面向方面编程的切点(aspect oriented programming (AOP) pointcut)来对方法执行before advice,并在 安全要求未满足的情况下抛出AccessDeniedException异常。 让Spring Security能够使用方法注解 我们还需要在dogstore-security.xml中做一个一次性的修改,通过这个文件我们已经进行了Spring Security其他的配置。只需要在声明之前,添加下面的元素即可: http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 123 / 350 页 校验方法安全 不相信如此简单?那我们将ROLE_USER声明修改为ROLE_ADMIN。现在用用户guest(密码guest)登 录并尝试修改密码。你会在尝试修改密码时,看到如下的出错界面: 如果查看Tomcat的控制台,你可以看到很长的堆栈信息,开始是这样的: DEBUG - Could not complete request o.s.s.access.AccessDeniedException: Access is denied at o.s.s.access.vote.AffirmativeBased.decide at o.s.s.access.intercept.AbstractSecurityInterceptor.beforeInvocation ... at $Proxy12.changePassword(Unknown Source) at com.packtpub.springsecurity.web.controller.AccountController. submitChangePasswordPage 基于访问拒绝的页面以及指向changePassword方法的堆栈信息,我们可以看到用户被合理的拒绝对业 务方法的访问,因为缺少ROLE_ADMIN的GrantedAuthority。你可以测试修改密码功能对管理员用户依旧是可 以访问的。 我们只是在接口上添加了简单的声明就能够保证方法的安全,这是不是太令人兴奋了?当然,我们不会愿 意Tomcat 原生的403错误页面在我们的产品应用中出现——我们将会在第六章:高级配置与扩展讲述访问拒绝 处理时,对其进行更新。 让我们介绍一下实现方法安全的其它方式,然后进入功能的背后以了解其怎样以及为什么能够生效。 几种实现方法安全的方式 除了@PreAuthorize注解以外,还有几种其它的方式来声明在方法调用前进行授权检查的需求。我们会讲 解这些实现方法安全的不同方式,并比较它们在不同环境下的优势与不足。 http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 124 / 350 页 遵守JSR-250标准规则 JSR-250, Common Annotations for the Java Platform定义了一系列的注解,其中的一些是安全相关 的,它们意图在兼容JSR-250的环境中很方便地使用。Spring框架从Spring 2.x释放版本开始就兼容JSR-250, 包括Spring Security框架。 尽管JSR-250注解不像Spring原生的注解富有表现力,但是它们提供的注解能够兼容不同的Java EE应用 服务器实现如Glassfish,或面向服务的运行框架如Apache Tuscany。取决于你应用对轻便性的需求,你可能会 觉得牺牲代码的轻便性但减少对特定环境的要求是值得的。 要实现我们在第一个例子中的规则,我们需要作两个修改,首先在dogstore-security.xml文件中: 其次,@PreAuthorize注解需要修改成@RolesAllowed注解。正如我们可能推断出的那样,@RolesAllowed注 解并不支持SpEL表达式,所以它看起来很像我们在第二节中提到的URL授权。我们修改IuserService定义如下: @RolesAllowed("ROLE_USER") public void changePassword(String username, String password); 正如前面的练习那样,如果不相信它能工作,尝试修改ROLE_USER 为ROLE_ADMIN并进行测试。 要注意的是,也可以提供一系列允许的GrantedAuthority名字,使用Java 5标准的字符串数组注解语 法: @RolesAllowed({"ROLE_USER","ROLE_ADMIN"}) public void changePassword(String username, String password); JSR-250还有两个其它的注解:@PermitAll 和@DenyAll。它们的功能正如你所预想的,允许和禁止对 方法的任何请求。 http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 125 / 350 页 【类层次的注解。注意方法级别的安全注解也可以使用到类级别上!如果提供了方法级别的注解,将会覆盖类 级别的注解。如果业务需要在整个类上有安全策略的话,这会非常有用。要注意的是使用这个功能要有良好的 注释的编码规范,这样开发人员能够很清楚的了解类和方法的安全特性。】 我们将会在本章稍后的练习中介绍如何实现JSR-250风格的注解与Spring Security风格 的注解并存。 @Secured注解实现方法安全 Spring本身也提供一个简单的注解,类似于JSR-250 的@RolesAllowed注解。@Secured注解在功能和 语法上都与@RolesAllowed一致。唯一需要注意的不同点是要使用这些注解的话,要在元素中明确使用另外一个属性: 因为@Secured与JSR标准的@RolesAllowed注解在功能上一致,所以并没有充分的理由在新代码中使用它, 但是它能够在Spring的遗留代码中运行。 使用Aspect Oriented Programming (AOP)实现方法安全 实现方法安全的最后一项技术也可能是最强大的方法,它还有一个好处是不需要修改源代码。作为替代, 它使用面向方面的编程方式为一个方法或方法集合声明切点(pointcut),而增强(advice)会在切点匹配的 情况下进行基于角色的安全检查。AOP的声明只在Spring Security的XML配置文件中并不涉及任何的注解。 以下就是声明保护所有的service接口只有管理权限才能访问的例子: 切点表达式基于Spring AOP对AspectJ的支持。但是,Spring AspectJ AOP仅支持AspectJ切点表达式语言的 一个很小子集——可以参考Spring AOP的文档以了解其支持的表达式和其它关于Spring AOP编程的重要元 素。 注意的是,可以指明一系列的切点声明,以指向不同的角色和切点目标。以下的就是添加切点到DAO中 一个方法的例子: http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 126 / 350 页 注意在新增的切点中,我们添加了一些AspectJ的高级语法,来声明Boolean逻辑以及其它支持的切点,而参数 可以用来确定参数的类型声明。 同Spring Security其它允许一系列安全声明的地方一样,AOP风格的方法安全是按照从顶到底的顺序进 行的,所以需要按照最特殊到最不特殊的顺序来写切点。 使用AOP来进行编程即便是经验丰富的开发人员可能也会感到迷惑。如果你确定要使用AOP来进行安全 声明,除了Spring AOP的参考手册外,强烈建议你参考一些这个专题相关的书籍。AOP实现起来比较复杂,尤 其是在解决不按照你预期运行的配置错误时更是如此。 比较方法授权的类型 以下的快速参考表可能在你选择授权方法检查时派上用场: 方法授权类型 声明方式 JSR标准 允许SpEL表达式 @PreAuthorize @PostAuthorize 注解 No Yes @RolesAllowed @PermitAll @DenyAll 注解 Yes NO http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 127 / 350 页 @Secure 注解 No No protect-pointcut XML No No 大多数使用Java 5的Spring Security用户倾向于使用JSR-250注解,以达到在IT组织间最大的兼容性和对 业务类(以及相关约束)的重用。在需要的地方,这些基本的声明能够被Spring Security本身实现的注解所代 替。 如果你在不支持注解的环境中(Java 1.4或更早版本)中使用Spring Security,很不幸的是,关于方法安 全的执行你的选择可能会很有限。即使在这样的情况下,对AOP的使用也提供了相当丰富的环境来开发基本的 安全声明。 方法的安全保护是怎样运行的? 方法安全的访问决定机制——一个给定的请求是否被允许——在概念上与web请求的访问决定逻辑是相 同的。AccessDecisionManager使用一个AccessDecisionVoters集合,其中每一个都要对能否进行访问做出 允许、拒绝或者弃权的的投票。AccessDecisionManager汇集这些投票器的结果并形成一个最终能否允许处罚 方法的决定。 Web请求的访问决策没有这么复杂,这是因为通过ServletFilters对安全请求做拦截(以及请求拒绝)都 相对很直接。因为方法的触发可能发生在任何的地方,包括没有通过Spring Security直接配置的代码,Spring Security的设计者于是选择Spring管理的AOP方式来识别、评估以及保护方法的触发。 下图在总体上展现了方法触发授权决策的主要参与者: 我们能够看到Spring Security的o.s.s.access.intercept.aopalliance.MethodSecurityInterceptor被标准 的Spring AOP运行时触发以拦截感兴趣的方法调用。通过上面的流程图,是否允许方法调用的逻辑就相对很清 晰了。 此时,我们可能会比较关心方法安全功能的性能。显然,MethodSecurityInterceptor不能在应用中每个 方法调用的时候触发——那方法或类上的注解是如何做到AOP拦截的呢? 首先,AOP织入默认不会对所有Spring管理的bean触发。相反,如果在 Spring Security配置中定义,一个标准的Spring AOP o.s.beans.factory.config.BeanPostProcessor将会被 注册,它将会探查AOP配置是否有AOP增强器(advisors)需要织入(以及拦截)。这个工作流是Spring标准 的AOP处理(名为AOP自动织入),并不是Spring Security所特有的。所有的BeanPostProcessors在spring ApplicationContext初始化时执行,在所有的Spring Bean配置生效后。 http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 128 / 350 页 Spring的AOP自动织入功能查询所有注册的PointcutAdvisors,查看是否有AOP切点匹配方法的调用并 使用AOP增强(advice)。Spring Security实现了 o.s.s.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor类,它会检查所有配置的方法安 全病建立适当的AOP拦截。注意的是,只有声明了方法安全的接口和类才会被AOP代理。 【强烈建议在接口上声明AOP规则(以及其它的安全注解),而不是在实现类上。使用类(通过Spring的 CGLIB代理)进行声明可能会导致应用出现不可预知的行为改变,通常在正确性方面比不上在接口定义安全声明 (通过AOP)。】 MethodSecurityMetadataSourceAdvisor将AOP影响方法行为的决定委托给 o.s.s.access.method.MethodSecurityMetadataSource的实例。不同的方法安全注解都拥有自己的 MethodSecurityMetadataSource,它将用来检查每个方法和类并添加在运行时执行的增强(advice)。 以下的图展现了这个过程是如何发生的: 取决于你的应用中配置的Sprin Bean的数量,以及拥有的安全方法注解的数量,添加方法安全代理将会 增加初始化ApplicationContext的时间。但是,一旦上下文初始化完成,对单个的代理bean来说性能的影响可 以忽略不计了。 http://lengyun3566.iteye.com 1.23 《Spring Security3》第五章第三部分翻译(保护业务层) 第 129 / 350 页 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小结) 发表时间: 2011-09-11 方法安全的高级知识 方法安全的表现力不仅局限于简单的角色检查。实际上,一些方法安全的注解能够完全使用Spring表达式语言 (SpEL)的强大功能,正如我们在第二章中讨论URL授权规则所使用的那样。这意味着任意的表达式,包含计 算、Boolean逻辑等等都可以使用。 使用bean包装类实现方法安全规则 另外一种定义方法安全的形式与XML声明有关,它可以包含在Spring Bean定义中。尽管阅读起来很容 易,但是这种方式的方法安全声明在表现性上不如切点,在功能上不如我们已经见过的注解方式。但是,对于 一定类型的工程,使用XML声明的方式足以满足你的需求。 我们可以替换前面的例子,将其改成基于XML声明的方式来保护changePassword方法。前面我们已经使 用了bean的自动织入,但是这与XML方法包装方式并不兼容,为适应这项技术我们需要明确声明服务层类。 安全包装是安全XML命名空间的一部分。首先我们需要在dogstore-base.xml文件中,包含进来安全的 schema,它用来包含安全相关的Spring Bean定义: 接下来(为了完成这个练习),移除IUserService.changePassword上的所有注解。 最后,用Spring XML的语法来声明bean,添加如下的附加的包装,它声明任何想触发changePassword方 法的人必须是一个ROLE_USER: 像本章前面的其它例子那样,这个保护功能能够很容易地通过将ROLE_USER 改为ROLE_ADMIN并尝试用 guest用户账号修改密码来校验。 在背后,这种方式的方法安全保护功能使用了MethodSecurityInterceptor,它被织入到 MapBasedMethodSecurityMetadataSource中。拦截器使用它来决定合适的访问ConfigAttributes。不同于 可使用SpEL以拥有更强表达能力的@PreAuthorize注解,声明只能在access属性中有逗号分隔的一 系列角色(类似于JSR-250 @RolesAllowed注解)。 可以使用简单的通配符来注明方法名,如,我们可以用如下的方式保护给定bean里所有的set方法: http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 131 / 350 页 方法名匹配可以包含前面或后面的正则表达式匹配符(*)。这个符号的存在意味着要对方法名进行通配符匹 配,为所有匹配该正则表达式的方法添加拦截器。注意,其它常用的正则表达式操作符(如?或[)并不支持。请 查阅相关的Java文档以理解基本的正则表达式。更复杂的通配符匹配或正则匹配并不支持。 在新代码中这种方式的安全声明并不常见,因为有更富于表现力的方式,但是了解这种方式的安全包装还 是有好处的,你可以把它当做方法安全工具栏中的一个可选项。这种方式对于无法为接口或类添加安全注解时 特别有效,如当你想为第三方类库添加安全功能时。 包含方法参数的实现方法安全规则 逻辑上,对一些类型的操作来说在制定规则时引用方法的参数是很合理的。例如,我们可能要对 changePassword方法进行重新限制,这样试图触发这个方法的用户必须满足两个约束条件: l 用户试图修改的必须是自己的密码,或者 l 用户是管理员(这种情况下,用户可以修改任何人的密码,这可能会通过一个管理界面) 修改这个规则限制只能管理员触发方法是很容易的,但是对我们来说怎样确定用户试图修改的是自己的密码 并不清楚。 幸运的是,Spring Security方法注解所绑定的SpEL支持更复杂的表达式,包括含有方法参数的表达式。 @PreAuthorize("#username == principal.username and hasRole('ROLE_USER')") public void changePassword(String username, String password); 译者注:个人感觉注解更应该是:@PreAuthorize("#username == principal.username or hasRole('ROLE_USER')") 在这里,你可以看到我们对第一个练习中使用的SpEL指令进行了增强,校验安全实体的用户名与方法参 数的用户名一致(#username——方法的参数名有一个#前缀)。方法参数绑定的强大功能可以使你更有创造 力并允许对方法的安全保护有更精确的逻辑规则。 参数绑定是如何实现的? http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 132 / 350 页 与我们在第二章中授权表达式的设计类似,一个表达式处理器 ——o.s.s.access.expression.method.MethodSecurityExpressionHandler的实现类——负责建立SpEL的上 下文,表达式基于这个上下文进行求值。MethodSecurityExpressionHandler使用 o.s.s.access.expression.method.MethodSecurityExpressionRoot作为表达式根,它(与 WebSecurityExpressionRoot为URL授权表达式所做的那样)为SpEL表达式的求值暴露了一系列的方法和伪属 性。 与第二章中我们见到过的内置表达式(如hasRole)基本完全一致,这些表达式也能够在方法安全的上下 文中使用,只是添加了一个与访问控制列表相关的方法(将在第七章:访问控制列表中介绍)以及另一个用来 基于角色过滤数据的伪属性。 你可能注意到在前面的例子中,相对于web层的表达式来说,我们使用的principal伪属性是一个在方法安 全表达式中很重要的表达式操作符。principal伪属性将会返回在当前Authentication对象中的principal,一般 来讲会是一个字符串(用户名)或UserDetails实现——这就意味着UserDetails的所有属性和方法都能被使用 来完善方法的访问限制。 下图展现了这个方面的功能: SpEL变量的应用要通过#前缀。需要注意的很重要一点是,为了使得方法参数的名字能够在运行时得到,调试 符号表中的信息必须在编译后保留。启用这个功能的常见方法如下: l如果你使用的javac编译器,在构建class使,要加上-g标示; l如果在ant中使用任务,添加debug="true"属性; l在Maven中,在构建你的POM是设置属性maven.compiler.debug=on。 查阅你的编译器、构建工具或IDE文档寻求帮助,以实现在你的环境中有相同的设置。 使用基于角色的过滤保护方法的数据安全 最后两个依赖Spring Security的注解是@PreFilter和@PostFilter,它们被用来对Collections或Arrays (仅@PostFilter有效)使用基于安全的过滤规则。这种类型的功能呢个被称为安全修正或安全修剪(security trimming or security pruning),并且涉及到在运行时使用安全实体的凭证进行集合对象的移除。正如你可 能预想的那样,这种过滤通过在注解声明中使用SpEL表达式来实现。 我们将会讲解一个JBCP Pets的例子,在其中我们将会对系统用户显示一个特别的分类,叫做顾客最爱 (Customer Appreciation Specials)。另外,我们将会使用Category对象的customersOnly属性来保证特 定分类的产品只能对该存储的顾客可见。 http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 133 / 350 页 对于使用Spring MVC的web应用来说,相关的代码很简单直接。 com.packtpub.springsecurity.web.controller.HomeController类用来显示主页,它拥有显示分类——一个 包含Category对象的Collection——到用户主页的代码: @Controller public class HomeController extends BaseController { @Autowired private IProductService productService; @ModelAttribute("categories") public Collection getCategories() { return productService.getCategories(); } @RequestMapping(method=RequestMethod.GET,value="/home.do") public void home() { } } 业务层IProductService接口的实现委托给数据访问层IProductDao。简单起见,IProductDao接口的实现类使 用了一些硬编码的Category对象。 通过@PostFilter实现基于角色的数据过滤 如同我们在方法安全授权中所作的那样,放置@PostFilter安全过滤指令在业务层上。在本例中,代码如 下: @PostFilter("(!filterObject.customersOnly) or (filterObject.customersOnly and hasRole('ROLE_USER'))") Collection getCategories(); 在理解它的工作原理之前,我们首先看一下@PostFilter注解的处理流程: 我们可以看到,再次使用了Spring AOP的标准组成,在一个after的AOP处理器中 http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 134 / 350 页 o.s.s.access.expression.method.ExpressionBasedPostInvocationAdvice被执行,为这个增强(advice)被 用来过滤目标方法返回的Collection或Array。像@PreAuthorize注解的处理那样, DefaultMethodSecurityExpressionHandler被再次用在这个表达式构建SpEL上下文和求值上。 应用修改后的效果能够在以guest和登录用户访问JBCP Pets时看到。你可以看到,当作为注册用户登 录,顾客最爱(Customer Appreciation Specials)分类将会对注册用户可见。 现在,我们已将学习方法后过滤的处理过程,让我们回到所使用的进行分类过滤的SpEL表达式上来。简单起 见,我们引用Collection作为方法的返回值,但是@PostFilter可以在Collection和Array返回类型的方法中使 用。 lfilterObject对于Collection中的每一个元素都会重新绑定到SpEL上下文中。这意味着,如果你的方法返回了包 含100个元素的Collection,SpEL表达式将会对每一个进行求值。 lSpEL表达式必须返回一个Boolean值。如果表达式的求值为true,这个元素将会保留在Collection中,如果表 达式求值为false,这个元素将会被移除。 在大多数情况中,你会发现Collection的事后过滤将会为你节省到处书写的大量模板代码。 注意理解@PostFilter在原理上怎样生效,不像@PreAuthorize,@PostFilter指定了方法行为而不是 事先条件。一些追求纯正面向对象的人可能会认为@PostFilter包含在方法注解并不合适,而是这样的过滤 应该在一个方法实现中通过代码进行处理。 【Collection过滤的安全性。需要注意的是你的方法实际返回的Collection被修改了。在一些场景下, 这并不是合适的行为,所以你需要保证你方法返回的Collection能够被安全地修改。如果返回的Collection 是ORM绑定的,这一点尤其重要,因为事后过滤的修改可能会无意间持久化到ORM的数据存储中。】 Spring Security还支持事先过滤Collections方法参数的功能,让我们尝试实现一下。 使用@PreFilter实现事先过滤集合 @PreFilter能被用来基于当前的安全上下文过滤传递到方法中的Collection元素。在功能上,只要拥有对 Collection的引用,这个注解的行为与@PostFilter除了以下两点外完全一致: l @PreFilter只支持Collection参数,不支持Array参数; l @PreFilter使用了一个额外可选的filterTarget属性,如果方法超过一个参数的话,这个属性被用来指明要过滤 哪个参数。 http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 135 / 350 页 同@PostFilter一样,要记住传递到方法中的原始Collection会被永久修改。这可能不是合适的行为,所以 你要保证调用者能够了解对Collection的修剪在方法调用后是安全的。 为了展现这个过滤的使用,我们临时修改在@PostFilter注解中用到的getCategories方法,改成把它的过 滤委托给一个新的方法。修改getCategories为如下: @Override public Collection getCategories() { Collection unfilteredCategories = productDao.getCategories(); return productDao.filterCategories(unfilteredCategories); } 我们要添加filterCategories方法到IProductDao接口和实现中。@PreFilter注解要加到接口声明中,如 下: @PreFilter("(!filterObject.customersOnly) or (filterObject.customersOnly and hasRole('ROLE_USER'))") public Collection filterCategories(Collection categories); 一旦你添加了该方法和@PreFilter注解声明到接口中,添加一个空实现(尽管你可以在方法中按照业务 需要进行进一步的过滤)。添加以下的方法体到ProductDao中: @Override public Collection filterCategories(Collection categories) { return categories; } 到此为止,你可以证实功能在从IProductService接口中移除@PostFilter注解后依旧正常使用,你会发现 行为与前面完全一样。 到底为何使用@PreFilter 此时,你可能挠头问@PreFilter到底有什么用处,因为@PostFilter的功能完全一样并适应更多的逻辑场 景。 http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 136 / 350 页 @PreFilter的确有很多用处,有一些与@PostFilter重叠,但是记住当声明安全限制时,宁可多余——我 们宁可过于小心也不能有潜在的安全危险。 以下是@PreFilter可能有用的场景: 大多数的应用都在数据层支持基于一系列的参数执行查询。@PreFilter能够保证安全过滤掉传递到数据库 查询中的参数。 在很多场景下,业务层收集来自于不同数据来源的信息。每个数据来源的输入能够进行安全的修剪以保证 用户不会无意间看到他本不应该看到的搜索结果或数据。 @PreFilter能够用来进行位置或关系相关的过滤——如可以基于用户点击过的分类或购买过的物品组成明 确搜索条件的基础。 希望这能够帮助你了解在哪里使用对Collections的事先或事后过滤,以在你的应用中添加额外的保护 层。 关于方法安全的警告 请记住这个关于实现和理解方法安全很重要的警告——为了真正很好地实现这个功能强大的安全类型,理 解其背后是怎样运行的很重要。缺乏对AOP的理解,不管是概念还是策略层面,都是造成方法安全失败的首要 原因。请确保你不仅完整阅读本章,还有Spring 3 Reference Documentation的第七章:使用Spring进行面 向方面编程。 在为一个已有系统实现方法安全之前,最好检查应用对面向对象设计原则的遵守情况。如果你的应用已经 合理使用了接口和封装,当你实现方法安全时就会有更少的不可预知错误。 小结 在本章中,我们覆盖了Spring Security处理授权的大部分功能。我们已经通过对JBCP Pets在线商店在应 用的各个层添加授权检查,学习了足够的知识,可以保证恶意用户不能操控或访问他们无权访问的数据。 尤其,我们: l学习了在应用设计过程中规划授权、用户/组匹配; l介绍两种实现细粒度授权的技术,基于授权或其它安全标准过滤出页面内容,使用了Spring Security的JSP标 签库和Spring MVC控制器的数据绑定; l介绍了在业务层保护业务功能和数据的方法,支持丰富且与代码紧密集成的安全模型指令。 到此为止,我们已经介绍到了你在web安全应用开发中所使用到的很多Spring Security重要功能。 http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 137 / 350 页 如果你一口气读到此处,这是一个很好的时间休息一下,复习我们所学的东西,并花些时间了解实例代码和 Spring Security本身的代码。 在接下来的两章中,我们会涵盖高级的自定义和扩展场景,以及Spring Security的访问控制列表(域对象 模型)模块。保证是令人兴奋的话题。 http://lengyun3566.iteye.com 1.24 《Spring Security3》第五章第四部分翻译(方法安全的高级知识和小 结) 第 138 / 350 页 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) 发表时间: 2011-09-18 关键字: Spring Security, Java 第六章 高级配置和扩展 到目前为止,我们已经介绍了大多数Spring Security组件的理论以及架构和使用。我们的JBCP Pets商业站 点也在逐渐变成一个安全的web应用,我们将会深入讲解一些更有难度的挑战。 在本章的课程中,我们将会: l 实现我们自己的安全过滤器,解决一个很有趣的问题,即对特定的用户角色用IP过滤的方式增强站点的安全; l 构建自定义的AuthenticationProvider及需要的支持类; l 理解和实现反黑客的措施名为session固化防护(session fixation protection)以及session的并发控制; l 使用包括session并发控制等功能构建简单的用户session报告增强; l 配置以及自定义访问拒绝后的行为和异常处理; l 构建基于Spring bean的Spring Security配置,放弃使用便利的安全命名空间配置风格,从头开始直 接织入和实例化完整的Spring Security大量类; l 了解如何通过基于Spring bean的方式配置session的处理和创建; l 对比配置风格相对于基于Spring bean配置风格的优劣; l 学习AuthenticationEvent的架构以及事件处理和自定义; l 构建一个自定义的SpEL表达式投票器,新建一个SpEL方法并在表达式中使用。 实现一个自定义的安全过滤器 对于安全应用来说,一个很常见的定制化场景就是使用自定义的servlet过滤器,它能够用来增加应用特定 的安全层,通过提供更完整的信息增强用户体验,并移除潜在的恶意行为。 在servlet过滤器级别实现IP过滤 http://lengyun3566.iteye.com 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) 第 139 / 350 页 一个能够使得JBCP Pets安全审计人员很高兴的功能增强就是围绕管理员账号的使用进行更强限制,或者 (更好的是)针对所有用户对站点的管理操作。 我们通过一个过滤器来解决这个问题,保证所有具有ROLE_ADMIN角色的用户只能在一系列特定的IP地 址上访问系统。我们将会在此做简单的地址匹配,但是你可以很容易地扩展这个例子,来使用IP掩码、从数据库 中读取IP地址等。 细致的用户可能会意识到会有其它的方法来实现这个功能,包括更复杂的访问声明;但 是,为了阐述的方便,这是一个很简单直接的例子。记住在现实世界中,诸如网络地址转换(Network Address Translation ,NAT)以及动态IP地址会使得基于IP的规则在公网或无管理的网络中很脆弱。 书写我们自己的servlet过滤器 我们的过滤器将会设置成目标角色以及特定的IP地址才能允许访问。我们将这个类命名为 com.packtpub.springsecurity.security.IPRoleAuthenticationFilter,并定义如下。这个代码有一些复杂,所 以省略掉了一下不重要的代码列表。请查看本章的源代码以了解此类: package com.packtpub.springsecurity.security; // imports omitted public class IPRoleAuthenticationFilter extends OncePerRequestFilter {} 可以看到,我们的过滤器继承自Spring web库中的o.s.web.filter.OncePerRequestFilter基类。但这并不是必 须的,这对于具有复杂配置且至执行一次的过滤器来说很便利,所以我们建议使用。 private String targetRole; private List allowedIPAddresses; 我们的过滤器具有两个属性——目标角色(如ROLE_ADMIN),以及一个允许的IP地址列表。这将会通过标准 的Spring bean定义进行配置,我们将会稍后见到。最后,我们到达这个bean的核心,也就是doFilterInternal 方法。 @Override public void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { http://lengyun3566.iteye.com 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) 第 140 / 350 页 // before we allow the request to proceed, we'll first get the user's role // and see if it's an administrator final Authentication authentication = SecurityContextHolder. getContext().getAuthentication(); if (authentication != null && targetRole != null) { boolean shouldCheck = false; // look if the user is the target role for (GrantedAuthority authority : authentication.getAuthorities()) { if(authority.getAuthority().equals(targetRole)) { shouldCheck = true; break; } } // if we should check IP, then check if(shouldCheck && allowedIPAddresses.size() > 0) { boolean shouldAllow = false; for (String ipAddress : allowedIPAddresses) { if(req.getRemoteAddr().equals(ipAddress)) { shouldAllow = true; break; } } if(!shouldAllow) { // fail the request throw new AccessDeniedException(“Access has been denied for your IP address: “+req.getRemoteAddr()); } } } else { logger.warn(“The IPRoleAuthenticationFilter should be placed after the user has been authenticated in the filter chain.”); } chain.doFilter(req, res); } // accessors (getters and setters) omitted } http://lengyun3566.iteye.com 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) 第 141 / 350 页 你可以看到,代码很简单明了,使用SecurityContext来获得Authentication关于当前请求的信息,就像我们 在前面的章节练习中所作的那样。你可能会意识到没有很多特定与Spring Security的东西,除了获取用户角色 (GrantedAuthority)的方法以及使用了AccessDeniedException来标示访问被拒绝。 让我们看一下如何作为一个Spring bean配置自定义的过滤器。 配置IP servlet过滤器 配置这个过滤器作为一个简单的Spring bean。我们可以在dogstore-base.xml文件中配置它。 使用标准的Spring bean配置语法,我们能够提供一系列的IP地址值。在本例中,1.2.3.4显然不是合法的IP地址 (如果本地部署的话,127.0.0.1会是不错的一个IP地址配置),但是它为我们提供了便利的方式来测试这个过 滤器是否生效。 最后,我们要将这个过滤器添加到Spring Security的过滤器链中。 将IP servlet过滤器添加到Spring Security过滤器链中 要添加到Spring Security过滤器链中的Servlet过滤器要通过相对于已经存在于过滤器链中其它过滤 器来确定位置。自定义的过滤器要在元素中配置,通过一件简单bena引用和位置标示,IP servlet过滤 器的配置如下: 需要记住的是我们的过滤器依赖于SecurityContext 和Authentication对象,来进行辨别用户的 GrantedAuthority。所以,我们要将这个过滤器的位置放在FilterSecurityInterceptor之前,它(你可能会回忆 http://lengyun3566.iteye.com 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) 第 142 / 350 页 起第二章:Spring Security起步)负责确定用户是否有正确的权限访问系统。在过滤器的这个点上,用户的标 示信息已经知道了,所以这是一个合适的位置以插入我们的过滤器。 现在你可以重启应用,因为这个新的IP过滤器,作为admin用户登录将会被限制。你可以对其进行随意的 修改,以完全理解各部分是如何协同的。 【扩展IP过滤器。对于更复杂的需求,可以扩展这个过滤器以允许对角色和IP地址(子网匹配,IP段等)更复杂 的匹配。但是,在java中并没有广泛使用的类库来进行这种类型的计算——考虑到安全环境中普遍存在的IP过 滤,这一点颇令人吃惊。】 尝试增强IP过滤器的功能以添加我们尚未描述到的功能——动手是学习的最好方法,而将练习改造成现实 世界中的例子是将抽象概念变得具体的很好方式。 http://lengyun3566.iteye.com 1.25 《Spring Security3》第六章第一部分翻译(自定义安全过滤器) 第 143 / 350 页 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 发表时间: 2011-09-18 关键字: Spring Security, java 实现自定义的AuthenticationProvider 在很多场景下,你的应用需要跳出Spring Security功能的边界,可能会需要实现自己的 AuthenticationProvider。回忆在第二章中AuthenticationProvider的角色,在整个认证过程中,它接受安全 实体请求提供的凭证(即Authentication对象或authentication token)并校验其正确性和合法性。 通过AuthenticationProvider实现一个简单的单点登录 通常,应用允许以用户或客户端方式登录。但是,有一种场景也很常见,尤其是在广泛使用的应用中,即允 许系统中的不同用户以多种方式登录。 假设我们的系统与一个简单的“单点登录”提供者进行集成,用户名和密码分别在HTTP头中的j_username 和j_password发送。除此以外,j_signature头信息包含了用户名和密码的随意编码算法形成的字符串,以辅助 安全请求。 【不要使用这个例子作为真实单点登录的解决方案。这个例子很牵强,只是为了说明实现一个完全自定义 AuthenticationProvider的步骤。真正的SSO解决方案显然会更安全并涉及到几次的握手以建立可信任的凭 证。Spring Security支持几种SSO解决方案,包括中心认证服务(CAS)和SiteMinder,我们将会在第十章: 使用中心认证服务实现单点登录中介绍。实际上,Spring Security提供了一个类似的过滤器用来进行 SiteMinder请求头的认证,即o.s.s.web.authentication.preauth.RequestHeaderAuthenticationFilter,也是 这种类型功能的一个好例子。】 对于admin用户的登录,我们的算法期望在请求头中包含如下的数据: 请求头 值 j_username admin j_password admin j_signature admin|+|admin http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 144 / 350 页 一般来讲,AuthenticationProvider将会寻找特定的AuthenticationToken,而后者会在过滤器链中位置 比较靠前的servlet filter里面进行填充赋值(明确会在AuthenticationManager访问检查执行之前),如下图 描述: 在这种方式中,AuthenticationToken的提供者与其消费者AuthenticationProvider有一点脱离关系了。 所以,在实现自定义AuthenticationProvider时,通常还需要实现一个自定义的servlet过滤器,其作用是提供 特定的AuthenticationToken。 自定义认证token 我们实现自定义的方法会尽可能的使用Spring Security的基本功能。基于此,我们会扩展并增强基本类如 UsernamePasswordAuthenticationToken,并添加一个新的域来存储我们已编码的签名字符串。最终的类 com.packtpub.springsecurity.security.SignedUsernamePasswordAuthenticationToken,如下: package com.packtpub.springsecurity.security; // imports omitted public class SignedUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken { private String requestSignature; private static final long serialVersionUID = 3145548673810647886L; /** * Construct a new token instance with the given principal, credentials, and signature. * * @param principal the principal to use * @param credentials the credentials to use * @param signature the signature to use */ public SignedUsernamePasswordAuthenticationToken(String principal, String credentials, String signature) { super(principal, credentials); http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 145 / 350 页 this.requestSignature = signature; } public void setRequestSignature(String requestSignature) { this.requestSignature = requestSignature; } public String getRequestSignature() { return requestSignature; } } 我们可以看到SignedUsernamePasswordAuthenticationToken是一个简单的POJO类,扩展自 UsernamePasswordAuthenticationToken。Tokens并不需要太复杂——它们的主要目的就是为后面的校验 封装凭证信息。 实现对请求头处理的servlet过滤器 现在,我们要写servlet过滤器的代码,它负责将请求头转换成我们新定义的token。同样的,我们扩展对 应的Spring Security基本类。在本例中,o.s.s.web.authentication. AbstractAuthenticationProcessingFilter满足我们的要求。 【基本过滤器AbstractAuthenticationProcessingFilter在Spring Security中是很多进行认证过滤器的父类(包 括OpenID、中心认证服务以及基于form的用户名和密码登录)。这个类提供了标准的认证逻辑并适当织入了 其它重要的资源如RememberMeServices和ApplicationEventPublisher(本章的后面将会讲解到)。】 现在,让我们看一下代码: // imports omitted public class RequestHeaderProcessingFilter extends AbstractAuthenticationProcessingFilter { private String usernameHeader = "j_username"; private String passwordHeader = "j_password"; private String signatureHeader = "j_signature"; protected RequestHeaderProcessingFilter() { super("/j_spring_security_filter"); } @Override http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 146 / 350 页 public Authentication attemptAuthentication (HttpServletRequest request,HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String username = request.getHeader(usernameHeader); String password = request.getHeader(passwordHeader); String signature = request.getHeader(signatureHeader); SignedUsernamePasswordAuthenticationToken authRequest = new SignedUsernamePasswordAuthenticationToken (username, password, signature); return this.getAuthenticationManager().authenticate(authRequest); } // getters and setters omitted below } 可以看到,这个比较简单的过滤器查找三个已命名的请求头,正如我们已经规划的那样(如果需要 的话,可以通过bean属性进行配置),并监听默认的URL /j_spring_security_filter。正如其它的Spring Security过滤器那样,这是一个虚拟的URL并被我们的过滤器的基类AbstractAuthenticationProcessingFilter 所识别,基于此这个过滤器采取行动尝试创建Authentication token并认证用户的请求。 【区分参与认证token流程的各个组件。在这个功能中,很容易被这些术语、接口和类的名字搞晕。代表要认证 的token接口是o.s.s.core.Authentication,这个接口的实现将以后缀AuthenticationToken结尾。这是一个很 简单的方式来区分Spring Security提供的认证实现类。】 在本例中,我们尽可能将错误检查最小化(译者注:即参数的合法性与完整性的检查)。可能在实际的应用 中,会校验是否所有头信息都提供了以及在发现用户提供信息不正确的时候要抛出异常或对用户进行重定向。 一个细小的配置变化是需要将我们的过滤器插入到过滤器链中: 你可以看到过滤器代码最后请求AuthenticationManager来进行认证。这将最终委托配置的 AuthenticationProvider,它们中的一个要支持检查SignedUsernamePasswordAuthenticationToken。接下 来,我们需要书写一个AuthenticationProvider来做这件事情。 http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 147 / 350 页 实现基于请求头的AuthenticationProvider 现在,我们写一个AuthenticationProvider的实现类,即 com.packtpub.springsecurity.security.SignedUsernamePasswordAuthenticationProvider,负责校验我们 自定义Authentication token的签名。 package com.packtpub.springsecurity.security; // imports omitted public class SignedUsernamePasswordAuthenticationProvider extends DaoAuthenticationProvider { @Override public boolean supports(Class extends Object> authentication) { return (SignedUsernamePasswordAuthenticationToken .class.isAssignableFrom(authentication)); } @Override protected void additionalAuthenticationChecks (UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { super.additionalAuthenticationChecks (userDetails, authentication); SignedUsernamePasswordAuthenticationToken signedToken = (SignedUsernamePasswordAuthenticationToken) authentication; if(signedToken.getRequestSignature() == null) { throw new BadCredentialsException(messages.getMessage( "SignedUsernamePasswordAuthenticationProvider .missingSignature", "Missing request signature"), isIncludeDetailsObject() ? userDetails : null); } // calculate expected signature if(!signedToken.getRequestSignature() .equals(calculateExpectedSignature(signedToken))) { throw new BadCredentialsException(messages.getMessage ("SignedUsernamePasswordAuthenticationProvider .badSignature", "Invalid request signature"), http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 148 / 350 页 isIncludeDetailsObject() ? userDetails : null); } } private String calculateExpectedSignature (SignedUsernamePasswordAuthenticationToken signedToken) { return signedToken.getPrincipal() + "|+|" + signedToken.getCredentials(); } } 你可以看到我们再次扩展了框架中的类DaoAuthenticationProvider,因为有用的数据访问代码仍然需要进行 实际的用户密码校验以及通过UserDetailsService加载UserDetails。 这个类有些复杂,所以我们将分别介绍其中的每个方法。 Supports方法,是重写父类的方法,向AuthenticationManager指明当前AuthenticationProvider要进 行校验的期望运行时Authentication token。 接下来,additionalAuthenticationChecks方法被父类调用,此方法允许子类对token进行特有的校验。 这正适合我们的策略,所以添加上我们对token新的签名检查。基本上已经完成了我们自定义“简单SSO”的实 现,仅剩一处配置了。 连接AuthenticationProvider 一个常见的要求就是将一个或更多的AuthenticationProvider接口连接起来,因为用户可能会以几种校验 方式中的某一种登录系统。 因为到目前为止,我们还没有了解其它的AuthenticationProvider,我们可以假设以下的需求,即使用标 准的用户名和密码基于form的认证以及前面实现的自定义简单SSO认证。当配置了多个 AuthenticationProvider时,每个AuthenticationProvider都会检查过滤器提供给它的 AuthenticationToken,仅当这个token类型它支持时才会处理这个token。以这种方式,你的应用同时支持不 同的认证方式并不会有什么坏处。 连接多个AuthenticationProvider实际上很简单。只需要在我们的dogstore-security.xml配置文件中声明 另一个authentication-provider引用。 http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 149 / 350 页 与我们安全配置文件中引用的其它Spring bean一样,signedRequestAuthenticationProvider引用 就是我们的AuthenticationProvider,它将在dogstore-base.xml中与其它的Spring bean一起进行配置。 我们自定义的AuthenticationProvider的bean属性其实都是父类所需要的。这些也都指向了我们在 AuthenticationManager的中第二个authentication-provider声明中的那些bean。 最终已经完成了支持这个简单单点登录功能的编码和配置,至此可以给自己一个小小的喝彩。但是,还有 一个小问题——我们应该怎样操作请求的http头以模拟我们的SSO认证提供者呢? 使用请求头模拟单点登录 尽管我们的场景比较牵强,但是有一些商业和开源的单点登录解决方案,它们能够被配置以通过HTTP请 求头发送凭证信息,最具有代表性的是CA(以前的Netegrity)SiteMinder。 【需要特别注意的是,与SSO方案集成的应用是不能通过用户的直接请求访问的。通常情况下,SSO provider 功能作为代理,通过它确定用户的请求流程(是安全的)或provider持有关于密码的信息并将这些信息与单个 的安全应用隔离。在没有完全了解一个其使用的硬件、网络和安全设施之前,不要部署SSO应用。】 Mozilla Firefox的浏览器扩展,名为Modify Headers(可以在以下地址获得: http://modifyheaders.mozdev.org),是一个很简单的工具能够用来模拟伪造HTTP头的请求。以下的截图表 明了如何使用这个工具添加我们的SSO方案所希望得到的请求头信息: http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 150 / 350 页 将所有的头信息标示为Enabled,访问这个URL:http://localhost:8080/JBCPPets/j_spring_security_filter, 会发现我们能够自动登录系统。你可能也会发现我们给予form的登录还能继续可用,这是因为保留了这两个 AuthenticationProvider实现以及过滤器链中对应的过滤器。 (译者注:说实话,作者这个实现自定义AuthenticationProvider的例子真的是比较牵强,但是还算完整描述 出了其实现方式,想深入了解AuthenticationProvider自定义的朋友,可以参照Spring Security提供的 CasAuthenticationProvider等实现。) 实现自定义AuthenticationProviders时要考虑的事项 尽管我们刚刚看到的例子并没有阐述你想构建的AuthenticationProvider,但是任何自定义 AuthenticationProvider的步骤是类似的。这个练习的关键在于: l 基于用户的请求完成一个Authentication实现的任务一般情况下会在过滤器链中的某一个中进行。取决于是 否校验凭证的数据,这个校验组件可能要进行扩展; l 基于一个合法的Authentication认证用户的任务需要AuthenticationProvider的实现来完成。请查看我们在 第二章中讨论过的AuthenticationProvider所被期望拥有的功能; l 在一些特殊的场景下,如果未认证的session被发现,可能会需要自定义的AuthenticationEntryPoint。我们 将会在本章接下来的部分更多了解这个接口,也会在第十章介绍中心认证服务(CAS)时,介绍一些 AuthenticationEntryPoint的实际例子。 如果你能时刻记住它们的角色,当你在开发应用特定的AuthenticationProvider时,会在实现和调试过程 中少很多的迷惑。 http://lengyun3566.iteye.com 1.26 《Spring Security3》第六章第二部分翻译(自定义 AuthenticationProvider) 第 151 / 350 页 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 发表时间: 2011-10-17 关键字: Spring Security, 安全, Java Session的管理和并发 Spring Security的一个常见配置就是检测相同的用户以不同的session登录安全系统。这被称为并发控制 (concurrency control),是session管理(session management)一系列相关配置功能的一部分。严格 来说,这个功能并不是高级配置,但是它会让很多新手感到迷惑,并且最好在你对Sping Security整体功能有所 了解的基础上再掌握它。Spring Security的session管理能够以两种不同的方式进行配置——session固化保护 (session fixation protection)和并发控制。因为并发控制的功能基于session固化保护所提供的框架,我们 先介绍session固化。 配置session fixation防护 如果我们使用的是security命名空间的配置方式,session固化防护已经被默认进行了配置。如果我们要指 明将其配置为与默认设置一致的话,我们需要这样: Session固化防护这个功能你可能并不会在意,除非你想扮演一个恶意的用户。我们将向你展示如何模拟一个 session窃取攻击,但是在此之前,有必要理解session固化是怎么回事以及怎样防止这样的攻击。 理解session fixation攻击 Session固化是恶意用户试图窃取系统中一个未认证用户的session。对攻击者来说,可以通过各种技术来 获取用户session的唯一标识(例如,JSESSIONID)。如果攻击者创建了带有用户JSESSIONID的cookie或者 URL参数,他就能够访问用户的session。 尽管这是一个明显的问题,但是一般情况下,如果用户没有经过认证,他们就还没有输入任何敏感信息 (假设站点的安全已经正确规划了)。如果用户认证后依旧使用相同的session标识符,这个问题就会比较更加 重要了。如果用户在认证后还使用相同的标识符,那攻击者现在就能访问认证过用户的session,而甚至不必要 知道他们的用户名和密码。 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 152 / 350 页 【此时,你可能很不屑并认为在现实世界中这不会发生。实际上,session窃取攻击经常发生。关于这个 话题,我们建议(正如在第三章那样)你花些时间阅读由OWASP组织(http://www.owasp.org/)发布的包含 重要信息的文章以及学习案例。攻击者和恶意用户是真实存在的,如果你不了解他们常用的技术及如何避免, 他们会对你的用户、应用或公司造成真正的损害。】 下图展现了session固化攻击是如何发生的: 既然了解了攻击如何进行,接下来我们查看Spring Security如何防止。 使用Spring Security防止session fixation攻击 如果我们能够阻止用户在认证前和认证后使用相同的session,我们就能够让攻击者掌握的session ID信息 变得没有用处。Spring Security的session固化防护解决这个问题的方式就是在用户认证之后明确创建一个新的 session并将旧的session失效。 让我们看下图: 我们可以看到一个新的过滤器,o.s.s.web.session.SessionManagementFilter,负责检查一个特定的用户是否 为新认证的。如果用户是新认证的,一个配置的 o.s.s.web.authentication.session.SessionAuthenticationStrategy将确定要怎样做。 o.s.s.web.authentication.session.SessionAuthenticationStrategy将会创建一个新的session(如果用户已经 拥有一个的话),并将已存在session的内容拷贝到新session中去。这看起来很简单,但是,通过上面的图表 我们可以看到,它能够有效组织恶意用户在未知用户登录后重用session ID。 模拟session fixation攻击 此时,你可能会想要看一下在模拟session固化攻击时会涉及到什么。为了实现这一点,你需要在 dogstore-security.xml中配置session固化防护失效。 接下来,你需要打开两个浏览器。我们将会在IE中初始化session,并从那里窃取,我们的攻击者将会使用窃取 到的session在Firefox中登录。我们将会使用Internet Explorer Developer Tools (IE 8中自带)以及Firefox Web Developer Add-On(第三章中已经给过URL)来查看和控制cookie。 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 153 / 350 页 在IE中打开JBCP Pets首页,然后打开开发者工具(看“工具”下拉菜单或点击F12),并在“缓存” 菜单下选择“查看Cookie信息”。在合适域下(如果使用localhost将为空)找到JSESSIONID的cookie。 将session cookie的值复制到粘贴板上,然后登录JBCP Pets站点。如果你重复“查看Cookie信 息”,你将会发现JSESSIONID在登录后没有变化,这将会导致很容易受到session固化攻击。 在Firefox下,打开JBCP Pets站点。你将会被分配一个session cookie,这能通过Cookie菜单的“查 看Cookie信息”菜单项查看到。 为了完成我们的攻击,我们点击“Edit Cookie”选项,并将从IE中复制到粘贴板上的JSESSIONID值粘贴进 来,如下图所示: 我们的session固化攻击完成了!如果此时在Firefox中重新加载页面,你将以IE中已登录用户相同的身份进入系 统,并不需要你知道用户名和密码。你是否害怕恶意用户了? 现在,重新使session固化防护生效然后重新尝试这个练习。你会发现,在这种情况下,JSESSIONID在用 户登录后会发生变化。因为你已经了解了session固化攻击是如何发生的,这意味着减少了可信任用户陷入这种 攻击的风险。干的漂亮! 细心的开发人员应该会注意到有很多种窃取session cookie的方式,有一些如跨站脚本攻击(XSS)可能会 使得session固化防护都很脆弱。请访问OWASP站点来了解更多防止这种类型攻击的信息。 比较session-fixation-protection选项 session-fixation-protection属性支持三种不同的选项允许你进行修改: 属性值 描述 none 使得session固化攻击失效,不会配置 SessionManagementFilter(除非其它 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 154 / 350 页 的属性不是默 认值) migrateSession 当用户经过认证后分配一个新的 session,它保证原session的所有属性 移到新session中。我们将在后面的章节 中讲解,通过基于bean的方式如何进行 这样的配置。 newSession 当用户认证后,建立一个新的session, 原(未认证时)session的属性不会进行 移到新session中来。 在大多数场景下,默认行为即migrateSession适用于在用户登录后希望保持重要信息(如点击爱好、购 物车等)的站点的站点 通过session的并发控制增强对用户的保护 紧随session固化防护一个很自然的用户安全增强功能就是session并发控制。如前面所描述的那样, session并发控制能够确保一个用户不能同时拥有超过一个固定数量的活跃session(典型情况是一个)。要确 保这个最大值的限制需要涉及到好几个组件的协作以精确跟踪用户session活动的变化。 让我们配置这个功能并了解其如何工作,然后对其进行测试。 配置session并发控制 既然我们要了解session并发控制所要涉及的组件,那将其运行环境搭建起来可能会更有感官的了解。首 先,我们需要使得ConcurrentSessionFilter生效并在dogstore-security.xml配置。 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 155 / 350 页 现在,我们需要在web.xml描述文件中配置中使得o.s.s.web.session.HttpSessionEventPublisher生效,这样 servelt容器将会通知Spring Security session生命周期的事件(通过HttpSessionEventPublisher)。 org.springframework.web.context.ContextLoaderListener org.springframework.security.web.session .HttpSessionEventPublisher dogstore 这两个配置完成,session的并发控制功能也就激活了。让我们看一下它内部是如何工作的,然后我们将 会通过几步操作来查看它对用户的session的保护功能。 理解session并发控制 我们在前面提到session并发控制试图限制相同的用户以不同的session进行访问。基于我们对session窃 取方式攻击的了解,我们可以发现session并发控制能够降低攻击者窃取已登录合法用户session的风险。你觉 得为什么会这样呢? Session并发控制使用o.s.s.core.session.SessionRegistry来维护一个活跃HTTP session的列表而认证过 的用户与其进行关联。当session创建或过期时,注册表中会实时进行更新,基于HttpSessionEventPublisher 发布的session生命周期事件来跟踪每一个认证用户的活动session的数量。 SessionAuthenticationStrategy的一个扩展类即 o.s.s.web.authentication.session.ConcurrentSessionControlStrategy提供方法来实现新session的跟踪以及 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 156 / 350 页 session并发控制的实际增强功能。每次用户访问这个安全站点时,SessionManagementFilter将会比照 SessionRegistry检查这个活跃的session。如果用户活跃的session不在SessionRegistry这个活跃session列表 中,最近最少被使用的session将会立即过期。 在修改后的session并发控制过滤器链中的第二个参与者是o.s.s.web.session.ConcurrentSessionFilter。 这个过滤器能够辨认出过期的session(典型情况下,session会被servlet容器或者被 ConcurrentSessionControlStrategy强制失效掉)并通知用户他的session已经失效了。 既然我们已经了解了session并发控制是如何工作的,那对我们来说很容易制造一个它使用的场景。 测试session并发控制 如同验证session固化攻击那样,我们需要访问两个web浏览器。按一下的步骤: 1. 在IE中,以guest用户登录; 2. 接下来,在Firefox中,以相同的用户(guest)登录; 3. 最后,返回到IE中,做任何的动作都可以。你会发现有一个信息提示你的session已经过期了。 将会显示以下的信息: 尽管不很友好,但是它能够表明session已经被软件强制失效了。如果你是一个攻击者,现在你可能会很灰 心。但是,如果你是一个合法的用户,你可能会很迷惑,因为这显然不是一个友好的方式来表明JBCP Pets 一直关注着你的安全。 【session并发控制对Spring Security的新用户来说是很难掌握的概念。很多用户在还没有完全理解其怎样运行 和能带来什么好处时就尝试实现它。如果你想使用这个强大的功能,并且它不像你预想的那样工作,请确保你 的配置全部正确并回顾一下本节讲的原理——希望它能帮助你理解什么出错了。】 在这样的事件发生时,我们应该将用户重定向到登录页,并提供一个信息来说明发生了什么错误。 配置session失效时的重定向地址 幸运的是,有一种很容易的方法使用户在session并发控制后重定向到友好的页面(一般来说,是登录 页),即设置expired-url属性为一个你应用中合法的页面。 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 157 / 350 页 这样在我们的应用中,就会将用户重定向到登录form,并且我们可以修改这个页面来展现用户友好的信息来表 明发现了多个活跃的session,从而需要重新登录。当然,这个URL是完全随意的,并根据你应用的需求来进行 相应的调整。 Session并发控制的其它好处 Session并发控制的另一个好处是存在SessionRegistry跟踪活跃的session(过期session是可选的)。这 意味着我们能够得到系统中运行时的用户活动信息(至少是认证过的用户)。 即使你不想使用session并发控制,你可以可以这样做。只需将max-sessions的值设置为-1,这样 session跟踪会保持可用,但没有最大session个数的限制。 让我们看两个使用此功能的两种简单方式。 显示活动的用户 你可能会在线论坛上见到显示系统中当前活跃用户的数量。借助于使用session注册跟踪(通过session并 发控制),很容易实现在应用中的每个页面对此进行展现。 让我们在BaseController中添加一个简单的方法以及bean自动织入。@Autowired SessionRegistry sessionRegistry; @ModelAttribute("numUsers") public int getNumberOfUsers() { return sessionRegistry.getAllPrincipals().size(); } 我们可以看到这暴露了一个能够在Spring MVC JSP页面中能够使用的属性,所以我们添加一个页脚footer.jsp 到JBCP Pets站点中并使用这个属性。 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 158 / 350 页