《Spring Security3》一书的翻译


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 期望得到的信息:
User: http://lengyun3566.iteye.com 1.6 《Spring Security3》第二章第三部分翻译(上) 第 31 / 350 页 Password:
你可以看到用户名和密码对应的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文件中配置它。 1.2.3.4 使用标准的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 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 页 如果你重新启动应用并登录,能够在每个页面的底部看到活动用户的数量。 很简单,但是它阐述了作为Spring Security一部分所提供的session跟踪的好处——尤其是你能够直接使用这 个内置的功能。 我们能够借助于SessionRegistry进行更高级的收据收集,这对于管理员来说很有用——现在让我们看一 下。 显示所有用户的信息 SessionRegistry跟踪所有活跃用户session的信息。如果你想增强站点的管理,我们可以可以很容易地在 一个页面中列出所有的用户活跃用户以及他们在站点中使用的名字。 让我们为AccountController添加一个新的方法(尽管这样的功能一般会添加在在管理区域,但是此时我 们可以假设JBCP Pets并不是一个真正的站点),这个方法将会查找SessionRegistry中的信息并收集当前 session的信息。 @RequestMapping("/account/listActiveUsers.do") public void listActiveUsers(Model model) { Map lastActivityDates = new HashMap(); for(Object principal: sessionRegistry.getAllPrincipals()) { // a principal may have multiple active sessions for(SessionInformation session : sessionRegistry.getAllSessions(principal, false)) { // no last activity stored if(lastActivityDates.get(principal) == null) { lastActivityDates.put(principal, session.getLastRequest()); } else { // check to see if this session is newer than the last stored http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 159 / 350 页 Date prevLastRequest = lastActivityDates.get(principal); if(session.getLastRequest().after(prevLastRequest)) { // update if so lastActivityDates.put(principal, session.getLastRequest()); } } } } model.addAttribute("activeUsers", lastActivityDates); } 这个方法使用了SessionRegistry的两个API。 l getAllPrincipals:返回拥有活跃session的Principal对象(典型情况下为UserDetails对象)所组成的List; l getAllSessions(principal, includeExpired):得到指定Principal的SessionInformation组成的List,包含了 每个session的信息。也能够包含过期的session。 了解了SessionRegistry API方法,listActiveUsers方法的逻辑就很简单了——检索注册表中所有的活跃用 户并寻找最近活动的session。Principal以及最近活跃的时间戳信息插入到一个Map中用来在UI中展现。 UI页面通过使用JSTL结构变得很简单了。在WEB-INF/views/account目录下,创建listActiveUsers.jsp, 内容如下(简便起见,我们省略了头部和尾部信息): <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

    Active Users

    • ${uinfo.key.username} / Last Active: ${uinfo.value}
    最后,当我们导航到http://localhost:8080/JBCPPets/account/listActiveUsers.do,页面基本如下: http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 160 / 350 页 通过这些例子,你已经了解到了SessionRegistry的威力。我们甚至可以扩展SessionRegistry来跟踪用户活动 的附加信息,如最后访问的页面、最后的行为等——这对基于Spring Security构建管理界面来说是很有用的。 http://lengyun3566.iteye.com 1.27 《Spring Security3》第六章第三部分翻译(Session的管理和并发) 第 161 / 350 页 1.28 《Spring Security3》第六章第四部分翻译(异常处理) 发表时间: 2011-10-18 关键字: spring, security, java 理解和配置异常处理 Spring Security使用简单的分发器模式将框架抛出的异常转移到明确的处理行为中,这将会影响用户对安 全资源的访问。Spring Security过滤器链中最后几个过滤器之一的 o.s.s.web.access.ExceptionTranslationFilter负责检查在认证和授权过程中(在过滤器链的最后一个过滤器即 FilterSecurityInterceptor里)抛出的异常并采取适当的行为。 标准的ExceptionTranslationFilter支持分发处理三种常规类型的失败,如下图所示: 我们能够看到ExceptionTranslationFilter处理如下的场景: l 抛出AuthenticationException异常,用户需要登录(在大多数场景下——取决于 AuthenticationEntryPoint,我们将会在本章稍后介绍); l 抛出AccessDeniedException,用户已经尚未登录; l 抛出AccessDeniedException,用户已经登录,在这种场景下,展现给用户的要么是出错页面,要么是通用 的HTTP 403响应。 让我们看一下AccessDeniedHandler的配置。 配置“Access Denied”处理 到此为止,当一个认证过的用户访问受保护的资源时,因为缺少GrantedAuthority或其它需要的权限被 拒绝的时候,他们看到的是servlet容器的默认HTTP 403(访问拒绝)页面。这个页面是 o.s.s.web.access.AccessDeniedHandler默认行为的结果,它被ExceptionTranslationFilter所触发以响应框架 抛出的一个AccessDeniedException异常。 尽管这个简单的出错页面有效,但是并不具有吸引力和也不对用户友好。最好能够将这个页面与我们站点 整体的外观和风格一致,并为用户提供信息告诉他发生了什么。 基本的处理用户访问拒绝报告的方法是配置自定义的URL,Spring Security会在请求拒绝时,将用户带到 这个URL。我们会发现这个功能与初始登录时,用户被定向到元素声明的 login-page很类似。 http://lengyun3566.iteye.com 1.28 《Spring Security3》第六章第四部分翻译(异常处理) 第 162 / 350 页 配置“Access Denied”的目标地址 配置用户被定向到的地址是这个练习中最简单的部分。只需在声明中,添加一个元素,它指明了我们的访问拒绝处理URL,如下: 注意——还没完事呢,因为我们还没有将这个URL与任何的Spring MVC应用代码关联起来。我们需要在 LoginLogoutController添加增强代码以处理这个URL,并推送一些有用的信息到model中供view展现给用 户。 添加对AccessDeniedException处理的控制器 我们需要添加一个控制器action处理方法以响应刚刚配置的URL。另外,我们会从 AccessDeniedException抽取一些细节信息,这可能在用户看到我们自定义访问拒绝页面时有用。 @Controller public class LoginLogoutController extends BaseController{ // Ch 6 Access Denied @RequestMapping(method=RequestMethod.GET, value="/accessDenied.do"). public void accessDenied(ModelMap model, HttpServletRequest request) { AccessDeniedException ex = (AccessDeniedException) request.getAttribute(AccessDeniedHandlerImpl .SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY); StringWriter sw = new StringWriter(); model.addAttribute("errorDetails", ex.getMessage()); ex.printStackTrace(new PrintWriter(sw)); model.addAttribute("errorTrace", sw.toString()); } } 注意的是,需要引用AccessDeniedHandlerImpl来获取request中指定名字的属性,它被用来临时存储当前 request范围内的异常。 http://lengyun3566.iteye.com 1.28 《Spring Security3》第六章第四部分翻译(异常处理) 第 163 / 350 页 遗憾的是,AccessDeniedException并没有在它的message中提供足够的细节信息,而这对系统管理员 和用户本身可能会有用。你可能会对使用Spring Security的AccessDeniedException感兴趣或者可能扩展它以 提供更多的上下文信息,以得到当授权检查不通过时用户正在试图进行什么操作。 编写Access Denied页面 控制器写好后,接下来是访问拒绝页面。 <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>

    Access Denied

    Access to the specified resource has been denied for the following reason: ${errorDetails}.

    Error Details (for Support Purposes only):
    ${errorTrace}
    你可以看到我们使用了在控制器设置的模型属性errorDetails和errorTrace。尽管不是很漂亮,但是这个 页面完成了它的任务即用通用的站点导航,将用户带到了站点的其它区域并给他们提供了导致出错的提示信 息。 什么会触发AccessDeniedException 当设计异常处理时,进行一些分析并理解目标异常的原理很重要。对于Spring Security的用户来说,通常 很迷惑的一件事就是AccessDeniedException(默认会导致HTTP 403页面)和AuthenticationException(一 般当用户根本没有登录时抛出)。以下的指南可能会帮助你分清框架在什么时间抛出每种类型的异常: http://lengyun3566.iteye.com 1.28 《Spring Security3》第六章第四部分翻译(异常处理) 第 164 / 350 页 异常类型 谁抛出以及原因 AuthenticationException AuthenticationProvider,当提供的凭 证不合法或用户失效、过期; DaoAuthenticationProvider,当访问 DAO数据存储时出错; RememberMeServices,当remember me cookie被篡改; 各种特定的认证类(CAS、NTLM等)在 用户特定的场景下。 AccessDeniedException AccessDecisionManager,当配置的 Voter投票拒绝访问——注意这可能在任 何投票场景下 要记住的是,我们前面提到的ExceptionTranslationFilter是区分这两种类型异常的关键点,因为这关系 到应用用户的请求和响应流程。 【注意过滤器链中在ExceptionTranslationFilter之前的那些过滤器。ExceptionTranslationFilter只会处理和响 应过滤器链中在此之后的过滤器所抛出的异常。用户可能会感到迷惑,尤其是将自定义过滤器进行了不正确排 序的时候,不明白为什么期望的行为与应用实际的异常处理不一致——在很多场景下,过滤器的顺序是原因所 在。】 尽管内置的处理流程在大多数情况下是可预测且满足需要的,但有时候你会需要自定义异常处理,尤其是 在引入基类异常的自定义子类时,这需要过滤器链的特殊处理。 AuthenticationEntryPoint的重要性 AuthenticationEntryPoint(在ExceptionTranslationFilter我们看到它是工作流程中的一个辅助类)在 处理未认证用户请求中很重要。当ExceptionTranslationFilter确定用户需要认证时,它请求 AuthenticationEntryPoint以了解下一步要做什么。在基于form的认证中, o.s.s.web.authentication.LoginUrlAuthenticationEntryPoint负责将用户定向到登录form。 我们将会在后面的章节中看到AuthenticationEntryPoint被用在各种认证机制中,从而实现更个性化的行 为——例如,在中心认证服务(CAS)单点登录中,AuthenticationEntryPoint要确保用户被定向到CAS门户 进行认证。 http://lengyun3566.iteye.com 1.28 《Spring Security3》第六章第四部分翻译(异常处理) 第 165 / 350 页 在很多环境下,当实现Spring Security与第三方认证系统(独立于web应用)集成时,你需要实现自己的 AuthenticationEntryPoint。 http://lengyun3566.iteye.com 1.28 《Spring Security3》第六章第四部分翻译(异常处理) 第 166 / 350 页 1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设施 的bean) 发表时间: 2011-10-19 关键字: Spring Security, java, 安全 手动配置Spring Security设施的bean 如果你工作要求的环境很复杂而Spring Security的基本功能——尽管非常强大——不能满足所有的要 求,你可能最终需要自己从头构建Spring Security的过滤器链以及支持实施。这是在Spring Security参考手册 中没有完全提及的部分,但是却难住了很多人。有些人将这种类型的配置成为Spring Security的“另一个宇宙 (alternate universe)”。 自己构建并织入所有需要的bean,将为你提供很高的灵活性和自定义功能,这是通过security命名空间的 风格配置所不允许的。 【我们不是开玩笑地说构建所需的bean是很复杂的。即使一个必须的bean都可能需要多达25个单独的 bean。,这需要开发人员明确理解bean之间的依赖关系以及每个bean的所有属性。记住,一旦你不再使用基 于XML命名空间的便利配置方式,就会与Spring Security代码和整体结构更密切相关了。Security XML命名空 间提供了一层很受欢迎的抽象并能很好地满足大多数的需求。】 希望到本书的这个地方,你已经理解了请求处理架构和相关的主要组件,这样的话,面对大量需要配置的 bean才不会感到吃惊。完全理解表面下所有的组件,对于配置每个bean来说是很有用的。 我们将会花些时间来介绍自己搭建Spring Security基础设施时所需要的主要组成部分。注意的是在有些场 景下,当没有必要进行阐述时,我们将会省略一些没有意思bean的细节;但是,完整的配置文件(在本章源码 中的dogstore-explicit-base.xml中)需要支持它。现在让我们进入主要的配置过程。 总体理解Spring Security bean的依赖关系 我们不想马上扎入到配置bean中,而是要给你一个总体了解手动建立Spring Security bean时所涉及的 主要组件。以下是一个依赖图展现了我们要配置的bean以及它们怎样交互的(这个图在本章的源码中包含完整 尺寸的,作为参考)。 需要记住的是,这个图中包含的bean超过了是系统启动和运行所需要的最少值。我们将会渐进的阐述怎样添加 所有的bean,从最小的集合开始,并逐渐构建出与用security命名空间相匹配的功能。 http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 167 / 350 页 重新配置web应用 为了表述清晰,我们为这种风格的配置创建一个全新的XML配置文件。这样我们能够很清晰地看到什么才 是明确需要的,并移除我们在前面章节中注释过的和没注释过的一些部分。 为了做到这些,我们需要重新配置Spring的ApplicationContext指向这个新的文件。我们将这个文件命 名为dogstore-explicit-base.xml,并更新web.xml文件指向它,如下: Dog Store contextConfigLocation /WEB-INF/dogstore-explicit-base.xml 我们不再需要单独dogstore-security.xml文件了,这个文件是我们为了使用XML security命名空间声明而创建 的。我们大多数的配置将会使用Spring标准的bean注入语法,但有少量的security命名空间装饰器在里面。 配置一个最小的Spring Security环境 我们将会以能使系统重新运行起来的最小的配置开始——这意味着没有remember me、logout以及异常 处理功能。这使得我们可以聚焦于Spring Security启动的最小需求。 首先,我们需要声明Spring Security拦截请求时所使用的servlet过滤器链,如下: http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 168 / 350 页 你可以看到这里我们已经引用了security命名空间。尽管可以使用手动的方式来配置需要bean的属性以建立路 径模式匹配和过滤器列表组合,但是使用security命名空间的filter-chain-map包装器更简便和便利。如果将这 与风格的配置进行对比的话,我们要注意以下的配置元素: l 默认过滤器链的建立是在处理元素的时候自动包含的并不需要直接配置。尽管使用security命名空间 的重写或扩展标准过滤器链的时候,允许很大程度的灵活性,但它并不能够得到 FilterChainProxy本身。 l 基于URL模式修改过滤器连并不适用于风格的声明。如果应用的某些部分不需要特定的处理这将会有 用处,并且能使得过滤器的调用尽可能得少。 需要意识到很重要的一点是,不同于Spring的一些配置(比较明显的如,在web.xml中的 contextConfigLocation),在过滤器的名字之间需要需要使用逗号分隔。 【过滤器的顺序很重要——正如我们在第二章中所讨论的那样,特定的过滤器必须在另一些的前面。除非你有 特殊的需求,请参考第二章中的表格,当你添加标准的过滤器到一个手动配置的过滤器链中时,要保证它们在 合适的位置。过滤器被包含在不正确的位置可能会导致不可预知的应用行为,而这是很难调试的。】 你会意识到元素引用了很多逻辑bean definitions,而它们还没有进行定义。现在对它们进 行定义,并逐个进行详细介绍。 配置最少的servlet过滤器集合 为了支持上面描述的过滤器链,我们要设置两类的对象。 首先,servlet过滤器本身必要要进行设置。它们定义了进入web应用的用户请求是如何处理的——与使 用security命名空间不同的是我们更接近底层本质,并需要明确定义在以前简单配置时隐藏在背后的过滤器。 其次,servlet过滤器依赖一系列提供支持功能的安全基础设施bean。它们中的一些类对你来说可能比较 熟悉,因为我们从第一章到第五章已经从架构和功能性的视角讲到了它们,但是这些bean的配置方式是全新 的。 我们将从需要的过滤器开始。 http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 169 / 350 页 SecurityContextPersistenceFilter SecurityContextPersistenceFilter用来建立SecurityContext,而它被用来贯穿整个request过程以跟踪 请求者的认证信息。你可能记得我们在上一章的Spring MVC代码中,为了得到当前认证过的Principa时,访问 过SecurityContext对象。 包含默认适当web session管理的SecurityContextPersistenceFilter基本配置如下: 如果你有时间深入研究这个类你会发现它有一个范式(译者注:原文为pattern,个人理解是用户名、密码、url 等配置)——其实这里还有更多的可配置内容。同样的,在基本配置能够运行后,我们将会再次介绍它。我们 引用了一个名为customAuthenticationManager的bean——这就是与使用security命名空间的 元素自动配置相同的AuthenticationManager。我们稍后将会配置这个bean。 http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 170 / 350 页 AnonymousAuthenticationFilter 我们的站点允许匿名访问。尽管对于比较特殊的条件AnonymousAuthenticationFilter并不需要,但是通 常情况下会使用它,因为只对请求添加了一点的预处理。你可能并不认识这个过滤器,除了我们在第二章对其 简短提到以外。这是因为对于AnonymousAuthenticationFilter的配置都掩盖在security命名空间之中。 这个过滤器的最小配置如下: 列出的这两个属性都是需要的。userAttribute属性声明了为匿名用户提供的用户名和GrantedAuthority。用 户名和GrantedAuthority可能在我们的应用中原来验证用户是不是匿名用户。Key可能是随机生成的,但是需 要在一个bean中使用(o.s.s.authentication.AnonymousAuthenticationProvider),我们稍后将会进行配 置。 FilterSecurityInterceptor 在我们基本处理过滤器链的最后一个是最终负责检查Authentication的,而这是前面已配置的安全过滤器 的处理结果。正是这个过滤器确定一个特定的请求最终是被拒绝还是被接受。 让我们看一下这个过滤器的配置并了解这个练习与其对应的security命名空间进行比较。 在继续阅读之前请思考一下。没错,它看起来和我们在使用security命名空间的配置的声明一样。模式匹配和访问声明格式完全一致,但是你会发现在本例中我们使用了一些配置魔法来使用相 同的声明在上下文中建立正常的Spring bean属性。 元素负责配置FilterSecurityInterceptor会用到的 SecurityMetadataSource的实现,包含URL声明以及可以访问所需要的角色。 【有效使用XML命名空间。对于不熟悉Spring高级配置的用户来说到这里通常会比较迷惑。在配置文件中, Spring XML配置有效使用了XML命名空间来提供清晰的基于组件所有的不同元素。标示元素在一个命名空间中 需要以一个冒号(:)做前缀,后面跟着元素的名字。所以如果我们看,我们可以看到 这个元素的名字是intercept-url,并在名为security的XML命名空间里。XML命名空间的通常在XML文件的顶 部声明,带有一个任意的前缀设置并关联一个URI。例如,security命名空间可以声明为 xmlns:security=http://www.springframework.org/schema/security。没有声明命名空间的元素呢?它们将 会被分配为XML文档的默认命名空间。默认的命名空间通过xmlns属性来标示——在dogstore-explicit- base.xml文件中,默认的命名空间被声明为xmlns="http://www.springframework.org/schema/beans"。没 有命名空间前缀的元素将会被自动设置为默认的命名空间。理解XML命名空间实际如何运行将会使你在构建复 杂Spring和Spring Security配置文件时免去很多头疼之苦。】 我们已经为明确设置的最小化过滤器链配置完了所有过滤器。这些过滤器不能独立存在——还有几个需要 的支持对象。 配置最少的支持对象集合 为了建立最小配置所需要的支持对象是我们在前面的章节中已经配置过的(除了一个)Spring bean—— 所以我们将会节省一些时间来介绍它们的用处。 以下为一系列的bean定义,它们是为了完成最小的支持对象集合和启动应用的: http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 172 / 350 页 这个配置为我们提供了一个最小的配置支持匿名浏览、登录以及数据库后台的认证(注意的是为了简洁我们省 略了需要的jdbcUserService和dataSource beans——这些bean的定义与前面的定义没有什么变化)。记住, AnonymousAuthenticationProvider的key属性必须与我们前面定义的AnonymousAuthenticationFilter的 key属性相匹配。 有一个bean需要,但是我们前面从来没有配置过的(这是因为security命名空间配置不允许这样做)就是 AuthenticationManager。我们可以定义这个bean如下: http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 173 / 350 页 AuthenticationManager的配置在这里看起来很简单,但是明确配置这个bean是很多有用增强和扩展框架的 关键,这些我们将会在本章的剩余部分讲解。 在手动配置bean的所有工作完成后,我们的站点依旧不支持我们用security配置时的一些功能,包括退出 功能、友好的异常处理、密码salting以及remember me。所以,这些为什么还有价值呢?(译者注:作者的 这个反问应该指的是手动配置的意义在哪里。) http://lengyun3566.iteye.com1.29 《Spring Security3》第六章第五部分翻译(手动配置Spring Security设 施的bean) 第 174 / 350 页 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean的 高级配置) 发表时间: 2011-11-22 关键字: Spring Security, 安全, 翻译, Java EE Spring Security基于bean的高级配置 正如我们在前面几页中看到的那样,基于bean的Spring Security配置尽管比较复杂,但是提供了一定层次的灵 活性,如果复杂应用需要超过security XML命名空间风格配置所允许的功能时会用到。 我们将利用这个章节来阐明可用的一些配置选项以及怎么使用。尽管我们不能提供每个可能属性的细节, 但是我们鼓励基于在本章和以前章节学到的内容你开始探索并查询Javadoc和Spring Security的源码。你仅仅 会受限于对这个框架是如何组织起来的知识和你的想象力。 Session生命周期的调整元素 Spring Security有很多地方影响用户的HttpSession的生命周期。有很多功能只有将相关类配置成Spring bean时才可用。以下的表格列出了能够影响session创建和销毁的bean属性: Class(类) 属性 默认值 描述 AbstractAuthentication ProcessingFilter (UsernamePasswordAuthenticationFilter 的父类) allowSessionCreation true 如果为true,当 认证失败时创建 一个新的 session(存储异 常) UsernamePasswordAuthenticationFilter allowSessionCreation true 如果为true的 话,这个特殊的 过滤器将会创建 一个session存储 最后尝试的用户 名。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 175 / 350 页 SecurityContextLogoutHandler invalidateHttpSession true 如果为true, HttpSession将 会失效(参考 Servlet规范了解 session失效的细 节) SecurityContextPersistenceFilter forceEagerSessionCreation false 如果为true,该 过滤器将会在执 行链中其它过滤 器之前创建一个 session。 HttpSessionSecurityContextRepository allowSessionCreation true 如果为true,如 果在请求结束时 session中还没有 SecurityContext 的话, SecurityContext 将存储到session 中。 取决于你应用的需要,需要审慎分析用户session——包括认证的和非认证的——并调整相应的session 生命周期。 手动配置其它通用的服务 还有一些其它的服务,我们已经在security命名空间自动配置中使用了。让我们将其添加上,这样就会有 一个完整的基本配置实现。 声明缺失的过滤器 我们将要增强手动配置的过滤器链,添加三个我们还没有配置的服务。包含处理退出、remember me以 及异常转换。一旦我们完成这些过滤器,我们将会有完整的功能并可以在它启动时深入了解一些有趣的配置选 项。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 176 / 350 页 以下缺失的过滤器将会添加到我们的过滤器链中: 正如在前面做的那样,现在我们需要声明这些Spring bean并配置他们以及所有依赖的服务bean。 LogoutFilter LogoutFilter的基本配置(它默认用来响应虚拟URL /j_spring_security_logout)如下: http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 177 / 350 页 你会发现构造LogoutFilter的方法与其它的认证过滤器都不一样,使用了构造方法而不是bean属性。第一个构 造参数是用户退出后要转向的URL,第二个参数是对LogoutHandler实例的引用。 【依赖注入中的构造器哲学。如果你使用Spring有一段时间了,可能已经讨论过以适当的面向对象方式管理需 要的依赖。尽管setter方式对Spring的用户来说很便利,但是追求纯正面向对象的人经常会认为如果一个依赖影 响到类的功能,那它应该是构造方法签名的一部分(思考一下在Spring以前你是怎样写类的)。鉴于Spring Security已经发展了有些年头,不同的作者对此有不同的观点,而我们碰巧正好遇到一个或两个这方面的不一 致。两种风格的依赖模型都可以——到底使用哪种风格其实取决于你喜欢哪种类型的需求依赖模型。】 我们还要声明一个简单的LogoutHandler的如下: 你可能认出这个LogoutHandler在我们前几页关于session处理的表格中出现过。一个明确配置logout处理器 的好处就是你能修改用户退出时默认的session处理行为。一旦我们完成这两点的配置,Log Out链接就能够再 次开始工作了。 请记住在第三章security命名空间配置中,我们曾经修改应用使用一个不那么明显绑定Spring Security的 退出URL。当我们从security命名空间配置方式修改为明确bean定义时,这是我们在这个bean定义时重写 filterProcessesUrl属性的原因,以保持应用的配置保持持续性。 RememberMeAuthenticationFilter 你可能也会回忆起在第三章中使用的remember me功能。即使在基于bean的配置中,我们也要保持这个 有用的remember me功能。这关系到一个新的过滤器、新的支持bean以及修改一些其它的bean。首先让我们 看一下这个过滤器: 你会发现对rememberMeServices的引用,而它还没有定义。现在让我们定义这个bean: http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 178 / 350 页 RememberMeServices实现的很多配置属性与security命名空间风格的配置很相似。这两种方法的最大不同是 RememberMeServices从应用配置的细节中抽象出来了(译者注:即在security方式的配置中,已经进行了一 些抽象),但是在基于bean的配置中,bean声明必须非常了解在remember me功能中所关联的所有bean。如 果你感到迷惑,请回顾第三章到第四章以了解remember me功能所关联的类和流程的细节。 我们有另一个bean应用来织入remember me的token存储。这个bean定义很简单,如下: 最后我们需要声明AuthenticationProvider,它负责处理remember me认证请求。它的声明如下: 你可能会记得这个key属性在AuthenticationProvider(用来进行token校验)和RememberMeServices(用 来进行token生成)之间共享。要保证它们被设置成相同的。你可能想通过一个properties文件来设置这个属性 值,那是要PropertyPlaceholderConfigurer工具类(或其它类似的)。 现在我们需要将这个AuthenticationProvider织入到AuthenticationManager的列表中。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 179 / 350 页 RememberMeServices必须织入到UsernamePasswordAuthenticationFilter,这样RememberMeServices 就能处理明确的登录成功(remember me cookie被更新)和失败(remember me被移除)。(译者注:其 实查看源码在登录失败时,没有移除cookie的操作) 最后要记住的一件事(在配置Spring Security bean声明时,用户经常遗忘)RememberMeServices也是 LogoutHandler功能的一部分,它在用户退出时清理用户的cookie。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 180 / 350 页 当这些配置到位,我们就完成了明确织入remember me功能。你可能没有想到过声明在幕 后做了多少的工作。 ExceptionTranslationFilter 在Spring Security标准的过滤器链中最后一个servlet过滤器是ExceptionTranslationFilter。从本章我们 前面的讨论,回忆一下这个过滤器在认证和授权整个流程中的重要性。最后的这个过滤器需要一个过滤器定义 和两个支持bean。 支持bean的声明如下: 你可能会认出errorPage指令定义的Access Denied页面就是本章前面访问拒绝配置练习中的那个地址。类似 的,loginFormUrl对应于我们在前面看到的security命名空间中的login-page属性。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 181 / 350 页 明确配置SpEL表达式和投票器 让我们看一下与security命名空间中use-expressions="true"属性等同的Spring bean配置: 接下来,我们需要建立投票器(Voter)指向表达式处理器,如下: 最后,我们需要使AccessDecisionManager bean使用这个投票器。 在完成这些配置以后,我们回到了使用use-expressions="true"默认设置的那个点上(译者注:即与使用命名 空间配置use-expressions="true"一样的效果)。但是,既然我们使用了明确配置方式,那么可以使用自定义 的类替换默认的某个或全部类。我们将会本章后面的部分看一个这样的例子。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 182 / 350 页 基于bean配置方法安全 在第五章中,我们使用security命名空间声明的方式配置过方法安全。迁移 到明确的、基于bean的配置时,我们必须配置需要的主要类和支持类以重写声明 的功能。 我们在第五章中,已经开发出完整的以bean配置的方法安全,支持各种类型的注解。鉴于这个配置很简 单直接,基本上没有什么有意思的属性,所以我们将完整的配置放在附录:参考资料中以及本章的dogstore- explicit-base.xml配置文件里面。 包装明确的配置 到此时,我们已经完成了配置基于bean的Spring security,应用也是功能完整的了。如果你想体验 security命名空间风格的配置与bean声明的配置,很容易修改web.xml中对Spring配置文件的引用从而把一系 列配置文件的集合切换到另一个。 l security命名空间的配置文件为web.xml引用的dogstore-base.xml和dogstore-security.xml; l 基于bean的配置文件为web.xml引用的dogstore-explicit-base.xml。 从此往后,本书的练习中将会假设你使用的是security命名空间配置。如果特定的功能只能使用基于bean 的配置,我们将会明确说明。我们也会包含一些基于bean配置的细节,因为我们知道一些开发人员更愿意 拥有这个级别上对安全框架的控制。 我们应该选择什么类型的配置呢? 希望你的大脑没有从我们刚才的所有配置中变迷糊。你可能会想知道,对于一个典型的工程,应该选择什 么类型的配置呢。正如你可能预料的那样,答案是取决于你项目的复杂性以及你重写或自定义Spring Security 元素的程度。 配置类型 好处 security命名空 间 l 强大、简洁语法,适用于通常的web和方法安全配置; l 用户配置复杂功能时,并不需要知道组件在幕后是如何交互 的; l security命名空间进行代码检查和并警告多种潜在的配置缺 陷; http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 183 / 350 页 l 明显减少缺失配置步骤的可能性。 明确的bean定义 l 允许最大灵活性以扩展、重写以及删节标准的Spring Security 功能; l 允许自定义过滤器链及根据URL模式(使用元 素的pattern属性)的认证方法。这可能在混合web service或 REST认证以及用户认证时用到; l 不用直接将配置文件绑定到Spring Security命名空间处理上; l 认证管理器可以明确配置或重写; l 与更简单的security命名空间相比,暴露了有更多的配置选 项。 对于大多数项目,比较明智的是以security命名空间开始,并在可能的情况下继续使用直到你的应用需要 它不满足的功能。要记住的是,自己配置所有需要的bean和bean间的依赖关系将会明显增加复杂度,在开始之 前,你应该对Spring Security的结构(可能还包括底层代码)有清晰的理解。 http://lengyun3566.iteye.com 1.30 《Spring Security3》第六章第六部分翻译(Spring Security基于bean 的高级配置) 第 184 / 350 页 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 发表时间: 2011-11-22 关键字: Spring Security, Java EE, 安全, 翻译 认证事件处理 有一个重要的功能只能通过基于bean的配置就是自定义处理认证事件。认证事件使用了Spring的时间发 布机制,它基于o.s.context.ApplicationEvent事件模型。Spring事件模型使用并不广泛,却能够很有用处—— 特别在认证系统中——如当你想绑定特定行为到认证领域的行动上去的时候。 事件是典型的订阅-发布模式,通知订阅者是Spring运行环境自己处理的。比较重要的一点是,在默认情 况下Spring的事件模型是同步的,所以有任何订阅监听的运行时会直接影响产生事件请求的性能。 在ApplicationContext初始化的时候,Spring将会检查所有配置的bean是否存在 o.s.context.ApplicationListener接口。这些bean的引用将会被 o.s.context.event.ApplicationEventMulticaster持有,它会在o.s.context.ApplicationEventPublisher发布事 件时,管理运行时事件的发布。这个设施已经存在很长时间了(从Spring1.1),所以你若想更深入了解Spring 的这个领域,有很多文档可查。 下图阐述了事件发布流程是如何组织在一起的: 因为在Spring Security内部没有广泛使用认证事件(实际上,唯一明显用的地方就是我们本章前面讨论的 session并发跟踪),自定义认证事件的监听器是一种实现审计、管理报警甚至复杂用户行为追踪的便利方式。 让我们了解一下配置简单安全事件监听的过程。 配置认证事件的监听器 使用简短的security命名空间配置,你不能配置认证事件监听器——它必须使用基于Spring bean的方式 因为ApplicationEventPublisher的实现类默认不会启用,必须织入到AuthenticationManager中。 声明需要的bean依赖 我们首先声明ApplicationEventPublisher的实现类,如下: http://lengyun3566.iteye.com 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 第 185 / 350 页 接下来,我们将会把它织入到使用的AuthenticationManager中: 这就是全部需要的配置。如果此时你重启应用,你将会什么也看不到。这是因为我们还没有创建bean来监听发 布的时间。现在,我们就做这件事。 构建自定义的应用事件监听器 ApplicationListener的实现类很简单,并且使用Spring 3加入的强大Java泛型功能支持类型安全。我们的 ApplicationListener只是简单记录收到的事件到标准输出中,但是在后面的练习中我们将会体验一个更有趣的 例子。 自定义的ApplicationListener如下: package com.packtpub.springsecurity.security; // imports omitted @Component public class CustomAuthenticationEventListener implements ApplicationListener { @Override public void onApplicationEvent(AbstractAuthenticationEvent event) { System.out.println("Received event of type: http://lengyun3566.iteye.com 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 第 186 / 350 页 "+event.getClass().getName()+": "+event.toString()); } } 你会发现我们这里使用了@Component注解,但是我们也可以在XML配置文件中简单声明一个Spring bean。 记住,ApplicationListener的实现类必须注明对什么类型的事件感兴趣,在Spring 3中通过在 ApplicationListener接口引用中声明泛型来标注。Spring的ApplicationEventMulticaster使用一些巧妙的方法 来检查类的接口实现声明并确保正确的事件到达正确的类中。 【巧妙的注解。你如果对复杂的注解处理和运行时检查注解感到好奇,毫无疑问那你应该查看使用Spring的 o.s.context.event.GenericApplicationListenerAdapter分发ApplicationEvent事件的巧妙代码。看一下并学 习一些Java反射的新技巧。】 重启应用,然后进行一些常用的行为如登录、退出以及登录失败。你能看到,当这些行为执行时,适当的 时间被触发并打印在控制台上。 尽管我们声明了自己的ApplicationListener来接受所有的认证事件,但是在大多数场景下这并不实用,根 据系统使用情况的,在一个小时内可能会有数千个时间触发。通过修改类implements关键词上的泛型标示,我 们能够使得实现类至监听一种类型的事件。 内置的ApplicationListeners Spring Security提供了两个ApplicationListener的实现类,它们绑定了在Spring Security中使用的 Apache Commons Logging日志。两个ApplicationListener实现类,一个是负责认证事件,一个负责授权时 间。你可以像以下代码那样配置它们: 这两个监听器将会输出Commons Logging日志以对应的类命名。一个示例记录 AbstractAuthenticationFailureEvent大致如下: http://lengyun3566.iteye.com 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 第 187 / 350 页 WARN - Authentication event AuthenticationFailureBadCredentialsEvent: adb; details: org. springframework.security.web.authentication.WebAuthenticationDetails@2 55f8: RemoteIpAddress: 127.0.0.1; SessionId: B20510F25464B109CE3AE94D9 FBF981E; exception: Bad credentials 如果你要实现类似的ApplicationListener来记录有用的事件,这些类可以作为模板。 大量的应用事件 Spring Security提供了很多的事件,其试图在用户认证请求的所有点上给出有用的信息。你的应用可以监 听可用的各种事件,这个范围可以很广泛(所有认证失败)也可以很窄小(一个用户通过提供完整的凭证认证 成功)。完整的事件列表在附录:参考资料中。一些其它的关于异常处理和事件监听的注意事项如下: l 授权事件和框架抛出异常的匹配关系可以通过DefaultAuthenticationEventPublisher的 exceptionMappings属性配置; l 记住,正如我们在本章前面看到的,跟踪HttpSession的生命周期是通过web.xml配置的变化,而不直接是 Spring。 你可以看到Spring Security的异常和事件处理很强大,允许在你的安全系统中进行很多场景的跟踪和对活 动的响应。 构建一个自定义实现的SpEL表达式处理器 我们将会阐述一个扩展基本SpEL表达式处理器的简单例子,提供一个表达式如果当前日期的分钟数为偶 数将会允许访问。尽管这是一个很牵强的例子,但是它描述了实现自定义SpEL表达式方法的所有步骤。 让我们创建一个类com.packtpub.springsecurity.security.CustomWebSecurityExpressionRoot以建立 自定义扩展的WebSecurityExpressionRoot。 public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot { public CustomWebSecurityExpressionRoot (Authentication a, FilterInvocation fi) { super(a, fi); } public boolean isEvenMinute() { return (Calendar.getInstance().get(Calendar.MINUTE) % 2) == 0; http://lengyun3566.iteye.com 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 第 188 / 350 页 } } 接下里,我们需要一个实现WebSecurityExpressionHandler的类。我们扩展了 DefaultWebSecurityExpressionHandler并重写一个方法在 com.packtpub.springsecurity.security.CustomWebSecurityExpressionHandler类中建立自己的 CustomWebSecurityExpressionRoot。 public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler { public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) { StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(authentication, fi); SecurityExpressionRoot root = new CustomWebSecurityExpressionRoot(authentication, fi); ctx.setRootObject(root); return ctx; } } 最后,当建立Voter时需要重新配置bean引用,如下: 现在,我们可以使用这个表达式来根据时间的分钟数是偶数还是奇数进行限制访问。 http://lengyun3566.iteye.com 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 第 189 / 350 页 很显然,这是一个简单的例子,但是阐述了实现自定义SpEL属性的基本步骤,你可以使用这种方式来控制对应 用特定部分的访问。 【配置自定义SpEL Voter的技术可能在使用security命名空间的时候也会用到,只需使用access-decision- manager-ref属性定义一个自定义的AccessDecisionManager,就像我们在第二章见过的那样。】 小结 在本章中,我们介绍了Spring Security标准配置的功能并实现了一些高级的自定义功能。我们涉及到以下 的内容: l 实现自定义的servlet过滤器来处理基于IP和角色的过滤以及基于HTTP头的SSO请求; l 添加一个自定义的AuthenticationProvider及支持类,从而实现HTTP请求头的SSO; l 了解session固化防护和session并发处理的配置及好处,包括一些间接的功能以允许用户进行session报告; l 配置自定义的访问控制拒绝处理并了解何时及为何AccessDeniedException会被抛出,还有怎样适当地响应 它; l 替换Spring Security的自动化配置为手动声明所有需要的参与类,这借助于标准的Sping bean XML配置技 术; l 了解一些基于Spring bean配置的高级功能,包括session管理和事件发布; l 实现自定义和内置的ApplicationListener来响应Spring Security框架发布的特定事件; l 实现标准SpEL表达式处理器的自定义扩展以允许个性化的URL访问表达式。 多有趣的一章!我们已经很习惯Spring Security,并进行了一些高级的扩展和自定义。 在第七章中,我们将会进行高级配置的旅程,通过使用访问控制列表使得实现复杂认证变得可能。 http://lengyun3566.iteye.com 1.31 《Spring Security3》第六章第七部分翻译(认证事件处理与小结) 第 190 / 350 页 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 发表时间: 2011-12-30 关键字: Spring Security, java, 安全, 翻译 第七章 访问控制列表(ACL) 在本章中,我们将会介绍访问控制列表这个复杂话题,它能够提供域对象实例层次授权的丰富模型。 Spring Security提供了强大的访问控制列表,但是复杂且缺少文档,它能够很好的满足小到中型规模的实 现。 在本章的内容中,我们将会: l 理解访问控制列表的概念模型; l 了解Spring Security ACL模型中的关于访问控制列表的术语和应用; l 构建和了解支持Spring ACL的数据库模式; l 配置JBCP Pets通过注解和Spring bean来使用ACL保护业务方法; l 进行高级配置,包括自定义许可、使用ACL的JSP标签检查和方法安全,易变的ACL以及缓存; l 了解架构要考虑的因素以及规划ACL部署的场景。 译者注:在本章中术语permission翻译为许可权限;entry翻译为条目;access control entry即为访 问控制条目。 使用访问控制列表保护业务对象 最后一个非web层安全的问题是业务对象层次的安全,它在业务层或业务层以下。这个层次的安全实现使 用了一项名为访问控制列表(access control list,或ACL)的技术。ACL中的对象可以进行权限判断——ACL 允许基于唯一的组、业务对象以及逻辑操作进行特定的许可。 例如,在JBCP Pets中的一个ACL声明可能会是这样的:用户只能对自己的简介进行写操作。可能像下面 展现的这样: http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 191 / 350 页 用户名 组 对象 许可权限 amy Profile123 read,write ROLE_USER Profile123 read ANONYMOUS Any Profile none 你会发现这个ACL对人工来说很易读——Amy对自己的简介(Profile123)能够进行读写;其它的注册用 户能够读Amy的简介,而匿名用户不能。简单来说,这种类型的规则矩阵就是ACL所试图做的,即将代码、访 问检查、安全系统和业务数据的元数据进行集成。大多数真正使用ACL的系统会有很复杂的ACL列表以及整个系 统中百万级的条目。尽管这听起来有令人感到害怕的复杂性,但是预先适当的思考和使用良好的安全库能够使 得ACL管理相当可行。 如果你使用Microsoft Windows或者基于Unix/Linux的电脑,那你每天都在体验ACL。大多数现代电脑 的操作系统都使用ACL指令作为文件存储系统的一部分,这允许基于用户或组、文件或目录以及许可权限的组合 进行许可授权。在Microsoft Windows中,你能通过右键点击一个文件并查看其安全属性的方法(属性|安全标 签)看到ACL功能: 你可以看见各种的组或用户以及权限,从而能够以可视化和很直观的方式输入ACL。 Spring Security中的访问控制列表 在安全系统中,Spring Security支持ACL驱动的授权检查用户访问某个域对象。与OS文件系统的例子很 相似,可以使用Spring Security ACL组件构建业务对象以及组或安全实体的逻辑树结构。基于请求者和被请求 对象得到的许可授权(继承或明确声明)被用来确定是否允许访问。 对于接触Spring Security ACL功能的用户来说,很可能被它的复杂性和缺乏文档、例子所吓倒。再加上 建立ACL的基础设施很复杂,需要很多的相互依赖以及与Spring Security其它地方不同的基于bean的配置机制 (我们等会建立初始配置的时候,你会看到)。 Spring Security的ACL模型比较基础,但是试图构建扩展功能的用户可能会发现一些令人沮丧的限制并且 Spring Security早期引入的一些设计决策已经不合时宜。不要让这些限制把你弄得灰心。ACL模型是一个往你 应用中嵌入丰富访问控制的强大方式,并且会进行仔细的审查以保护用户的行为和数据。 在我们开始配置Spring Security ACL之前,我们需要了解一些关键的术语和概念。 http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 192 / 350 页 在Spring ACL系统中,主要的安全角色标识是security identity或SID。SID是一个逻辑组成,能够被用来 抽象一个单独的安全实体或组(GrantedAuthority)。ACL数据模型定义的SID被用做确定安全实体访问级别的 基础,而这个访问规则可以是明确指明的也可以是继承下来的。 如果SID被用作确定ACL系统的角色,那对应的另一半安全相关的就是安全对象本身了。单个的安全对象 标识被称为(毫无意外的)对象标识(object identity)。默认的Spring ACL实现中,对象标识需要ACL规则 定义在对象实例层级,这就意味着,如果需要系统中的每一个对象都能有单独的访问规则。 单个的访问规则就是所谓的访问控制条目(access control entries或ACEs)。一个ACE包含以下的元 素: l 规则应用的角色SID; l 规则应用的对象标识; l 应用于给定SID和对象标识的许可权限; l 给定的许可权限对于特定的SID和对象标识应该允许还是拒绝(译者注:个人理解,此处指的是给定一个权限 能够判断出是否允许访问)。 Spring ACL系统作为一个整体,其目的是评估每一个方法调用并确定方法中的对象针对每一个ACE是 否可用。适当的ACE在运行时进行评估,这要基于调用者和使用的对象。 【Spring Security ACL在实现上是灵活的。尽管本章的内容大多数都是Spring Security模块内置的功 能,但是要记住的是很多的规则是默认实现,在很多情况下能够基于复杂的需求进行重写。】 Spring Security使用一些有用的值对象来描述相关联的每一个概念实体。它们列到了以下的表格中: ACL概念对象 Java对象 SID o.s.s.acls.model.Sid 对象标识(Object Identity) o.s.s.acls.model.ObjectIdentity ACL o.s.s.acls.model.Acl http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 193 / 350 页 ACE o.s.s.acls.model.AccessControlEntry 让我们通过JBCP Pets store应用的一个例子来了解使用Spring Security ACL组件的过程。 支持Spring Security ACL的基本配置 尽管我们前面提到过在SpringSecurity中配置支持ACL需要基于bean的配置(它的确如此),但是你可以 使用ACL却保持较为简单的security XML命名空间配置(译者注:即ACL需要基于bean的配置,但是原有的 security命名空间配置可以保留)。如果你要运行示例代码,你需要切换web.xml到security命名空间配置,使 用dogstore-base.xml和dogstore-security.xml。 定义一个简单的目标场景 我们一个简单的目标场景是不允许没有ROLE_ADMIN GrantedAuthority权限的用户访问主页上的第一个 分类。第一个分类被称为“Pet Apparel”——这就是我们要用ACL安全保护的分类。 尽管有多种方式建立ACL检查,但是我们喜欢在第五章用到过的基于注解实现方法安全的方式。它很好的 把使用ACL从实际接口声明中抽取出来,并允许你以后将角色声明替换为(如果愿意)除了ACL的其它方式。 我们将要在IProductService.getItemsByCategory方法上添加一个注解,它需要触发这个方法的任何人 有适当的角色来查看这个分类: @Secured("VOTE_CATEGORY_READ") public Collection getItemsByCategory(Category cat); 这个方法被JBCP Pets站点的分类查看页面调用,而这个页面可以通过点击主页上的分类进入: 让我们看是进行配置的变化! 添加ACL表到HSQL数据库中 我们需要做的第一个事情是添加支持持久化ACL条目的表到我们的内存HSQL数据库中。要做到这一点, 我们添加一些新的SQL DDL文件到我们的嵌入式数据库声明中,在dogstore-security.xml里: http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 194 / 350 页 acl-schema.sql文件会放在WEB-INF/classes文件夹下(或在应用类路径下的其它位置),并包含以下内容: create table acl_sid ( id bigint generated by default as identity(start with 100) not null primary key, principal boolean not null, sid varchar_ignorecase(100) not null, constraint uk_acl_sid unique(sid,principal) ); create table acl_class ( id bigint generated by default as identity(start with 100) not null primary key, class varchar_ignorecase(500) not null, constraint uk_acl_class unique(class) ); create table acl_object_identity ( id bigint generated by default as identity(start with 100) not null primary key, object_id_class bigint not null, object_id_identity bigint not null, parent_object bigint, owner_sid bigint not null, entries_inheriting boolean not null, constraint uk_acl_objid unique(object_id_class,object_id_identity), constraint fk_acl_obj_parent foreign key(parent_object)references acl_object_identity(id), constraint fk_acl_obj_class foreign key(object_id_class)references acl_class(id), http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 195 / 350 页 constraint fk_acl_obj_owner foreign key(owner_sid)references acl_ sid(id) ); create table acl_entry ( id bigint generated by default as identity(start with 100) not null primary key, acl_object_identity bigint not null, ace_order int not null, sid bigint not null, mask integer not null, granting boolean not null, audit_success boolean not null, audit_failure boolean not null, constraint uk_acl_entry unique(acl_object_identity,ace_order), constraint fk_acl_entry_obj_id foreign key(acl_object_identity) references acl_object_identity(id), constraint fk_acl_entry_sid foreign key(sid) references acl_sid(id) ); 这将会生成如下的数据库模式: 你能够看到SID、对象标识以及ACE的概念如何匹配到数据库模式中。概念上讲,这很方便,因为我们能够匹配 ACL系统的模型并直接将其关联到数据库中。 如果你将这与Spring Security文档中提供的HSQL数据库模式进行对比,你会发现我们进行了一些改变, 这可能会使用户走弯路。如下: l 修改ACL_CLASS.CLASS列从100个字符到500个字符。一些满足要求的长类名可能超过100个字符; l 外键的名字更有意义,这样失败能够更容易的进行诊断。 【如果你使用其它的数据库,如oracle,你需要将这个DDL转换成你特定数据库的数据类型。】 一旦我们配置完ACL系统的其它部分,我们将会回到数据库这边建立一些基本的ACE以证明ACL的基本功 能。 http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 196 / 350 页 配置访问决策管理器 我们需要配置以启用注解(我们将在这里注明基于ACL的权限)并引用一个自 定义的访问决策管理器(access decision manager)。 【用于ACL检查的访问决策管理器必须与用于检查web URL授权规则的进行区分。这个需求源于传递给决 策管理器的所有认证检查必须被它所有配置的voter所支持。不幸的是,web请求认证与方法请求认证的处理方 法不一样,所以需要一个单独配置的访问决策器。当我们在第五章:精确的访问控制第一次接触方法安全时, 我们不需要明确进行这个配置变化,因为security命名空间解析所实例化的访问控制管理器足够满足我们的要 求。】 配置访问决策管理器的引用被设置在dogstore-security.xml文件中,如下: 这是对访问控制管理器的一个bean引用,我们定义在dogstore-base.xml文件中: 我们将会在练习的下一步中从投票器开始处理这个引用链。要记住的是这个AccessDecisionManager像其它的 一样,自然也像web认证决策管理器那样,依赖于投票器做授权决策。 配置ACL的支持bean 即使是进行一个相对很简单的ACL配置,就像我们的场景那样,也有许多需要的依赖要建立。正如我们前 面提到的,Spring Security ACL模块自带了很多的组件,你可以对它们进行组装以提供很好的ACL功能。注意 的是,下图所引用的所有组件只是框架的一部分! http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 197 / 350 页 ACL子系统与Spring Security框架其它大部分功能有一个明显的不同就是大多数的配置需要构造方法注入 (constructor injection),而不是属性注入(property injection)。正如我们在第六章:高级配置和扩展中 明确bean配置所讨论的那样,一部分Spring Security在这方面并不一致,强制要求使用构造器以确保需要的属 性被设置——这在配置大多数ACL相关组件时是需要注意的。 让我们从配置categoryReadVoter开始。这是AccessDecisionVoter的实现,它会访问ACL存储(在数据 库中)并进行运行时的认证做出访问决策。 不是明显bean引用的属性需要进行一些说明。第二个构造参数,我们给它一个值为 VOTE_CATEGORY_READ,用来表明这个投票器能够投票的安全属性。你可能会记得这与我们要进行ACL保护 的方法上的@Secured注解匹配。 第三个构造参数以及属性联合起来声明了能够对请求做出一个成功投票的ACL属性。在本例中,声明的投 票器用来对包含VOTE_CATEGORY_READ的方法访问进行授权,请求者必须有ACL条目来表明他允许对 com.packtpub.springsecurity.data.Category域对象进行READ访问。 我们可以看到,ACL投票器的声明是一个抽象层,并且位于代码资源声明的授权要求和域对象对ACL SID 分配许可权限之间。这层抽象使得对安全资源分配有意义的属性方面有了很大的灵活性。 引用aclService的bean处理的是o.s.s.acls.model.AclService的一个实现,它负责(通过代理)将ACL保 护的对象转换成期望的ACE。 http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 198 / 350 页 我们将会使用o.s.s.acls.jdbc.JdbcAclService,它是AclService的一个实现。这个实现是内置的并且使用我们这 个练习上一步定义的数据库模式。JdbcAclService将会使用递归的SQL和事后处理机制来理解SID的等级关系, 并确保等级关系的表述能够传递回AclEntryVoter。 JdbcAclService使用与嵌入式数据库定义时相同的JDBC dataSource,并代理给一个 o.s.s.acls.jdbc.LookupStrategy的实现,而这个实现将真正负责数据库查询以及处理对ACL的请求。Spring Security提供的唯一LookupStrategy是o.s.s.acls.jdbc.BasicLookupStrategy,定义如下: 现在,BasicLookupStrategy是一个很复杂的家伙(beast)。记住,其目标是从数据库中将一系列的 ObjectIdentity转换成实际可用的ACE列表。因为ObjectIdentity的声明可能是递归的,这会是一个很有挑战性 的问题,并且对于高负荷使用的应用要考虑产生的SQL对数据库性能的影响。 【使用最小公分母进行查询。要注意的是BasicLookupStrategy要兼容所有的数据库,这通过严格坚持标准的 ANSI SQL语法实现,要注意的是左[外]连接(left[outer]joins)。一些老的数据库(典型的如Oracle 8i)并不 支持这种连接语法,所以要确保检查SQL的语法和结构是否兼容你特定的数据库。当然还有更高效依赖数据库的 等级查询方法,这要使用非标准的SQL——如Oracle的CONNECT BY语句以及其它很多数据库(包括 PostgreSQL和Microsoft SQL Server)的Common Table Expression(CTE)功能。正如我们在第四章使用 JdbcDaoImpl UserDetailsService自定义模式的例子那样,在使用BasicLookupStrategy的时候也有属性暴露 出来配置SQL。请查询Javadoc和源码本身来了解它们怎么使用,这样它们就能够在你自定义模式中正确使 用。】 我们可以看到LookupStrategy需要引用与AclService相同的JDBC dataSoure。而另外三个引用将会把我 们带到这个依赖链的最后。 http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 199 / 350 页 o.s.s.acls.model.AclCache声明了一个接口依赖缓存ObjectIdentity到ACL的映射,这样就阻止大量(且 代价高昂)的数据库查询。Spring Security只提供了一个AclCache的实现,即使用第三方库Ehcache。简单起 见,在此时的配置中,我们忽略掉配置Ehcache的额外工作,替换为实现一个简单的,不进行任何操作的 AclCache,在类com.packtpub.springsecurity.security.NullAclCache中: package com.packtpub.springsecurity.security; // imports omitted public class NullAclCache implements AclCache { @Override public void clearCache() { } @Override public void evictFromCache(Serializable arg0) { } @Override public void evictFromCache(ObjectIdentity arg0) { } @Override public MutableAcl getFromCache(ObjectIdentity arg0) { return null; } @Override public MutableAcl getFromCache(Serializable arg0) { return null; } @Override public void putInCache(MutableAcl arg0) { } } 这通过一个简单的bean声明来配置: 不要担心,我们将会在本章后面配置基于Ehcache的实现,但是现在我们首先想要关注配置ACL真正需要的组 件。 http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 200 / 350 页 BasicLookupStrategy所要处理的下一个依赖是o.s.s.acls.domain.AuditLogger接口的一个现实,它被 BasicLookupStrategy用来进行审计ACL和ACE的查找。类似于AclCache接口,Spring Security只提供了一个 实现,它简单地在控制台上记录log。我们将用一行的bean声明来配置它: 最后一个要处理的依赖是o.s.s.acls.domain.AclAuthorizationStrategy接口的实现,它在从数据库加载ACL的时候 并没有马上要提供的任何作用。相反,这个接口的实现负责确定运行时对ACL或ACE的修改是否允许,这要基于 修改的类型。当我们涉及到易变的ACL时,会有更详细的介绍,因为这个逻辑过程有些复杂并且与我们初始化配 置完成没有关系。最后的配置如下: 你可能想知道重复引用的aclAdminAuthority是干什么的——AclAuthorizationStrategyImpl提供了三个特殊 的GrantedAuthority名称来允许对ACL进行运行时的特定操作。我们将会本章后面涉及到。 我们最终完成了对Spring Security ACL内置实现的配置。接下来也是最后的一步就是需要我们插入简单 的ACL和ACE到HSQL数据库中,并进行测试。 创建一个简单的ACL entry http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 201 / 350 页 回忆一下,我们简单的场景就是锁定JBCP Pets store的第一个分类,使得只有ROLE_ADMIN授权的原来 才能查看。你可能会发现翻到前几页参考一下模式图对于理解我们要插入什么数据以及为什么会有帮助。 在WEB-INF下建立一个文件test-acl-data.sql,与其它我们在JBCP Pets中使用到的SQL文件放到一起。 本节描述的所有SQL将会加到这个文件中——你可以基于我们提供的实例SQL随便体验并添加更多的测试用例 ——实际上,我们鼓励你使用实例数据进行体验。 首先,我们需要在ACL_CLASS中添加任意或所有的拥有ACL规则的域对象类——在我们的例子中,这就 是我们的Category类: insert into acl_class (class) values ('com.packtpub.springsecurity. data.Category'); 接下来,ACL_SID表插入SID,它将关联到ACE中。要记住的是,SID可以是角色或用户——在我们的应用中简 单插入角色(注意principal列表明一个给定的行是否为单个用户): insert into acl_sid (principal, sid) values (false, 'ROLE_USER'); insert into acl_sid (principal, sid) values (false, 'ROLE_ADMIN'); 开始变得复杂的表是ACL_OBJECT_IDENTITY,它用来声明单个的域对象实例和它们的父对象(如果存 在)以及拥有者的SID。我们插入拥有以下属性的一行数据: l 域对象类型为Category(通过OBJECT_ID_CLASS列外键关联到ACL_CLASS); l 域对象的PK为1(OBJECT_ID_IDENTITY); l 拥有者SID为ROLE_ADMIN(通过外键OWNER_SID column关联到ACL_SID)。 对于主键为1的Category,插入一行的SQL如下: insert into acl_object_identity (object_id_class,object_id_ identity,parent_object,owner_sid,entries_inheriting) select cl.id, 1, null, sid.id, false from acl_class cl, acl_sid sid http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 202 / 350 页 where cl.class='com.packtpub.springsecurity.data.Category' and sid. sid='ROLE_ADMIN'; 要记住的是,在典型的场景下,拥有者SID应该为一个安全实体而不是一个角色。但是,对于ACL系统来说,两 种类型的规则功能是一样的。 最后,我们要对这个只有ROLE_ADMIN角色才能访问的对象实例添加ACE: insert into acl_entry (acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) select oi.id, 1, si.id, 1, true, true, true from acl_object_identity oi, acl_sid si where si.sid = 'ROLE_ADMIN'; 这里的MASK列代表位掩码,它用来给指定对象在特定的SID上进行授权。我们将会在本章后面进行更详细的介 绍——幸运的是,在这里它并不像听起来那么有用。 在SQL文件准备好之后,我们需要扩展声明并添加最终的ACL测试数据文件到数 据库启动中: 现在,我们能够启动应用并运行实例场景。你会发现不是管理员的任何用户试图访问第一个分类“Pet Apparel”,他们被拒绝访问。如果没有经过认证的用户试图访问这个分类,他们将会按照标准的 AccessDeniedException处理(第六章已描述)并要求登录。 现在我们建立了基本的基于ACL的安全(尽管是一个很简单的场景)。接下来我们对这个过程中的概念进 行更多的讲解,然后了解在使用Spring ACL之前两个要考虑的问题。 http://lengyun3566.iteye.com 1.32 《Spring Security3》第七章第一部分翻译(访问控制列表ACL) 第 203 / 350 页 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 发表时间: 2012-01-10 高级ACL话题 一些高级的话题在我们配置ACL环境时略过了,包括处理ACE许可授权,在运行时根据GrantedAuthority 确定某种类型的ACL变化是否允许。既然现在我们已经有了一个运行环境,那我们要开始了解这些更高级的话 题。 Permission如何工作 许可授权(permission)只不过是简单的逻辑标识符用一个整数的二进制位来表示。一个访问控制条目对 于SID的授权是基于逻辑与操作所有应用于这个条目的许可授权得到的位掩码。 默认的Permission实现,即o.s.s.acls.domain.BasePermission定义了一系列的整数值来代表常用的ACL 授权。这些整数值对应于单个位设置为整数,所以BasePermission. WRITE的值,对于的整数为1,按位的值就 是21或2。它们如下图所示: 你可以看到示例的许可授权掩码有二进制整数值为3,这是因为应用Read和Write许可后就会具有这个许可授权 值了。在这个图中,所有标准的单个整数授权值都是在BasePermission中作为静态常量定义的。你可能会回忆 起来我们在构建ACL配置练习中在o.s.s.acls.AclEntryVoter里,使用过它们中的一个常量 BasePermission.READ。 【BasePermission所包含的逻辑常量只是在访问控制条目中经常用到的,并且在Spring Security中没有特殊的 语义。对于非常复杂的ACL实现,创建自己的许可授权是很常见的,以增强独立于域或业务的最佳实践。】 一个经常困扰用户的问题是位掩码实际上是如何应用的,因为很多数据库要么不支持按位逻辑操作要么不 支持可伸缩的方式。Spring ACL通过把按位进行许可授权计算放到应用中来解决这个问题,而不是放在数据库 中。 http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 204 / 350 页 了解这个处理过程很重要,在这里我们能够看到AclEntryVoter怎样处理声明在方法上的许可授权(在我 们的例子中,通过@Secured注解)到真正的ACL授权。下图阐述了针对安全实体的请求,Spring ACL评估声 明的许可权限与相关ACE的过程: 我们可以看到AclEntryVoter依赖实现o.s.s.acls.model.ObjectIdentityRetrievalStrategy和 o.s.s.acls.model.SidRetrievalStrategy接口的实现类,以获取适当的ObjectIdentity和Sids进行认证检查。关 于这些策略有一个很重要的事情就是基于授权认证的上下文,默认的实现类如何决定要返回的ObjectIdentity和 Sids。 ObjectIdentity有两个属性分别为type和identifier,它们是根据运行时要检查的对象得到的,并用来声明 ACE条目。默认的ObjectIdentityRetrievalStrategy使用全类名来填充type属性。identifier属性通过调用实际 对象实例的Serializable getId()方法得到的结果进行填充(译者注:因此进行ACL授权的对象需要有getId这个 方法)。 【为了支持ACL检查你的对象并不需要实现接口,但是需要实现特定签名的一个方法这一点恐怕会让实现 Spring Security ACL的开发人员感到惊讶。请事先规划,并确保你的域对象包含此方法!你也可以实现自己的 http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 205 / 350 页 ObjectRetrievalStrategy(或内置实现的子类)来调用你选择的一个方法。但遗憾的是,这个方法的名字和类 型是不可配置的。】 不幸的是,AclImpl的实际实现直接将配置的Permission与AclEntryVoter进行对比,但是Permission存 储在ACE上在数据库中,不能使用按位逻辑操作。Spring Security社区关于这是否有意为之还有争论,但是不 管怎样,当你声明一个拥有组合许可授权的用户时需要特别小心,要么AclEntryVoter必须配置上所有组合许可 授权要么ACE需要忽略permission域本来可以配置多个值,而是要在每个ACE上只配置一个许可授权。 如果你想在我们简单的场景下校验它,将我们授给ROLE_ADMIN SID的权限由Read许可修改为Read和 Write组合二进制掩码,也就是将其转换成3。这需要修改test-acl-data.sql: insert into acl_entry (acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) select oi.id, 1, si.id, 3, true, true, true from acl_object_identity oi, acl_sid si where si.sid = 'ROLE_ADMIN'; 如果现在你作为管理员访问ACL保护的分类,你会被拒绝,即使我们已经在单个ACE上声明了你可以进行Read 和Write。(译者注:不得不说,在一点上Spring Security ACL有点弱。) 自定义ACL permission声明 正如在讨论许可授权声明时所讲的那样,许可授权(permission)只不过是带有逻辑名的整数值。既然如 此,可以扩展BasePermission来声明自己的许可权限。这里我们会涉及一个很简单的场景,创建一个新的ACL 许可权限名为ADMIN_READ。这个权限只会授予给管理员用户,并分配给只有管理员才能读的资源。尽管这对 于JBCP Pets站点是一个比较牵强的例子,但是这种类型的自定义权限在处理个人信息时很常用(如社会保险号 等——可以回忆下我们在第一章:一个不安全应用的剖析中提到的PII)。 让我们开始要支持这个所要进行的变化。第一步就是用我们的 com.packtpub.springsecurity.security.CustomPermission来扩展BasePermission: package com.packtpub.springsecurity.security; // imports omitted public class CustomPermission extends BasePermission { protected CustomPermission(int mask, char code) { super(mask, code); } http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 206 / 350 页 protected CustomPermission(int mask) { super(mask); } public static final Permission ADMIN_READ = new CustomPermission(1 << 5, 'M'); // 32 } 接下来,我们要扩展o.s.s.acls.domain.PermissionFactory的默认实现 o.s.s.acls.domain.DefaultPermissionFactory,来注册我们的自定义许可权限逻辑值。PermissionFactory的 角色是将许可的二进制掩码转换成逻辑许可值(它可以在应用的其它部分被常量值或通过名字引用,如 ADMIN_READ)。PermissionFactory需要所有的自定义许可权限在这里进行注册以便于查找。 我们实现com.packtpub.springsecurity.security.CustomPermissionFactory class类,如下: package com.packtpub.springsecurity.security; // imports omitted public class CustomPermissionFactory extends DefaultPermissionFactory { public CustomPermissionFactory() { super(); registerPublicPermissions(CustomPermission.class); } public CustomPermissionFactory(Class permissionClass) { super(permissionClass); } public CustomPermissionFactory( Map namedPermissions) { super(namedPermissions); } } 我们可以看到增强了默认的构造方法以调用注册CustomPermission,使其作为一个可用的许可授权。 【在本章的练习中我们不会强调基类的所有可用代码,但是建议你查看父类的其它功能并了解其在ACL系 统中其它方面是如何使用的。例如,我们能够看到buildFromName方法在使用ACL自动以JSP tag中用到,这 部分我们稍后展示。】 http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 207 / 350 页 我们需要配置CustomPermissionFactory并将其织入到BasicLookupStrategy。在dogstore- base.xml文件中要做以下的修改: 现在,我们的自定义ACL权限在ACL框架中可用了。接下来,我们要添加一个新的管理员,他被明确分配这 个权限到第二个分类“Dog Food”上。我们要添加以下内容到test-acl-data.sql上,以完成这个新授权的需 要: -- User SID insert into acl_sid (principal, sid) values (true, 'admin2'); -- Category #2 insert into acl_object_identity (object_id_class,object_id_ identity,parent_object,owner_sid,entries_inheriting) select cl.id, 2, null, sid.id, false from acl_class cl, acl_sid sid where cl.class='com.packtpub.springsecurity.data.Category' and sid.sid='admin2'; -- Give user 'admin2' access to category 2 -- "32" == 1 << 5 insert into acl_entry (acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure) select oi.id, 2, si.id, 32, true, true, true from acl_object_identity oi, acl_sid si where si.sid = 'admin2' and oi.object_id_identity = 2; commit; 你可以看到新的整数二进制掩码值32已经被ACE数据引用了——这会对应我们在代码中定义的新 ADMIN_READ权限。在ACL_OBJECT_IDENTITY表中,“Dog Food”分类被它的主键(在object_id_identity 列中)值2所引用。 http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 208 / 350 页 我们还需要在dogstore-base.xml文件中声明一个新的AclEntryVoter。 除此以外,我们需要将这个投票器添加到访问决策管理器上,这个管理器负责在ACL保护方法的场景中,进 行授权决策: 最后,我们需要在方法声明本身上添加需要的角色,在IProductService接口声明上: public interface IProductService { // other methods omitted @Secured({"VOTE_CATEGORY_READ","VOTE_ADMIN_READ"}) public Collection getItemsByCategory(Category cat); } http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 209 / 350 页 在所有配置完成后,我们可以启动站点并测试ACL权限。基于已配置的示例数据,以下不同用户点击分类 时,应该发生的结果: 用户名 Pet apparel (分类1) Dog Food (分类 2) 其它分类 admin 允许(通过ROLE_ADMIN SID ACE拥有READ权限) 拒绝 允许 admin2 允许(通过ROLE_ADMIN SID ACE拥有READ权限) 允许(通过安全实体SID ACE拥有ADMIN_READ权 限) 允许 guest 拒绝 拒绝 允许 可以看到即使使用我们简单的例子,也能扩展Spring ACL的功能。当然我们使用了很有限的方式来说明 这个定义良好的访问控制系统的强大功能,这个系统是建立在安全实体、GrantedAuthority、单个域对象以及 业务方法之上。 在JSP中使用Spring Security JSP tag库启动ACL 在第三章:增强用户体验和第五章中看到,Spring Security的JSP tag库提供了暴露认证相关的数据给用 户的功能以及基于各种规则限制能看到的内容。 相同的tag库也能够内置地与使用ACL的系统交互。从我们上面简单的例子中,我们已经围绕首页上前两 个分类配置了一个简单的ACL授权场景。 让我们更进一步,使用tag来隐藏用户实际不能访问的分类。 请参考我们前面的表格来了解到此为止,我们已经配置的访问规则。 我们将显示每个分类用tag包围起来,声明对这个显示对象要进行的权限检查: http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 210 / 350 页
  • ${category.name}
  • 请想一下,我们期望在这里发生什么——我们想要用户只能看到他有READ或ADMIN_READ(我们自定 义的许可权限)权限的条目。所以我们声明了一个逗号分隔的权限列表以及要进行检查的域对象(通过JSP EL 的表达式${category}来指定)。 在背后,这个tag的实现使用我们前面讨论的相同SidRetrievalStrategy和 ObjectIdentityRetrievalStrategy,所以它与方法安全中使用的ACL具有相同的访问检查计算流程。 http://lengyun3566.iteye.com 1.33 《Spring Security3》第七章第二部分翻译(高级ACL)(上) 第 211 / 350 页 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 发表时间: 2012-01-17 关键字: Spring Security, 安全, 翻译, java ee 支持ACL的Spring表达式语言 SpEL对ACL系统的支持仅限于方法安全,通过使用hasPermission SpEL方法。典型情况下,这种类型的访问检 查会与引用一个或多个传入参数(进行@PreAuthorize检查)或集合过滤(进行@PostAuthorize检查)联合 使用。 遗憾的是,启用ACL方法安全配置需要我们配置所有的方法安全以明确的Spring Bean的方式。这样,我 们就需要我们移除元素并替换为我们在第六章中介绍过的明确方式配置方法安全。 鉴于我们这里不想重复配置(尽管它包含在本章代码中),我们所要做的只是做一下如下的细微修改,ACL hasPermission检查所需要的类就可用了: 我们更新的methodExprHandler的bean定义,配置了o.s.s.access.PermissionEvaluator的实现(默认的 PermissionEvaluator实现会拒绝所有的许可认证检查)。o.s.s.acls.AclPermissionEvaluator使用了 AclService及相关的类去实际检查SpEL表达式声明的许可权限。 随着配置完成,我们可以使用一个替代的方法来过滤显示在首页上的分类列表(确保你已经移除了上一个 练习所添加的tag库)。 【注意,hasPermission方法不再支持逗号分隔的许可授权(正如我们在 JSP tag中见到的 那样),所以我们需要使用SpEL的布尔逻辑。】 http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 212 / 350 页 只需简单的添加以下声明到IProductService接口上: @PostFilter("hasPermission(filterObject, 'READ') or hasPermission(filterObject, 'ADMIN_READ')") Collection getCategories(); 现在,重启应用并比较以匿名用户、admin、admin2进入时首页的分类列表。注意这个显示列表是如何通过 ACL许可授权完成的?我们可以看到SpEL的hasPermission方法很好的与使用ACL的应用通过方法安全注解结合 起来。 易变的ACL(Mutable ACLs)和授权 尽管JBCP Pets站点没有实现完整的用户用户管理功能,但是你的应用可能会有这些通用的功能如:新用 户注册以及管理用户维护。到此时,缺少这些功能——我们是通过在应用初始化的时候用SQL插入的方法代替 的——并没有阻止我们演示Spring Security和Spring ACL的很多功能。 但是,在运行时适当的处理声明的ACL、添加或删除系统用户,对于基于ACL的授权环境来说保证一致性 和安全很重要。Spring ACL通过易变的ACL(o.s.s.acls.model.MutableAcl)来解决这个问题。 扩展自Acl接口的MutableAcl允许运行时操作ACL域以实现修改某个特定ACL的内存表现。这个功能包括 新增、更新以及删除ACE,修改ACE的拥有者和其它有用的功能。 那么,我们可能会期望Spring ACL模块能够有内置的方式将运行时的ACL变化持久化到JDBC数据存储 中,它确实如此。o.s.s.acls.jdbc.JdbcMutableAclService能用来创建、更新和删除数据库中的MutableAcl实 例,并且管理其它支持ACL的表(能够处理SID,ObjectIdentity以及域对象类名)。 对我们来说,它只需要一个简单的配置来使用JdbcMutableAclService替换JdbcAclService——易变 service需要一个对ACL缓存的引用,这样它就能够在更新数据库条目的时候将缓存删除。这个bean的配置很简 单: http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 213 / 350 页 请回忆在本章前面,AclAuthorizationStrategyImpl允许我们指定对易变ACL进行操作所需要的角色。它们以 bean配置的方式提供给构造方法。构造参数以及它们的意义,如下: 参数序 号 做什么的 1 表明安全实体要修改ACL保护对象拥有者所要拥有的角色 2 表明安全实体要修改ACL保护对象审计所要拥有的角色 3 表明安全实体要对ACL保护对象进行其它修改(新建、更新和删除)所要 拥有的角色 译者注:以上的三个参数的作用就是要保证对保护的对象进行操作时,登录人必须有足够的权限。 JdbcMutableAclService包含一系列的方法在运行时来操作ACL和ACE数据。尽管这些方法本身很容易理解 (createAcl,updateAcl,deleteAcl),但是正确的配置和使用JdbcMutableAclService经常对Spring Security的高级用户都很困难。 让我们实现一个ACL启动类它会替换我们的启动脚本,并用代码的方式插入当前的ACL和ACE条目(以及支 持的ObjectIdentity和Sid)。 配置Spring事务管理器 JdbcMutableAclService使用Spring的JdbcTemplate来与JDBC DataSource进行交互。所以,它需要一 个Spring JDBC PlatformTransactionManager以保证(很有侵入性)所有与数据库的交互正确地包装在事务 中。 大多数实际使用Spring的应用可能已经有一个声明的事务管理器,但是我们的JBCP Pets应用并没有。我们 在dogstore-base.xml添加一个: http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 214 / 350 页 声明一个对启动类的应用,一会将会对它进行编码: 很像在第四章:凭证安全存储中的DatabasePasswordSecurerBean,我们配置这个bean使得Spring ApplicationContext初始化时,aclBootstrap方法会被触发——恰当的时间来启动我们应用中的ACL数据。 与JdbcMutableAclService交互 现在,我们要编写启动bean——创建com.packtpub.springsecurity.security.AclBootstrapBean 类。首先我们使用@Autowired注入需要的依赖: package com.packtpub.springsecurity.security; // imports omitted public class AclBootstrapBean { @Autowired MutableAclService mutableAclService; @Autowired IProductDao productDao; @Autowired PlatformTransactionManager transactionManager; 接下来是方法的实际定义,它将会被触发以启动ACL数据——我们将会每次分析一个片段: public void aclBootstrap() { // domain data to set up Collection categories = productDao.getCategories(); Iterator iterator = categories.iterator(); final Category category1 = iterator.next(); final Category category2 = iterator.next(); http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 215 / 350 页 我们需要引用实际要保护的域对象以便于为它们创建ACL。回忆一下,我们的ACL启动SQL只对前两个分类添 加条目,所以我们要把它们从ProductDAO中取出来。 JdbcMutableAclService需要使用ObjectIdentity来创建初始的MutableAcl(它能够被进一步的管理, 我们等会能看到)。为了创建ObjectIdentity,我们需要真正的域对象。 // needed because MutableAclService requires a current authenticated principal GrantedAuthorityImpl roleUser = new GrantedAuthorityImpl("ROLE_USER"); GrantedAuthorityImpl roleAdmin = new GrantedAuthorityImpl("ROLE_ADMIN"); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("admin","admin",Arrays.asList(new GrantedAuthority[]{roleUser, roleAdmin})); SecurityContextHolder.getContext().setAuthentication(token); JdbcMutableAclService会验证一个用户已经登录,并将其作为建立的MutableAcl的默认拥有者。遗憾的是, 这是一个强制检查,即便随后你明确设置ACL拥有者(也不行)。因为这些代码执行时没有认证过的用户,所以 我们让JdbcMutableAclService把admin用户作为当前认证过的用户(译者注:就是上面最后两句代码)。 // sids final Sid userRole = new GrantedAuthoritySid("ROLE_USER"); final Sid adminRole = new GrantedAuthoritySid("ROLE_ADMIN"); // users final Sid adminUser = new PrincipalSid("admin"); final Sid admin2User = new PrincipalSid("admin2"); 我们需要为安全实体和组创建Sid,它们是ACL和ACE的结构,所以在这里我们明确创建它们。记住的是,如果 你在应用范围内管理ACL(例如,通过UI层调用业务服务完成),你可以以不同的方式处理这个问题——这里的 代码只是一个例子,我们鼓励你根据情况作出调整。 // all interaction with JdbcMutableAclService must be within a transaction TransactionTemplate tt = new TransactionTemplate(transactionManager); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 216 / 350 页 { // category 1 ACL MutableAcl createAclCategory1 = mutableAclService.createAcl(new Obje ctIdentityImpl(category1)); createAclCategory1.setOwner(adminRole); createAclCategory1.insertAce(0, BasePermission.READ, adminRole, true); mutableAclService.updateAcl(createAclCategory1); // category 2 ACL MutableAcl createAclCategory2 = mutableAclService.createAcl(new Obje ctIdentityImpl(category2)); createAclCategory2.setOwner(admin2User); createAclCategory2.insertAce(0, CustomPermission.ADMIN_READ, admin2User, true); mutableAclService.updateAcl(createAclCategory2); }}); SecurityContextHolder.clearContext(); } } 在一个新的事务范围内,与JdbcMutableAclService的交互完成了。我们可以看到初始的createAcl调用返回一 个MutableAcl。在这背后,针对ObjectIdentity和Sid进行了数据库插入和查找。MutableAcl本身提供了方法 来创建、更新和删除ACL中的ACE(记住,ACE声明的是单个的权限-SID匹配)。最后MutableAcl通过 updateAcl方法调用更新到数据库中。 要注意的是JdbcMutableAclService还负责确保MutableAcl操作进行时,AclCache被更新。 建议你体验易变的ACL服务并增强这个站点以支持用户的新建和编辑——JdbcMutableAclService有很好 的文档(在代码层面),尝试实现它会是一个很好的练习,这取决于你是否愿意实现完整的运行时驱动的ACL模 型。 Ehcache ACL缓存 Ehcache是一个开源的、内存和基于硬盘的缓存库,它被广泛用在很多开源和商用Java产品中。正如我们 在本章前面提到的,Spring Security提供了一个默认的ACL缓存实现,它依赖于一个配置的Ehcache实例,它 会存储ACL信息并优先于在数据库中读取ACL。 鉴于在本节我们不想过多介绍详细配置Ehcache,我们将会涉及到Spring ACL如何使用cache并介绍一个 简单的默认配置。 http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 217 / 350 页 配置Ehcache ACL缓存 建立Ehcache很简单——我们需要简单地声明两个Spring Core的bean,它们管理Ehcache实例并暴露几 个有用的配置属性: 接下来,我们要实例化Ehcache ACL缓存bean: 最后,我们将NullAclCache实现替换为基于Ehcache的实现: 当这些配置完成(并且Ehcache运行时JAR在你的classpath中),ACL数据将会基于Ehcache缓存管理器的配 置进行缓存。 http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 218 / 350 页 Spring ACL怎样使用Ehcache 前面介绍的配置步骤可能会比较简单,添加Ehcache到你的应用中——尤其是大量使用——要进行细致的 分析,比较缓存的成本和数据库查询的成本。理解Ehcache在Spring ACL中是如何使用的对于规划缓存大小和 寿命很重要。 作为ACL缓存策略的一部分,Spring ACL将会存储以下所有的对象(它们的大多数在o.s.s.acls.domain) 到缓存中,要么作为key要么作为值: l ObjectIdentity(实现类为ObjectIdentityImpl); l Sid(实现类为GrantedAuthoritySid或PrincipalSid); l Acl(实现类为AclImpl),包含AccessControlEntry (实现类为AccessControlEntryImpl); l 你对象实例的Serializable类型主键(一般为Long,除非你对Spring ACL运行时类做了重要的个性化) 只有BasicLookupStrategy和MutableAclService是ACL缓存机制的用户,使用缓存很简单。关于缓存中条 目的大小,比较好的办法是在合适的负载测试中监控缓存以评估缓存元素的内存使用和存在寿命。 如果你的应用为了其它的目的(如Hibernate或其它ORM根据)已经使用了Ehcache,你可能愿意将用于 ORM目的的缓存实例和用于存储ACL数据的缓存实例区分开来。一般的做法是在构建用于Spring ACL的 EhCacheFactoryBean时,提供唯一的cache名,如下: 奇怪的是,除了Javadoc以外,Spring Core中对于Ehcache的支持没有任何正规的文档。如果使用Spring ACL和Ehcache对你很重要,那你很可能需要自己深入研读代码。 http://lengyun3566.iteye.com 1.34 《Spring Security3》第七章第二部分翻译(高级ACL)(下) 第 219 / 350 页 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) 发表时间: 2012-01-17 关键字: Spring Security, 安全, 翻译, java ee 典型ACL部署所要考虑的事情 实际部署Spring ACL到业务应用是很复杂的。我们总结了Spring ACL要注意的事情,它们在大多数 Spring ACL实现场景中都存在。 关于ACL的伸缩性和性能模型 对于小型和中型应用,添加ACL功能是很容易的,尽管它增加数据库存储和影响运行时性能,这个影响可 能不会那么明显。但是,取决于ACL和ACE建模的粒度,在中到大型应用中,数据库的行数可能会非常惊人,甚 至对对熟练的DBA都是很难的任务。 让我们假设我们将要扩展ACL以覆盖JBCP Pets应用大多数的功能,包括用户账号和订单,还包括JBCP Pets中顾客论坛的帖子。我们对着数据建模如下: l 所有的顾客都有账号; l 10%的顾客拥有订单。其中每个顾客的平均订单数是2; l 订单对顾客是要保护的(read-only),但是管理员也能访问(read/write); l 10%的顾客会在顾客论坛上发帖,其中每个顾客的帖子数量是20; l 帖子对顾客是要进行保护的(read-write),对管理员也是如此。帖子对其它顾客是read-only的。 基于我们对ACL系统的了解,我们知道数据库表有以下的可伸缩相关属性: 表 是否随数据伸缩 可伸缩性注释 ACL_CLASS NO 每个域类需要一行 ACL_SID Yes(User) 每个角色 (GrantedAuthority)需要 一行 http://lengyun3566.iteye.com 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) 第 220 / 350 页 每个用户账号需要一行(如果 域对象根据用户保护) ACL_OBJECT_IDENTITY Yes(域类*每个类的实例 数) 每个保护的域对象需要一行 ACL_ENTRY Yes(域对象实例*单个的 ACE条目) 每个ACE需要一行,对于每个 域对象可能要多行 我们可以看到ACL_CLASS并没有伸缩性的考虑(大多数的系统少于1000个域类)。ACL_SID是基于 系统中的用户数线性伸缩的。这可能不用考虑,因为其它用户相关的表也以这种方式处理伸缩性(如用户账 号等)。 要考虑的两个表是ACL_OBJECT_IDENTITY和ACL_ENTRY。如果对一个用户有一个订单这种情况计 算需要的行数,我的得到如下的估算结果: 表 每个订单的ACL数据 每个论坛帖子的ACL数 据 ACL_OBJECT_IDENTITY 每个订单需要一行数据 每个帖子需要一行数据 ACL_ENTRY 三行——一行用于拥有者(即 顾客的SID)的read访问,两 行(一行用于读访问,一行用 于写访问)用于管理员组的 SID。 四行——一行用于顾客 组SID的读访问,一行用 于拥有者的写访问,两 行用于管理员组(如同 订单) 我们可以使用以上的假设并计算出ACL的伸缩性矩阵: 表/对象 伸缩性因素 估算(低) 估算(高) http://lengyun3566.iteye.com 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) 第 221 / 350 页 用户(Users) 10,000 1,000,000 订单(Orders) # Users * 0.1 * 2 2,000 200,000 论坛帖子 (ForumPosts) # Users * 0.1 * 20 20,000 2,000,000 ACL_SID # Users 10,000 1,000,000 ACL_OBJECT_IDENTITY # Orders + # Posts 22,000 2,200,000 ACL_ENTRY (# Orders * 3) + (# Posts * 4) 86,000 8,600,000 在典型的ACL实现中,这些预测只是要关联和保护对象的子集,你可以看到存储ACL数据的数据库行 数是随着业务数据线性(或更快)增长的。特别是对于大型系统的规划,预测你可能用到的ACL数据特别重 要。对于非常复杂的系统拥有数亿行关于ACL存储的数据并不罕见。 不要忽视自定义开发的成本 使用Spring ACL的安全环境通常需要明显的开发工作以及我们讲过的配置步骤。我们例子的配置场景有 以下的限制,这可能会影响到将ACL安全应用到完整站点上: l SID和访问凭证硬编码在应用启动的时候; l 没有提供管理域对象(创建和删除)或管理用户、组的功能; l 应用没有有效使用ACL的等级体系。 当整个应用规划Spring ACL时,你必须仔细考虑所有域数据管理的地方,并确保这些地方正确更新ACL和 ACE规则并失效缓存。一般来说,方法和数据安全发生在应用的服务或业务层,而维护ACL和ACE所需要的钩子 (hook)通常发生在数据访问层。 http://lengyun3566.iteye.com 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) 第 222 / 350 页 如果你正在处理一个标准应用的架构,拥有合适的隔离及功能封装,可能会很容易找到一个中央位置标识这些 变化。但是,如果你正在处理一个遗留的(或者开始的时候出来没有设计)架构,添加ACL功能并在数据操作的 代码中支持钩子(hook)将会很困难。 正如前面所提到的,需要注意的是Spring ACL自从Acegi 1.x时代就没有明显的变化过,大约三年了。在那 时,很多用户尝试使用它,并记录和以文档的形式提出了很多限制,它们中的很多保存在Spring Security JIRA 库中(http://jira.springframework.org/)。缺陷SEC-479可以作为很多关键限制的入口,它们中的很多在 Spring Security3中依然没有处理,(如果它们适用于你的场景)可能会需要很大的自定义编码工作。 以下是一些最重要和常见的缺陷: l ACL基础设施需要数字型的主键。对于使用GUID或UUID主键(近来用的越来越多,因为现代数据库提供了高 效支持)的应用,这会是很大的限制; l JIRA缺陷SEC-1140记录的缺陷,默认ACL实现不能用按位操作正确的对比Permission二进制掩码。在关于许 可授权一节,我们已经提到; l 配置Spring ACL的方法与其它Spring Security功能存在一些不一致。简单来说,在这个功能领域,类代理和 属性并没有通过DI(依赖注入)暴露,需要覆盖或重写策略会很耗时且维护代价高昂; l Permission的二进制掩码通过整数(integer)实现,所以最多有32个可能的位。比较常见的做法是扩展默认 的未分配来声明单个对象属性的权限(例如,为读的员工社会保险号分配一个位)。复杂的部署可能会每个域 对象超过32个属性,在这种情况下唯一可选的就是为了这个限制,重新对你的域对象建模。 取决于你特定应用的需要,可能会遇到新的问题,尤其是关于这些实现自定义功能要修改的类。 我应该用Spring Security的ACL吗? 正如使用整体使用Spring Security是很强的业务依赖,使用Spring ACL也是这样——实际上,这对于 ACL可能更明显,因为它紧密连接到业务方法和域对象上了。我们希望这个关于Spring ACL的指导已经为你描 述了重要的各层配置和Spring ACL的概念,这用来分析Spring ACL在你的应用中如何使用,并帮助你决定和匹 配它的功能到实际应用中。 http://lengyun3566.iteye.com 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) 第 223 / 350 页 小结 在本章中,我们关注访问控制列表安全以及对于这种类型的安全Spring Security ACL模块是如何实现 的。我们所做如下: l 了解访问控制列表的基本概念,以及为什么说它们是授权的有效解决方式; l 学习Spring ACL实现的关键概念,包括访问控制条目、SID以及对象标识; l 考察支持ACL系统的数据库模式以及逻辑设计; l 配置所有需要的Spring Bean来启用Spring ACL模块并增强了一个服务接口来使用注解的方法授权。我们接下 来将数据库中已有的用户以及站点使用的业务对象,变成ACE声明和辅助的实例数据; l 了解Spring ACL许可授权处理相关的概念; l 扩展了我们关于SpringSecurity JSP标签库和SpEL表达书语言(用于方法安全)的知识来实现ACL检查; l 讨论易变的ACL概念,并了解在易变的ACL环境中的基本配置和需要自定义的代码; l 开发了一个自定义的ACL许可授权并配置应用验证器有效性; l 配置和分析使用Ehcache缓存管理器以减少Spring ACL的数据库影响; l 分析在复杂业务应用中使用Spring ACL的影响以及设计考虑因素。 这完成了本书中关于Spring Security关键概念的部分。在接下来的章节中,我们将会深入讨论Spring Security认证与多种类型的外部系统进行集成。如果你不知道这些系统(OpenID、LDAP等)背后的技术,我 们也将会带领你进行学习,所以请继续阅读。 http://lengyun3566.iteye.com 1.35 《Spring Security3》第七章第三部分翻译(ACL的注意事项) 第 224 / 350 页 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) 发表时间: 2012-01-17 关键字: Spring Security, Java EE, 安全, 翻译 第八章对OpenID开放 OpenID是很流行的可信任身份管理方式,它允许用户通过一个单独的可信任提供者(provider)管理其身 份信息。这个便利的功能为用户提供了安全的方式即使用可信任的OpenID提供者来存储器密码和个人信息, 并可以随意的基于请求获取其个人信息。另外,启用OpenID功能的站点能够确信用户提供的OpenID凭证信 息就是他们所说的人。 在本章中,我们将会: l 学习在五分钟之内建立自己的OpenID; l 使用快速实现的OpenID来配置JBCP Pets站点; l 学习OpenID的概念架构以及它怎样为你的站点提供可信任的用户访问; l 实现基于OpenID的用户注册; l 体验OpenID属性交换得到用户简介功能; l 检查基于OpenID登录的安全性。 OpenID承诺的世界 作为一项技术,OpenID的承诺是允许web上的用户通过一个可信任的提供者集中管理他们的个人数据和 信息,然后这个可信任的提供者作为代理与用户想交互的站点建立起互信。 概念上来说,这种类型的登录要通过一个长时间存在的可信任第三方,可以有多种方式(如Microsoft Passport,曾经是最知名的中心登录服务)。OpenID的区别优势在于OpenID提供者(OpenID Provider)仅 仅需要实现一个协议,这个协议与任何试图通过OpenID进行集成登录的站点相协调。OpenID规范本身也是一 个开放的规范,这就导致出现了不同的提供者但运行相同的协议。这对健康竞争是一种好事,也有利于客户的 选择。 下图整体说明了一个集成OpenID的站点在登录过程中与OpenID提供者之间的关系: http://lengyun3566.iteye.com 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) 第 225 / 350 页 我们可以看到用户以唯一名字标识的方式提供自己的凭证信息,一般是统一资源标识符(Uniform Resource Identifier,URI),这是OpenID提供者分配给用户的,用来唯一标识用户和OpenID提供者。这通常是 OpenID提供者URI的子域名(如:https://jamesgosling.myopenid. com/),或者在OpenID提供者URI上添加唯一标识(如:https://me.yahoo.com/jamesgosling)。我们可 以看到这两种方式的URL都能很明确的标识出OpenID提供者(通过域名)和唯一的用户标识。 【不要不加考虑地信任OpenID。在这里你看到我们可以伪装系统中的用户。我们可以用一个OpenID登 录,它可能会以为我们是James Gosling,当然我们不是。不要做这样错误的假设,即因为有一个听起来令人信 服的OpenID(或OpenID代理提供者)那他们就是认证过的人了,而不需要额外方式的认证。以另外的方式想 一下这个问题,如果有个人敲你的门并说自己是James Gosling,你会不检查他的ID就让他进来吗?】 启用OpenID的应用接下来会重定向到OpenID提供者哪里,在这里用户提供他的认证信息,OpenID提供 者负责做出访问决定(译者注:即登录是否成功)。一旦提供者做出访问决定,它将用户重定向会原始的站 点,这是可以确信用户的真实性了。 如果你开始进行尝试,那OpenID就更容易理解了。让我们添加OpenID到JBCP Pets登录页上。 注册一个OpenID 为了完全体现本节中这个练习的价值(并测试登录),你需要在众多可用的OpenID中选择一个拥有自己 的OpenID,他们的列表可以在这里看到:http://openid.net/get-an-openid/。有些通用的OpenID提供者, 你可能已经有它们的账号了如Yahoo!,AOL, Flickr或MySpace。Google的OpenID支持略有不同,在本章后 http://lengyun3566.iteye.com 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) 第 226 / 350 页 面的添加Sign In with Google到登录页时,我们将会看到。为了完整的完成本章的练习,我们建议你最少有以 下的账号: l myOpenID l Google 使用Spring Security启用OpenID认证 在接下来的几章介绍外部认证提供者时,我们将会看到一个通用的模式。关于与Spring系统之外的提供者 集成,Spring Security提供了便利的包装。 在这里,openid4java项目(http://code.google.com/p/openid4java/)为Spring Security的OpenID 功能提供了底层OpenID提供者发现和请求/响应握手(negotiation)的功能。 编写一个OpenID登录表单 常见的方式是站点在一个登录页上提供了标准(用户名和密码)和OpenID两种登录选择,允许用户在两 种方式中选择,JBCP Pets的最终登录页如下: 基于OpenID的form代码如下:

    Or, Log Into Your Account with OpenID

    Please use the form below to log into your account with OpenID.

    http://lengyun3566.iteye.com 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) 第 227 / 350 页
    : OpenID
    Form域的名字即openid_identifier是有特定含义的。OpenID规范建议使用的站点以这个名字作为OpenID登 录域,所以用户客户端(浏览器)对于这个域的功能也具有语义理解。甚至有浏览器插件如Verisign's OpenID SeatBelt (https://pip.verisignlabs.com/seatbelt.do),它能够利用这个语义理解,预先在识别的页面把 OpenID凭证填入OpenID域中。 你可能会意识到在OpenID登录中没有提供remember me选项。这是因为从重定向到提供者再回来,导 致remember me复选框的值丢失,所以当用户登录成功后,他们不再有remember me选项。这很遗憾,但是 这却增强了我们OpenID登录机制的安全性,因为OpenID强制用户每次登录时都与提供者建立可信任的关系。 在Spring Security中配置支持OpenID 回到基本的OpenID,它包括一个servlet过滤器和认证提供者,只要在dogstore-security.xml文件中的 配置元素中添加一个指令即可: 在添加完这个配置元素后并重启应用,你可以重启应用,你能够使用OpenID登录form提供OpenID并会执行 OpenID认证流程。但是当你回到JBCP Pets,你会被拒绝访问。这是因为你的凭证并没有任何角色。接下来, 我们要处理它。 添加OpenID用户 因为现在我们还没有使用OpenID的新用户注册,所以需要手动的插入用户账号(测试所用)到数据库 中,这是通过添加到test-users-groups-data.sql数据库启动代码中做到的。我们推荐在这一步你使用 myOpenID(注意,如果使用Yahoo!会有问题,原因我们下面介绍)。假设我们的OpenID是 https://jamesgosling.myopenid.com/,那要插入这个文件的SQL如下: http://lengyun3566.iteye.com 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) 第 228 / 350 页 insert into users(username, password, enabled, salt) values ('https://jamesgosling.myopenid.com/','unused',true,CAST(RAND()*1000000000 AS varchar)); insert into group_members(group_id, username) select id,'https://jamesgosling.myopenid.com/' from groups where group_name='Administrators'; 你可能会发现这与我们插入传统的基于用户名和密码的admin账号很类似,只是我们使用“unused”作为密 码。我们这样做,当然是因为基于OpenID的登录并不需要我们的站点存储用户的密码。但是,细心的读者可能 会发现,这并不允许用户创建一个任意的用户名和密码并将其与OpenID关联——我们将会在本章后面简单介绍 这个过程,你也可以作为使用这项技术的高级用法,自己探索如何实现。 此时,你可以完成使用OpenID实现完整登录。重定向的顺序通过以下的截图以箭头的方式进行了描述: 现在我们有了使用OpenID的JBCP Pets登录!可以测试多个OpenID提供者。你会发现,尽管整体的功能是一 样的,但是不同提供者的检查和接受OpenID的体验是不同。 http://lengyun3566.iteye.com 1.36 《Spring Security3》第八章第一部分翻译(OpenID与Spring Security) 第 229 / 350 页 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 发表时间: 2012-01-18 关键字: Spring Security, java EE, 安全, 翻译 OpenID用户的注册问题 请使用我们前面的技术来测试Yahoo! OpenID——例如,https://me.yahoo.com/pmularien。你会发 现它并不好用,像其它OpenID提供者那样。这带出了OpenID结构的一个很重要的问题,并体现出了启用 OpenID用户注册的重要性。 OpenID标识是如何处理的 Yahoo!返回的实际OpenID类似于如下的样子:https://me.yahoo.com/pmularien#9a466。在OpenID 术语中,用户在登录框中输入的标识符被称为用户提供的标识符(user-supplied identifier)。这个标识符可 能并不对应唯一的用户标识符(用户的声明标识符,claimed identifier),但是作为鉴别所有者的一部分, OpenID提供者将会把用户的输入转换成提供者能真正证明用户拥有的标识符。(译者注:即用户记住的标识符 与OpenID Provider返回的标识符可能并不一致)。 【OpenID发现协议以及OpenID提供者本身实际上能够很奇妙地基于OpenID认证请求计算出用户是谁。 例如,尝试输入OpenID提供者(如www.yahoo.com)的名字在OpenID登录框中——你会看到一个不同的界 面让你输入OpenID,因为你并没有在登录框中提供唯一的OpenID。很聪明!关注这个还有更多OpenID的规 范,可以OpenID组织站点的规范页面(在开发者页面中),http://openid.net/developers/。】 一旦用户能够提供他拥有的声明标识符,OpenID提供者将会返回给请求拥有一个声明标识符的正规版 本,这被叫做OpenID提供者本地标识符(OpenID Provider Local Identifier或OP-Local Identifier)。这是 OpenID提供者表明用户的最终唯一标识符,也是到OpenID提供者的认证请求的返回值。所以,这就是JBCP Pets应该存储并用来区分用户的标识符。 Spring Security处理OpenID登录请求的流程如下图所示: http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 230 / 350 页 o.s.s.openid.OpenIDAuthenticationFilter负责监听/j_spring_openid_security_check这个URL并响应用户的 登录请求,类似于UsernamePasswordAuthenticationFilter为/j_spring_security_check URL所作的那样。从 这个图我们可以看到o.s.s.openid.OpenID4JavaConsumer委托openid4java库构建最终重定向用户到OpenID 提供者的URL。openid4java库(通过org.openid4java.consumer.ConsumerManager)还负责查找提供者, 对此我们前面有所描述。 这个过滤器实际上负责OpenID认证的两个阶段——即格式化到OpenID提供者的重定向以及处理提供者 的认证响应。OpenID提供者的响应是一个简单的GET请求,带有一系列定义良好的域,它们会被openid4java 库处理并认证。你并会直接处理这些域,其中一些重要的如下: 域名 描述 openid.op_endpoint OpenID提供者用来校验的URL openid.claimed_id 用户提供的OpenID声明标识符 http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 231 / 350 页 openid.response_nonce 提供者计算出的当前时间,用来创建签名 openid.sig OpenID的响应签名 openid.association 根据请求者生成的一次性使用的相关数据,被用来计算签 名并确定响应的合法性 openid.identifier OP-Local identifier 我们将会了解这些域怎样用于校验响应的合法性。让我们看一下处理提供者的OpenID响应的几个相关 者: http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 232 / 350 页 我们可以看到用户在提交凭证到OpenID提供者站点后,被重定向到/j_spring_openid_security_check。 OpenIDAuthenticationFilter进行一些基本的检查以判断这个请求是OpenID请求(从JBCP Pets登录表达发 起)还是一个提供者的合法OpenID响应。 一旦确定这个请求是OpenID响应,要进行一系列复杂的校验以保证响应的正确性和可靠性(参考随后的Is OpenID secure?章节以了解更多细节)。OpenID4JavaConsumer最终会返回一个填充不完整的 o.s.s.openid.OpenIDAuthenticationToken对象,而它会被过滤器用来确定对响应的初始校验是否成功。这个 token会被传递到AuthenticationManager中,而AuthenticationManager像处理其它Authentication对象那 样处理它。 o.s.s.openid.OpenIDAuthenticationProvider最终负责跟本地存储进行的校验(如JdbcDaoImpl)。要 记住的重要一点是,在数据存储中期望得到的包含OP-Local Identifier的用户名,它与用户提供的标识符并不 一定一致——这是进行OpenID注册的问题关键。从这以下的处理流程与传统的用户名/密码认证很相似,最重 要的是要从UserDetailsService取到正确的组和角色分配。 使用OpenID实现注册 对于启用了OpenID功能的JBCP Pets站点来说,用户要创建一个账号就要首先证明他们拥有自己所声明 的标识符。所以,我们允许用户提供一个OpenID注册。我们已经添加了为标准的用户名和密码认证添加了注册 流程,你可以通过页头上的“Registration”进行尝试。让我们扩展这个注册流程以允许用户使用OpenID来注 册。 添加OpenID注册选项 首先,我们需要添加一个简单的表单到注册页上以允许用户输入和校验他们的OpenID标识,这个表单与 登录的表单很相似(实际上,完全一致)。在registration.jsp中添加以下代码:

    Or, Register with OpenID

    Please use the form below to register your account with OpenID.

    : http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 233 / 350 页 OpenID
    这个表单实际上与登录页的表单完全一样。那我们怎样区分登录和注册请求呢? 区分登录和注册请求 我们选择了一种很简单的方法来区分登录和注册请求。如果用户进行了一个成功的OpenID认证尝试,但 是在我们的数据库中还没有他的账号,那我们就假设这是一个注册请求并把它加到数据库中。当然还可以完善 这种方式(如,在用户创建账号之前显示确认信息),但是对于我们的例子来说,这就足够了。 我们会扩展标准的AuthenticationFailureHandler,在 com.packtpub.springsecurity.security.OpenIDAuthenticationFailureHandler类中,如下: package com.packtpub.springsecurity.security; // imports omitted public class OpenIDAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { if(exception instanceof UsernameNotFoundException && exception.getAuthentication() instanceof OpenIDAuthenticationToken&& ((OpenIDAuthenticationToken)exception.getAuthentication()).getStatus().equals(OpenIDAuthenticationStatus.SUCCESS)) { DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); request.getSession(true).setAttribute("USER_OPENID_CREDENTIAL", ((UsernameNotFoundException)exception).getExtraInformation()); // redirect to create account page redirectStrategy.sendRedirect(request, response, "/registrationOpenid.do"); } else { super.onAuthenticationFailure(request, response, exception); } } } 我们可以看到这个代码扩展了父类的默认行为,在满足以下条件时将重定向用户到registrationOpenid.do: http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 234 / 350 页 l 用户遇到了UsernameNotFoundException; l 用户已经被OpenID提供者成功认证(这通过检查OpenIDAuthenticationToken的 OpenIDAuthenticationStatus属性值)。 这个代码将OpenID提供者返回的OP-Local Identifier值设置到session中,所以在被重定向到OpenID注册 URL时还能取到。 配置自定义的认证失败处理器 我们需要配置这个认证失败处理器,只需要简单调整dogstore-security.xml中的声明: defaultFailureUrl是用户遇到真正的登录失败时(如提供了非法的凭证信息),用户被重定向到的地址。 添加OpenID注册功能到控制器上 处理基于OpenID的注册很容易,添加到LoginLogoutController上就可以,这上面已经有了标准的用户 名和密码注册: @RequestMapping(method=RequestMethod.GET,value="/registrationOpenid.do") public String registrationOpenId(HttpServletRequest request) { String userId = (String) request.getSession().getAttribute("USER_OPENID_CREDENTIAL"); if(userId != null) { userService.createUser(userId, "unused", null); setMessage(request, "Your account has been created. Please log in using your OpenID."); return "redirect:login.do"; } else { setMessage(request, "Please register using your OpenID."); return "redirect:registration.do"; http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 235 / 350 页 } } 接下来,我们要修改IuserService接口和UserServiceImpl实现来建立一个简单的createUser方法: @Service public class UserServiceImpl implements IUserService { @Autowired CustomJdbcDaoImpl jdbcDao; // existing code omitted @Override public void createUser(String username, String password, String email) { jdbcDao.createUser(username, password, email); } } 你会发现我们也修改了@Autowired注解以明确引用CustomJdbcDaoImpl,我们需要在这个类中实现一个自 定义的createUser,如下: @Transactional public void createUser(String username, String password, String email) { getJdbcTemplate().update("insert into users(username, password, enabled, salt) values (?,?,true,CAST(RAND()*1000000000 AS varchar))", username, password); getJdbcTemplate().update("insert into group_members(group_id, username) select id,? from groups where group_name='Users'", username); } 你可能对SQL感到熟悉——这就是我们在第四章:凭证安全存储中原来初始化用户的。还记得我们创建 DatabasePasswordSecurerBean再启动时为密码加salt?有了createUser方法,我们可以移除启动SQL了并且 使用Java来初始化用户——你觉得我们应该怎样写代码来完成它呢?你为什么不试一试呢——这是一个很有效 的练习来测试你对这方法的知识。 http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 236 / 350 页 如果我们不是使用带salt域的自定义user表,我们可以简单的将CustomJdbcDaoImpl改为继承自 JdbcUserDetailsManager(就像我们在第四章讨论的那样)并使用已经为我们实现的createUser方法: public class CustomJdbcDaoImpl extends JdbcUserDetailsManager implements IChangePassword { 这会需要对UserServiceImpl做一些小的修改: @Override public void createUser(String username, String password, String email) { GrantedAuthority roleUser = new GrantedAuthorityImpl("ROLE_USER"); UserDetails user = new User(username, password, true, true, true, true, Arrays.asList(roleUser)); jdbcDao.createUser(user); } 你可以看到有两种不同的方式注册用户,自定义和内置的。每种方法都能有效的处理OpenID注册问题。尽可 以体验实例应用并选择你喜欢的方法。 一旦用户通过我们的IuserService功能建立,用户被重定向到首页并且可以登录了。如果你想增强用户体 验,我们可以做一些代码修改,保持OpenIDAuthenticationToken到重定向并自动认证用户。 【注意,OP-Local identifiers可能会很长——实际上,OpenID 2.0规范并没有提供OP-Local identifier的一个 最大长度。Spring Security默认的JDBC数据库模式提供了一个相对很小的用户名列(你可能会记得我们将它从 默认值扩展到了100个字符)。取决于你的需要,你可能愿意进一步扩展用户名列以容纳长的标识,或者实现 OpenID处理链上的子类(如OpenIDAuthenticationProvider或UserDetailsService)以正确处理太长的标识 符。这可能包括将用户名拆为多列或存储被删节的URL和完整URL的哈希值。 要记住的是,认证不仅仅是基于OpenID标识数据库中的用户。有一些使用OpenID的站点比这更进一步,允许 关联OpenID标识符和要进行认证的用户名(例如,允许多个OpenID关联相同的用户账号)。根据用户名提取 http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 237 / 350 页 出OpenID对于那些拥有不同提供者的多个OpenID且想在站点中使用的用户来说会有用——尽管这在一定程度 上违背了OpenID的初衷,但它确实存在,你在设计使用OpenID站点的时候需要记住。】 除了凭证管理和中心认证,OpenID的另一个承诺就是对用户来说在一个地方管理其个人信息并对特定的 站点有选择的释放信息。这可能会提供能够丰富的注册体验。让我们看一下属性交换期望如何解决这个问题。 http://lengyun3566.iteye.com 1.37 《Spring Security3》第八章第二部分翻译(OpenID用户的注册) 第 238 / 350 页 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 发表时间: 2012-01-18 关键字: Spring Security, 安全, 翻译, Java EE 属性交换(Attribute Exchange) OpenID另外一个有趣的功能就是如果使用OpenID的站点需要,OpenID provider提供(基于用户的许 可)典型的用户注册数据,如名字、e-mail、生日。这个功能叫做属性交换(Attribute Exchange,AX)。下 图展现了在OpenID请求中要求属性交换是如何添加进去的: AX属性值(如果provider提供的话)将会与其它的OpenID响应一起返回,并作为一个 o.s.s.openid.OpenIDAttribute列表(list)插入到OpenIDAuthenticationToken中。 AX属性能够被OpenID提供者随意定义,但是均为唯一定义的URI。有人在努力标准化可用的和常见的属 性模式。以下的属性是可用的(完整的列表在http://www.axschema.org/types/): 属性名 描述 http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 239 / 350 页 http://axschema.org/contact/email 用户的e-mail地址 http://axschema.org/namePerson 用户的全名 axschema.org站点列出了超过30个不同的属性,带有唯一的URI和描述。在一些特定情况下,你可能需 要引用schema.openid.net而不是axschema.org(稍后我们将会解释为什么)。 让我们看一下在Spring Security中如何配置属性交换。 在Spring Security OpenID中启用AX 一旦你知道了要请求的属性,在Spring Security OpenID中启用AX实际上很容易。我们可以配置AX以请 求e-mail: 对这个例子,我们建议你使用myOpenID标识登录。你将会看到,当你重定向到提供者时,提供者会通知你 JBCP Pets站点请求额外的信息。在以下的截屏中,我们在请求中实际上包含了更多的AX属性: http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 240 / 350 页 请求的属性一旦被提供者返回,那在OpenIDAuthenticationToken就可用了(在成功认证的请求中),是以 键值对的方式存在而名字(即键)就是在中声明的。这就取决于我们的站点来检查这些数 据并对其进行处理。一般来说这些数据能够要在填充用户简介或用户注册表单上。 为了进行探索研究,可以增强我们写的OpenIDAuthenticationFailureHandler来打印检索出的属性到控 制台上: request.getSession(true).setAttribute("USER_OPENID_CREDENTIAL", ((UsernameNotFoundException)exception).getExtraInformation()); OpenIDAuthenticationToken openIdAuth = (OpenIDAuthenticationToken)exception.getAuthentication(); for(OpenIDAttribute attr : openIdAuth.getAttributes()) { System.out.printf("AX Attribute: %s, Type: %s, Count: %d\n", attr.getName(), attr.getType(), attr.getCount()); for(String value : attr.getValues()) { System.out.printf(" Value: %s\n", value); } } redirectStrategy.sendRedirect(request, response, "/registrationOpenid. do"); http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 241 / 350 页 在我们的例子中将会得到如下的输出: AX Attribute: email, Type: http://schema.openid.net/contact/email, Count: 1 Value: peter@mularien.com AX Attribute: birthDate, Type: http://schema.openid.net/birthDate, Count: 1 Value: 1968-04-13 AX Attribute: namePerson, Type: http://schema.openid.net/namePerson, Count: 1 Value: Peter Mularien AX Attribute: nickname, Type: http://schema.openid.net/namePerson/ friendly, Count: 1 Value: pmularien AX Attribute: country, Type: http://schema.openid.net/contact/country/ home, Count: 1 Value: US 我们可以看到AX数据能够很容易从OpenID提供者那里得到,并且支持通过简单API调用访问。在典型的应用 场景下,如前面所讨论,AX信息会用在注册时填入用户基本信息或偏好信息,节省了用户重复键入OpenID简 介中已包含的信息。 现实世界的AX的支持和限制 但是,在现实中AX的承诺实现的很不足。市面上的OpenID提供者对AX支持的都很差,只有少数支持 (myOpenID和Google是最好的)。另外,即使支持标准的提供者在有什么属性对应数据方面也有混乱。比 如,要查询用户的e-mail,即使两个支持AX的提供者所请求的属性名都不一样! 提供者 支持的AX属性 myOpenID http://schema.openid.net/contact/ email http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 242 / 350 页 Google http://axschema.org/contact/email 在写作本书的时候,在OpenID相关的邮件列表中还在讨论怎样更好的标准属性并允许OpenID提供者公 开他们所支持的属性。 AX的替代方式,叫做简单注册(Simple Registration ,SReg)被openid4java所支持,但是并没有暴 露到Spring Security OpenID(由开发人员选择)。这很遗憾,因为SReg实际上很多的提供者支持,超过 AX。AX曾经想作为更开放和灵活的替代SReg的方案,但是缺乏支持和标准化使得妨碍了它被提供者所采用。 支持Google OpenID Google选择一种稍微不同的方式实现OpenID,并没有给用户分配用户友好的OpenID标识符。相反, Google期望提供OpenID的站点使用Google式的提供者URL,并且用户直接向Google提供凭证。为了是这个 处理对用户来说更简单,通用的做法是站点提供了“Sign in with Google”按钮,这会触发Google OpenID提 供者。我们可以添加这个到登录页:
    我们可以看到Google URL不需要暴露给用户。用户会看到这个经典的登录页: http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 243 / 350 页 在Google登录过程完成后,用户的OP-Local Identifier会被返回,并且注册/登录能像其它OpenID提供者一样 处理。 OpenID安全吗? OpenID依赖于OpenID提供者的可靠性以及提供者响应的可验证性,为了应用能够信任基于OpenID的用 户登录,安全和可靠性至关重要。 幸运的是,OpenID规范的设计者注意到了这些关切并实现了一系列的校验步骤来防止响应伪造、重放攻 击以及其它类型的篡改,描述如下: l 响应伪造(Response forgery)通过联合使用公用的密钥(使用OpenID的站点在初始请求之前创建)和对 响应信息本身进行单向hash信息加密来阻止。恶意的用户不能访问共享的密钥和加密算法,伪装响应的任何域 数据都会生成不合法的响应。 l 重放攻击(Replay attacks)通过包含当前时间(nonce)或一次使用的随机key来阻止,这应该保存在使用 OpenID的站点上所以不能重复使用。这样,即使用户试图重新发送响应URL也会失败,因为接受的站点会确定 当前时间(nonce)以前已经被使用过了,这个请求就失效了。 最可能的一种攻击方式可能会导致一个用户交互可能是中间攻击(man-in-the-middle attack)——在这 里恶意用户拦截了用户电脑和OpenID提供者的交互。假设的攻击者在一个地方记录用户浏览器和OpenID的会 话,并记录下请求初始化的密钥。在这种情况下,需要攻击者有很高的水平和相当完整的OpenID签名规则实现 ——简言之,在正常情况下这不会发生。 注意的是,尽管openid4java库支持使用JDBC持久化当前时间(nonce)跟踪,但是Spring Security OpenID当前没有作为一个配置属性暴露——所以当前时间只能在内存中跟踪。这意味着一个重复攻击可能在服 务器重启后或集群环境下发生,在这里内存存储没有在不同服务器的JVM间复制。 小结 在本章中,我们了解了OpenID——一个相对很新的技术用来进行用户认证的凭证管理。OpenID在web 中应用广泛,在最近一两年中在可用性和接受程度上发展迅速。现代web中大多数面向公众的站点都应该规划 一些OpenID支持功能,JBCP Pets也不例外。 在本章中,我们: l 学习了OpenID认证机制并探究了它的整体结构和术语; l 利用JBCP Pets站点,实现了OpenID的登录和自动用户注册功能; l 通过使用属性交换(Attribute Exchange ,AX),了解了OpenID未来的基本信息管理; http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 244 / 350 页 l 检查了OpenID登录响应的安全性。 在本章中,我们涉及到大多数常用基于web的外部认证方法。在下一章中,我们将会介绍一种最常用的基 于目录的企业认证方式:LDAP。在我们要转到下一章的时候,请观察外部和内部认证机制的区别以及相似之 处。 http://lengyun3566.iteye.com 1.38 《Spring Security3》第八章第三部分翻译(属性交换) 第 245 / 350 页 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 发表时间: 2012-01-19 关键字: Spring Security, java EE, 安全, 翻译 第九章 LDAP目录服务 在本章中,我们将会了解轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)以 及它怎样集成到使用Spring Security的应用中以提供认证、授权和用户信息服务。 在本章的内容中,我们将会: l 学习一些LDAP协议相关的基本概念以及服务器实现; l 在Spring Security中配置一个嵌入式的LDAP服务器; l 使用LDAP认证和授权; l 理解LDAP查找和用户匹配背后的模型; l 从标准的LDAP结构中查询额外的用户信息; l 不同的LDAP授权方法,并比较它们的优劣; l 使用Spring Bean声明明确配置Spring Security LDAP; l 连接LDAP目录,包括Microsoft Active Directory进行认证。 理解LDAP LDAP在逻辑目录模型方面能够追溯到超过三十年前——在概念上类似于组织机构图和地址薄。今天, LDAP越来越多的用来作为集中管理组织用户信息的方式,可以将成千的用户分成逻辑上的组并允许在不同的分 布式系统间共享统一的用户信息。 为了安全目的,LDAP经常被用来帮助集中的用户名和密码认证——用户的凭证存储在LDAP目录中,而 对于用户来说,认证请求基于目录进行。这对于管理员来说可以便于管理,因为用户的凭证——登录、密码和 其它信息——都存储在LDAP中的一个地址。另外,组织机构信息,如组和团队的分配、地理位置以及组织的等 级结构,也定义在用户在目录中的位置上。 http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 246 / 350 页 LDAP 如果你以前从来没有使用过LDAP,你可能想知道它到底是什么。我们通过一个Apache Directory Server 1.5的截图作为LDAP模式的例子进行介绍。 让我们从一个特定用户Albert Einstein(截图中高亮显示)的条目开始,我们可以看到Mr. Einstein的组织成 员信息可以从他的树节点往上移动看到。我们可以看到einstein是组织单元(organizational unit,ou)users 的成员,而组织单元本身是域example.com的一部分(截屏中显示的dc代表的域组件“domain component”)。在此之前的是LDAP树本身的组织机构元素(DIT和Root DSE),这些现在与我们无关。用 户aeinstein在LDAP结构中有其语义且意义明确——你可以想象一个巨大组织更复杂的等级结构也能够很容易 的说明其组织机构和部门的边界。 一个叶子节点在树上从上到下的路径形成了包含所有参与节点的字符串,如Mr. Einstein的节点路径为: uid=aeinstein,ou=users,dc=example,dc=com 这个节点路径是唯一的,并被称为节点的标识名(distinguished name,DN)。标识名类似于数据库里的主 键,允许节点在复杂的树结构中唯一标识和定位。我们将会看到的节点的DN广泛应用于Spring Security LDAP 集成的认证和查找过程。 我们可以看到还有几个其他的用户和Mr. Einstein在同一个等级的组织结构上。我们假设所有的这些用户 都与Mr. Einstein在相同的组织下。尽管这个组织机构的例子相对简单,但是LDAP的结构是极其灵活的,使得 很多层级的逻辑组织机构嵌套成为可能。 Spring Security LDAP需要Spring LDAP模块的支持(http://www.springsource.org/ldap),它是单 独于Spring框架核心和Spring Security的工程。它被认为很稳定并对标准的Java LDAP功能进行了有用的封 装。 http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 247 / 350 页 通用的LDAP属性名 树上的每个实际节点都是通过一个或多个的对象类(object class)来定义的。一个对象类是组织机构的 一个逻辑单元,分为一系列具有语义的相关属性。通过将一个条目声明为特定对象类的实例,如person,LDAP 目录的管理人员就能够为目录的用户提供每个条目的确切含义。 LDAP具有很丰富的标准模式(schema),涵盖了可用的LDAP对象类和它们的可用属性(以及一些其它 信息)。如果你计划广泛的使用LDAP,强烈建议你参考一个较好的用户手册,如Zytrax OpenLDAP的附录 (http://www.zytrax.com/books/ldap/ape/ ),或者Internet2组织提供的人员相关模式 (http://middleware.internet2.edu/eduperson/)。 在上一节中,我们了解到LDAP中的每个条目都会有一个标识名,它在树上唯一标识节点。DN有一系列的 属性组成,其中一个(或更多)用来标识树上的用DN代表的路径。因为DN中路径的每一部分都代表一个LDAP 属性,所以你能够通过定义良好的LDAP模式和类对象来确定DN中每个属性的含义。 我们在以下的表格中,列出了一些常见的属性和它们的含义。这些属性是用来作为组织相关的属性——一 意味着它们一般用来定义LDAP树的组织机构——并按照结构上从上到下的顺序,正如你通常在LDAP中会见到 的那样。 属性名 描述 示例 dc 域组件(Domain Component)——一般 为LDAP等级结构中的最高一级组织 dc=jbcppets,dc=com c 国家(Country)——一些LDAP等级结构 中将国家作为很高的等级 c=US o 组织名(Organization name)——一个 LDAP资源分类上的业务组织 o=Sun Microsystems ou 组织单元(Organizational unit)——业 务组织部门,一般在组织之内 ou=Product Development cn 通用名(Common name)——对象的通 用名或唯一名或者为对人可读的名字。对人 cn=Super Visor cn=Jim Bob http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 248 / 350 页 来说一般为人的全名,对于LDAP中的其它 资源(电脑等等)一般为主机名。 uid 用户ID(User ID)——尽管并不是原生作 为组织相关使用,但是Spring一般会查找 uid进行用户认证和搜索 uid=svisor userPassword 用户密码(User password)——存储人对 象相关联的密码。一般会经过SHA或类似的 单向哈希算法。 userPassword=plaintext userPassword={SHA}cryptval 要记住的是有上百个标准的LDAP属性——上面只是其中的一小部分,当你与一个完整LDAP集成的话会 看到它们。但是表中的这些属性是目录树中组织相关的属性,当你配置Spring Security与LDAP交互的时候可能 会用来形成各种查询表达式或匹配符。 运行一个嵌入式的LDAP服务 作为测试,Spring Security允许使用嵌入式的LDAP服务器。就像我们使用嵌入式数据库那样,这使得应 用可以启动一个基于内存的LDAP服务器并插入初始化数据。当然,这样的一个配置只能用于测试的目的,但是 这能够节省我们很多配置单独LDAP服务器的时间。 嵌入式的LDAP服务器功能是通过使用Apache Directory Server (DS) 1.5来支持的,它是一个基于Java、 开源且完全符合规范的LDAP服务器。实际上,你也可以使用Apache DS作为独立的服务器,它很相对很容易配 置并易于获取和安装。本章实例代码中的Dependencies目录下包含了嵌入式LDAP服务器所需要的JAR包—— 如果你要自己使用它的话,你要么使用Maven要么自己到以下地址http://directory.apache.org/ 下载Apache DS。 如同嵌入式的HSQL数据库允许在启动时加载SQL脚本,嵌入式的LDAP服务器提供了启动时从LDAP数据 交换格式(LDAP Data Interchange Format ,LDIF)文件中插入目录的方法。LDIF是一种简洁的且对人和机 器都很易读的数据定义格式,它提供了LDAP对象和支持数据的灵活定义。在本章的源码中提供了几个实例性的 LDIF文件。 配置基本的LDAP集成 现在让我们让JBCP Pets支持基于LDAP的认证。幸运的是,通过使用嵌入式的LDAP服务器和实例LDIF文 件,这是一个相对容易的练习。在这个练习中,我们使用为本书创建的LDIF文件,这个文件用来进行表述LDAP http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 249 / 350 页 和Spring Security的常用配置场景。我们提供了几个其它的LDIF文件,其中一些来自Apache DS 1.5,还有一 个来自SpringSecurity的单元测试,你可能会愿意选择它们进行体验。 配置LDAP服务器引用 第一步是在dogstore-security.xml中声明嵌入式LDAP服务器的引用。LDAP服务器的声明在元素 之外,与相同的等级: 我们从classpath中加载JBCPPets.ldif,并用其为LDAP服务器插入数据。这意味着(如同嵌入式HSQL数据库 启动那样)我们应该在WEB-INF/classes放置JBCPPets.ldif文件。root属性用特定的DN声明了LDAP目录的 根。这应该与我们使用的LDIF文件逻辑根DN相对应。 【注意,对于嵌入式的LDAP服务器,root是必须的,尽管XML模式并没有这样声明。如果它没有指明或指明错 误,你会在Apache DS server启动的时候看待几个奇怪的错误。】 当我们在Spring Security配置文件中声明LDAP用户服务和其它配置元素时,会重用这里定义的bean ID。对于嵌入式的LDAP模式来说,声明的其它属性都是可选的。 启用LDAP AuthenticationProvider 接下来,我们要配置另一个AuthenticationProvider,它用来用LDAP来检查用户凭证。简单得添加另一 个AuthenticationProvider即可,如下: 我们稍后将会介绍这些属性——现在,回到应用并运行,使用用户名ldapguest和密码password进行登录。你 应该能够登录进去了! http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 250 / 350 页 解决嵌入式LDAP的问题 很可能你在使用嵌入式LDAP时,调试问题很困难。Apache DS的出错信息并不友好,这在 SpringSecurity嵌入模式下更严重。如果你不能让这个简单的例子正常运行,请仔细检查以下的地方: l 确保Apache DS依赖的所有JAR都在web应用的classpath下。这会有很多——最好的方式就是包含所有的 (实例代码就是这样做的); l 确保在你的配置文件中设置了root属性,且它与启动时加载的LDIF文件中root的定义相匹配。 如果你遇到了找不到引用的错误,很可能要么缺少root元素,要么与LDIF文件不匹配; l 注意的是启动嵌入式LDAP的错误并不会是一个致命错误。为了分析加载LDIF文件的错误,你需要确保适当设 置了日志,包括Apache DS的日志启用,至少要在ERROR级别。LDIF的加载器在包下,它应该被用来启用LDIF 加载错误的日志; l 如果应用没有被正常关闭,为了重新启动服务,你可能会需要删除临时目录下的一些文件(Windows系统下 为%TEMP%)。这个的出错信息(幸运的是)很清楚。 遗憾的是,嵌入式LDAP并不像嵌入式HSQL数据库那样简单,但是相对于需要下载和配置的很多外部 LDAP服务器来说,已经比较简单了。 一个用于排除问题和访问LDAP的好工具是Apache Directory Studio,它提供了独立的和Eclipse插件的 版本。免费下载地址:http://directory.apache.org/studio/ 。 理解Spring LDAP认证如何工作 我们看到可以使用LDIF文件定义的用户(也就会在LDAP目录中出现)进行登录了。一个LDAP用户进行 登录时到底发生了什么?在LDAP认证过程中有三个基本的步骤: l 将用户提供的凭证与LDAP目录进行认证; l 基于LDAP上的信息,确定用户拥有的GrantedAuthority; l 为了应用以后用到,从LDAP条目中预先加载用户信息到自定义的UserDetails对象中。 认证用户凭证 第一步,通过织入AuthenticationManager的自定义认证提供者与LDAP目录进行认证。 o.s.s.ldap.authentication.LdapAuthenticationProvider将用户提供的凭证与LDAP目录进行校验,如下图所 示: http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 251 / 350 页 我们可以看到o.s.s.ldap.authentication.LdapAuthenticator接口定义了一个代理从而允许提供者以自定义的 方式认证请求。在这里我们明确配置的是o.s.s.ldap.authentication.BindAuthenticator,它会尝试使用用户的 凭证绑定(登录)LDAP服务器,就像用户本身尝试建立连接。对嵌入式的服务器来说,这对于我们的认证要求 是足够的,但是,外部的LDAP服务器在用户绑定LDAP目录上可能要求更严格。幸运的是,还有一种替代的认 证方式,我们将会在本章稍后介绍。 正如图中所标注的那样,记住查找是在引用的manager-dn属性所创建的LDAP上下文中 进行的。对于嵌入式的服务器,我们没有使用这个信息,但是对于外部的服务器引用,除非提供manager- dn,否则的话将会进行匿名绑定。为了保持目录中公开访问信息的限制,通常需要合法的凭证来进行LDAP目录 的搜索,这样的话,manager-dn在现实世界场景中基本上就是必需的了。manager-dn代表了用户的全DN, 基于合法的访问绑定目录并进行查找。 确定用户的角色 http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 252 / 350 页 在用户基于LDAP服务器成功认证之后,接下来必须要进行权限信息的确定。授权是通过安全实体的一系 列角色定义的,LDAP认证过的用户角色确定如下图所示: 我们可以看到,用户在使用LDAP认证之后,LdapAuthenticationProvider委托给了一个 LdapAuthoritiesPopulator。DefaultLdapAuthoritiesPopulator将会尝试在LDAP等级中另一个条目的同级或 下级属性中查找认证用户的DN。(译者注:即在LDAP目录角色相关的条目中寻找当前用户,以确定用户的角 色) 查找用户角色分配的DN是通过group-search-base属性定义的——在我们的例子中,我们这样设置 group-search-base="ou=Groups"。当一个用户的DN在group-search-base DN下面的条目中时,包含用户 DN的条目中的一个属性将会作为这些用户的角色。 【你可能注意到我们混合使用了属性的写法——在类流程图中使用了groupSearchBase,在文本中使用的是 group-search-base。这是有意的——文本中对应的是XML配置属性而图中指的是相关类的成员(属性)。他 们的命名相似,但是在不同的上下文中(XML和Java)要适当调整。】 Spring Security中的角色和LDAP中的用户如何关联还是有点令人迷惑,所以让我们看一下JBCP Pets库 以及用户与角色关联是如何进行的。DefaultLdapAuthoritiesPopulator使用了几个声明的属性来管理为用户查找角色。这些属性大致按以下的顺序使用: l group-search-base:它定义了基础的DN,LDAP集成应该基于此往下为用户查找一个或多个的匹配项。默 认值会在LDAP根中进行查找,这可能会代价较高; http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 253 / 350 页 l group-search-filter:它定义了LDAP查找的过滤器,用来匹配用户的DN与group-search-base之下的条目 属性。这个过滤器通过两个参数进行参数化设置——第一个({0})作为用户的DN,第二个作为({1})作为用 户的名字。默认值为(uniqueMember={0})。 l group-role-attribute:它定义了匹配条目中用来组装用户GrantedAuthority的属性,默认值为cn; l role-prefix:要拼到在group-role-attribute中发现值的前缀以产生Spring Security的GrantedAuthority。 默认值为ROLE_。 这对于新的开发人员可能会比较抽象和困难,因为这与我们基于JDBC的UserDetailsService实现有很大的 区别。让我们以JBCP Pets LDAP目录中的ldapguest用户登录以了解其过程。 用户的DN是uid=ldapguest,ou=Users,dc=jbcppets,dc=com而group-search-base被配置成了 ou=Groups。对于这个ou的LDAP树展现如下: 我们可以看到在ou=Groups之下,有两个条目(cn=Admin和cn=User)。每个条目都具有objectClass: groupOfUniqueNames(你可能会记起我们在本章前面讨论过的对象类)。这种类型的LDAP对象允许多个 DN值存储在这个条目下并进行逻辑分组。条目cn=User的属性列在下图中: 我们可以看到cn=User的uniqueMember属性用来标识这个组里面的LDAP用户。你也可能会发现 uniqueMember的属性值就是对应用户的DN。 http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 254 / 350 页 现在再看角色搜索的逻辑的就很容易了。从ou=Groups (group-search-base)开始,Spring Security将会 查找任何uniqueMember属性值与用户DN(group-search-filter)匹配的条目。当它找到匹配的条目,条目 的cn值(group-role-attribute)——在本例中即为User,将会加上ROLE_ (role-prefix)前缀然后转换成大写 字母组成用户的GrantedAuthority。一旦我们使用过它,再理解起来就容易一些了,不是吗? 【Spring LDAP很灵活。要记住的是尽管这是一个组织LDAP兼容Spring Security的方式,但是通常的使用 场景恰恰相反——LDAP目录已经存在,Spring Security需要织入。在很多场景下,你可以重新配置Spring Security来处理LDAP的等级结构。但是,很关键的一点是你要有效规划并理解Spring在查询时如何与LDAP一 起工作。开动你的大脑,勾画出用户查找和组查找以及你能想到的最优方案——让查询范围尽可能小和精 确。】 如果你此时还是感到困惑,我们建议你休息一下然后尝试使用Apache Directory Studio来看一下运行系统 配置的嵌入式LDAP服务器。如果你按照前面描述的算法,尝试自己查找一下目录将会有助于你了解Spring Security LDAP配置的流程。 匹配UserDetails的其它属性 最后,在通过LDAP查找分配给用户GrantedAuthority后, o.s.s.ldap.userdetails.LdapUserDetailsMapper将会使用o.s.s.ldap.userdetails.UserDetailsContextMapper 来获取另外的细节信息来填充UserDetails。 使用我们到现在为止配置的,LdapUserDetailsMapper将会使用用户 LDAP条目中的信息填充UserDetails对象。 http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 255 / 350 页 我们稍后将会看到UserDetailsContextMapper怎样配置才能从标准的LDAP person和inetOrgPerson中获取 丰富的信息。使用基本的LdapUserDetailsMapper,仅仅能够存储用户名、密码以及GrantedAuthority。 尽管在LDAP用户认证里面还有很多的结构,但是你会发现整体的流程与我们前面学习的JDBC认证很类似 (认证用户、填充GrantedAuthoritys)。如同JDBC认证中那样,在LDAP集成中也有进行高级配置的能力 ——让我们了解的更深入一些并看看还能做什么。 http://lengyun3566.iteye.com 1.39 《Spring Security3》第九章(LDAP)第一部分翻译(LDAP基本配置) 第 256 / 350 页 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 发表时间: 2012-01-19 关键字: Spring Security, 安全, 翻译, Java EE LDAP的高级配置 一旦我们要了解LDAP基础集成之外的知识,就会发现security XML命名空间方式的配置中,Spring Security LDAP模块还有许多的可用配置。它包括查询用户的个人信息、用户认证的其它方式以及使用LDAP作 为UserDetailsService且与DaoAuthenticationProvider结合。 实例JBCP LDAP用户 在JBCP Pets LDIF文件中,我们提供了许多的用户。在高级配置练习和自学中,以下的快速查询表可能会 对你有所帮助。要注意的是除了userwithphone以外,所有用户的密码均为password。 用户名 角色 密码编码 ldapguest ROLE_USER Plaintext anotherldapuser ROLE_USER Plaintext ldapadmin ROLE_USER和 ROLE_ADMIN Plaintext shapassworduser ROLE_USER {sha} sshapassworduser ROLE_USER {ssha} userwithphone ROLE_USER Plaintext(在 telephoneNumber属性中) http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 257 / 350 页 我们将会在后面的章节中介绍为什么密码编码很重要。 密码对比与绑定认证 有一些LDAP服务器可能会配置成不允许特定的用户直接绑定到服务器上,这样的话匿名绑定(这是我们 到此为止所使用的用户搜索办法)就被禁止了。这可能发生在很大规模的组织中,它想要限制能够从目录中读 取信息的用户集合。在这种情况下,标准的Spring Securiry LDAP认证就行不通了,必须使用一种替代策略, 通过o.s.s.ldap.authentication.PasswordComparisonAuthenticator实现(BindAuthenticator的兄弟类)。 PasswordComparisonAuthenticator绑定到LDAP上并查找匹配用户所提供用户名的DN。它接下来会比较用 户提供的密码和匹配的LDAP条目中存储的userPassword属性。如果编码后的密码相匹配,用户认证成功,接 下来的流程与BindAuthenticator相同。 配置基本的密码对比 配置密码对比认证来替换绑定认证很简单,只需在中添加一个子元素即 可,如下: http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 258 / 350 页 默认的PasswordComparisonAuthenticator使用LDAP密码编码算法SHA(回忆一下我们在第四章:凭证安全 存储中讨论过的SHA-1密码加密算法)。在重启服务之后,你可以使用用户名shapassworduser和密码 password尝试登录。 LDAP密码编码和存储 LDAP支持多种的密码加密算法,从简单文本到单向加密算法(类似于我们在第四章中了解到的基于数据 库认证)。最常用的LDAP密码存储格式是SHA(SHA-1单向加密)和SSHA(使用salt值的单向加密算法)。 很多的LDAP实现支持RFC 2307, An Approach for Using LDAP as a Network Information Service (http://tools.ietf.org/html/rfc2307)定义的其它的密码格式。 RFC 2307的设计者在密码存储方面做了一项很高明的事情。从目录中的得到的密码当然是按照一定的算 法(SHA等)进行加密的,但是它以使用的加密算法作为前缀。这使得LDAP服务器能够很容易支持多种密码编 码算法。例如,一个SHA编码的密码在目录中存储如下: {SHA}5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 我们可以看到密码存储算法很清楚地进行了标明(使用{SHA}标识),并于密码一起存储。 SSHA是尝试联合使用SHA-1哈希算法和密码salting,以防止目录攻击。正如我们在第四章中所了解的那 样,salt在计算hash之前添加到密码中。当经过hash的密码存储到目录中后,salt值也拼在hash后的密码上。 密码以{SSHA}开头,这样LDAP服务器能够知道用户提供的密码需要以不同的方式进行对比。大多数的现代 LDAP使用SSHA作为默认的密码存储算法。 密码对比认证的缺点 现在你了解了一些关于LDAP用户密码并建立了PasswordComparisonAuthenticator,这是请你想一下 如果使用sshapassworduser用户以及SSHA格式存储的密码登录会发生什么?试一下——把书放在一边,尝试 一下,然后回来。 你的登录被拒绝了,对不?可是你还能够使用SHA编码密码的用户登录——为什么?当我们使用绑定授权 时,密码编码方式和存储并不会影响我们——你觉得这会是为什么呢? http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 259 / 350 页 绑定认证不会受到影响是因为LDAP服务器进行了认证和校验用户密码。在使用密码对比认证的时候, Spring Security LDAP负责将密码编码成目录期望的格式然后与目录进行对比进行认证校验。 为了安全,密码对比策略并不能真正从目录上读取(基于安全策略,读取目录的密码通常会被拒绝)。作 为替代,PasswordComparisonAuthenticator会从用户的目录条目作为根节点进行一个LDAP查找,试图查找 与Spring Security编码密码值匹配的密码属性值。所以,当我们以sshapassworduser登录时, PasswordComparisonAuthenticator使用配置的SHA算法对密码进行编码并试图进行简单查找,这个查找失 败了,因为目录中的用户密码是以SSHA的格式存储的。 当我们将PasswordComparisonAuthenticator的hash算法改成使用SSHA会发生什么呢,如下: 重新启动并尝试——它还是不能好用。让我们想一下可能为什么。记住SSHA使用的是salted密码,而salt值是 与密码一起存储LDAP目录上的。但是PasswordComparisonAuthenticator编码并不能从LDAP中获取任何信 息(这在不允许的绑定的公司中是违背安全策略的)。所以当PasswordComparisonAuthenticator计算hash 密码时,它不能确定使用什么salt值。 总之,PasswordComparisonAuthenticator在特定的环境下很有用(要考虑目录本身的安全),但是它 并不像直接绑定认证灵活。 配置UserDetailsContextMapper 正如我们在前面讲到的,一个o.s.s.ldap.userdetails.UserDetailsContextMapper实例用来匹配用户条目 和内存中的UserDetails对象。默认UserDetailsContextMapper的行为与JdbcDaoImpl类似,都是将一定数量 的细节信息填充到要返回的UserDetails中——也就是说,除了用户名和密码以外并没有太多的信息。 但是,LDAP目录可以包括除了用户名、密码和角色以外更多的用户细节信息。Spring Security提供了方 法从两个标准的LDAP对象模式——person和inetOrgPerson获取更多的用户信息。 明确配置UserDetailsContextMapper 为了配置一个不同与默认实现的UserDetailsContextMapper,我们只需简单声明希望 LdapAuthenticationProvider返回什么类型的LdapUserDetails类即可。security命名空间解析器能够根据要求 的LdapUserDetails类型,足够智能地实例化正确的UserDetailsContextMapper。 让我们配置使用inetOrgPerson版本的匹配器。 http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 260 / 350 页 如果你重启应用并尝试使用LDAP用户登录,你会发现没有任何变化。实际上,UserDetailsContextMapper在 幕后已经发生了变化,在本例中会根据inetOrgPerson模式从用户目录条目中读取额外的细节信息。 查看其它的用户细节 作为这节的辅助功能,我们将在JBCP Pets的账号信息页面增加“View LDAP User Profile”区域。我们 会用这个页面来阐述更丰富的person和inetOrgPerson LDAP模式能提供更多(可选)的信息给使用LDAP的应 用。 添加以下逻辑到AccountController中: @RequestMapping(value="/account/viewLdapUserProfile. do",method=RequestMethod.GET) public void showViewLdapUserProfilePage(ModelMap model) { final Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); model.addAttribute("user", principal); if(principal instanceof LdapUserDetailsImpl) { model.addAttribute("isLdapUserDetails", Boolean.TRUE); } if(principal instanceof Person) { model.addAttribute("isLdapPerson", Boolean.TRUE); } if(principal instanceof InetOrgPerson) { model.addAttribute("isLdapInetOrgPerson", Boolean.TRUE); } } 这个代码将会查询LdapAuthenticationProvider存储在Authentication对象中的UserDetails (principal),并 确定是什么类型的LdapUserDetailsImpl。页面的代码则要根据不同的UserDetails类型显示绑定到用户认证信 息上的细节,JSP代码如下。这个JSP应该放在WebContent/WEB-INF/views/account/ viewLdapUserProfile.jsp。 http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 261 / 350 页

    View Profile

    Some information about you, from LDAP:

    • Username: ${user.username}
    • DN: ${user.dn}
    • Description: ${user.description}
    • Telephone: ${user.telephoneNumber}
    • Full Name(s): ${cn}
    • Email: ${user.mail}
    • Street: ${user.street}
    我们也可以在WebContent/WEB-INF/views/account/home.jsp加一个链接:
  • View LDAP User Profile
  • 我们已经增加了另外的两个用户即shapasswordperson和shapasswordinetorgperson,你可以用它们来进行 比较可用数据元素的不同。重启系统并查看“View LDAP User Profile”页面的三种不同类型的用户(非 person,person和inetOrgPerson)。你会发现,当user-details-class被配置成inetOrgPerson时,尽管要返 回的是o.s.s.ldap.userdetails.InetOrgPerson,但是其中的域可能被填充也可能没有填充,这取决与目录条目 中可以得到的属性。 实际上,inetOrgPerson拥有很多的属性远超过我们这个简单页面所提到的。你可以查看所有属性的列 表:RFC 2798, Definition of the inetOrgPerson LDAP Object Class (http://tools.ietf.org/html/rfc2798)。 http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 262 / 350 页 你可能会意识到,没有办法支持条目中指明的而标准模式中没有的属性。标准的 UserDetailsContextMappers不支持任意列表的属性,但是可以通过使用user-context-mapper-ref属性指向 自定义的UserDetailsContextMapper引用。 使用可代替的密码属性 在很多场景中,可能会需要使用其它的LDAP属性而不是userPassword进行认证。这可能在公司已经部署 完自定义的LDAP模式时发生或不需要进行比较严格的密码管理(可以预见的是,这不是一个好主意,但在现实 世界中它就存在)。 PasswordComparisonAuthenticator也支持对用户的密码与一个其它的LDAP条目属性进行校验,而不 是标准的userPassword属性。这很容易配置,我们可以提供一个很简单的例子,使用简单文本的 telephoneNumber属性,如下: 我们可以重启服务并使用用户userwithphone和密码(即电话号码)1112223333进行登录。 当然,这种方式的认证具有我们前面讨论过的PasswordComparisonAuthenticator基础认证的所有风 险。但是,万一在LDAP中使用它的时候要注意。 使用LDAP作为UserDetailsService 需要注意的一件事就是LDAP也可以用作UserDetailsService。要记住的是UserDetailsService在Spring Security中要启用很多其它的功能,包括remember me和OpenID认证功能。 配置LDAP作为UserDetailsService到LDAP AuthenticationProvider中是很简单的。就像JDBC UserDetailsService,一个LDAP UserDetailsService作为的兄弟节点进行声明。 http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 263 / 350 页 在功能上,o.s.s.ldap.userdetails.LdapUserDetailsService的配置与LdapAuthenticationProvider的配置基本 完全相同,除了没有尝试使用安全实体的用户名绑定LDAP。相反的,应用提供的凭证信息用来 进行用户的查找。 【如果你想自己使用LDAP认证用户,请避免使用user-details-service-ref(引用了一个 LdapUserDetailsService)配置的常见错误。】 就像我们前面提到的那样,LdapUserDetailsService使用提供的manager-dn来获取自己 的信息——这意味着它不会尝试绑定用户到LDAP上,这可能与你期望的行为不一样。LdapUserDetailsService 一般用来支持系统的其它组件,如OpenID登录或remember me中,在这里认证已经通过的但是用户的详细信 息在认证过程中并没有提供。 注意使用LDAP UserDetailsService时的remember me 注意的是,如果此时重新启动服务器会失败,并有如下的错误信息: More than one UserDetailsService registered. Please use a specific Id reference in or elements. 如果我们回忆一下在第三章:增强用户体验和第四章:凭证安全存储中对于remember me功能的介绍,我们 就会记得remember me依赖UserDetailsService来查找remember me cookie中的用户名。不幸的是, AbstractRememberMeServices只可能查找一个UserDetailsService获取用户信息。所以,我们使用 remember me功能时只能有一个配置的UserDetailsService而不能是两个。这使得使用相同的登录页同时支持 LDAP认证和JDBC,并为用户提供remember me功能变得很难实现。调整配置引用某一个 UserDetailsService(通过Spring Bean ID)足以明确告诉Spring Security你想做什么。 配置基于内存的remember me服务 在LDAP中另一个关于使用remember me要注意的是——为了给LDAP认证的用户提供remember me功 能,你必须(通常会这样)使用基于JDBC持久化的token remember me服务。 你可能还记得在第三章中讨论的remember me cookie的组成,基于内存的remember me算法(在 InMemoryTokenRepositoryImpl)依赖于从UserDetails得到的用户名和密码。在很多场景下,LDAP服务器 配置成不允许读取userPassword属性(这就是为什么PasswordComparisonAuthenticator写成那个样子), 所以LdapUserDetailsMapper很可能不能为UserDetails对象填充password属性。缺失这个关键的属性会导致 创建remember me cookie失败,而且会潜在的影响cookie的安全。 http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 264 / 350 页 避免这个问题也很简单——配置位于JDBC中的remember me cookie存储,它只依赖于用户名来校验 cookie(我们在第四章已经讨论过)。 集成外部的LDAP服务器 很可能你在测试完与嵌入式LDAP服务器集成后就要与外部的LDAP 服务器交互了。幸运的是,这是简 单,通过建立嵌入式LDAP 服务器的指令的简单语法就能完成。 以下的代码就是一个连接在10389端口上的外部LDAP 服务器的示例配置: 这里的明显区别(除了LDAP URL)就是提供了用户的DN和密码。这个账号(实际上是可选的)应该允许绑定 到目录上并且能够进行所有相关用户和组信息的查询。使用这些凭证对LDAP服务器URL的绑定结果就是原来进 行安全系统其它的LDAP操作。 注意很多的LDAP服务器支持SSL加密的LDAP(LDAPS)——这无疑也是出于安全的考虑,Spring LDAP 也支持。只需在LDAP服务器URL上使用ldaps://开始即可。LDAPS通常运行在636TCP端口。 注意有很多的商业或非商业的LDAP实现。用于连接、用户绑定、填充GrantedAuthoritys的具体配置完 全取决于其提供者和目录的结构。在下一个章节中,我们会讲解一个很通用的LDAP实现,即Microsoft Active Directory。 http://lengyun3566.iteye.com 1.40 《Spring Security3》第九章(LDAP)第二部分翻译(LDAP高级配置) 第 265 / 350 页 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 发表时间: 2012-01-19 明确的LDAP bean配置 在本节中,我们带领你学会明确配置以下两项功能所需要的bean集合,即连接外部的LDAP服务器和支持 授权的LdapAuthenticationProvider。正如其它基于bean的配置,你可能不希望这样做,除非你发现security 命名空间风格的配置不能支持你的业务或技术需求——在这种情况下,请继续阅读。 配置外部的LDAP服务器引用 为了实现这个配置,我们假设在本地的10389端口上运行着LDAP服务器,与前面章节的对应有相同的配置。需要的bean定义在dogstore-base.xml中提供,如下: 接下来,我们需要配置稍微复杂一些的LdapAuthenticationProvider。 配置LdapAuthenticationProvider 如果你已经读过并理解本章中关于Spring Security LDAP如何工作的讲解,那这个bean配置就很容易理 解了,尽管稍有些复杂。我们将会配置一个包含如下特性的LdapAuthenticationProvider: l 用户的凭证进行绑定认证(不是密码对比); l 在UserDetailsContextMapper中使用InetOrgPerson 让我们开始吧——首先我们声明LdapAuthenticationProvider: http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 266 / 350 页 接下来是BindAuthenticator以及支持的FilterBasedLdapUserSearch bean(用来在绑定前在LDAP目录中定 位用户的DN): 最后是LdapAuthoritiesPopulator和UserDetailsContextMapper,它们的角色我们在前文中已经 介绍过: http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 267 / 350 页 最后需要配置的元素是对我们LdapAuthenticationProvider的引用,我们将会在dogstore- security.xml中通过引用进行声明: 到此时为止,我们已经使用明确Spring bean的方法配置了LDAP认证。正如我们在第六章:高级配置和扩展第 一次引入明确bean配置那样,在LDAP集成中使用这项技术能够在security命名空间配置没有暴露特定属性时, 或者需要自定义的实现类以满足特定业务场景的地方很有用。接下来,我们将会介绍一个这样的场景即怎样通 过LDAP连接到Microsoft Active Directory。 通过LDAP集成Microsoft Active Directory Microsoft Active Directory的一个便利功能是它不仅能够很好的与基于Microsoft Windows网络的结构 集成,还能够配置成使用LDAP协议保罗Active Directory内容。如果你们是使用Windows的公司,很可能任何 的LDAP集成都是基于你的Active Directory实例。 取决于你的Microsoft Active Directory配置(以及目录管理员支持Spring Security LDAP的意愿),你 可能遇到的困难不在认证和绑定的过程中而是匹配Active Directory中的信息到Spring Security系统中的用户 GrantedAuthority上。 JBCP Pets公司的Active Directory LDAP树的实例在LDAP浏览器中的截图如下: http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 268 / 350 页 在这里你没有看到前面实例LDAP结构中的ou=Groups——这是因为Active Directory在用户条目本身上以属 性的方式存储分组信息。内置的Spring Security(本书出版之时)并没有提供支持典型Active Directory LDAP 树的LdapAuthoritiesPopulator。 让我们使用刚刚了解到的明确配置bean的方式来织入一个简单的LdapAuthoritiesPopulator实现,它只 是简单的为每个LDAP认证的用户授予ROLE_USER角色。我们将这个类称为 com.packtpub.springsecurity.security.SimpleRoleGrantingLdapAuthoritiesPopulator public class SimpleRoleGrantingLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator { protected String role = "ROLE_USER"; public Collection getGrantedAuthorities( DirContextOperations userData, String username) { GrantedAuthority ga = new GrantedAuthorityImpl(role); return Arrays.asList(ga); http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 269 / 350 页 } public String getRole() { return role; } public void setRole(String role) { this.role = role; } } 并不十分令人兴奋,但是要记住如果需要的话你可以使用传入的o.s.ldap.core.DirContextOperations对象进 行很多类型的LDAP操作,如查询目录获取用户的信息以及使用这些信息填充返回的角色。 假设我们从上一节中的bean配置开始,让我们看一下怎样修改配置以支持Active Directory结构。 属性sAMAccountName是在Active Directory中与标准LDAP条目的uid属性对等的——其它的配置修改都是 典型的Active Directory配置。 尽管大多数的Active Directory LDAP集成都可能比本例复杂,但这会给你一个深入理解和探索Spring Security LDAP内部原理的起点,会使得支持更复杂的集成会简单一点。 http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 270 / 350 页 注意存在另外一种与Active Directory集成的方式——Kerberos认证,它是Spring Security扩展项目的 一部分,并会在第十二章:Spring Security扩展中涉及到。你可能会愿意了解两种类型的集成并确定哪种更适 合你的需求(我们将会在第十二章中,比较Active Directory的LDAP和Kerberos认证)。 委托角色查询给UserDetailsService 最后一项明确配置bean支持的技术就是在UserDetailsService中根据用户名查找用户并在此处获取 GrantedAuthority。配置很简单就像声明一个bean,含有一个对UserDetailsService引用(就像基于JDBC的 或我们在前面的章节中用到过的): 你可能会预见到的逻辑和管理问题是用户名和角色必须同时在LDAP中和UserDetailsService所有的存储中管理 ——在大型用户场景下这可能不是一个灵活的模式。 这种场景的通常用在需要LDAP认证确保应用中的用户即为公司用户,但是应用本身想存储授权信息。这 会导致潜在的应用相关数据在LDAP目录以外,这可能会是出于分开操作的考虑。 小结 我们已经看到在接受请求时,可以依赖LDAP服务器提供认证和授权信息,以及丰富的用户个人信息。在 本章中,我们学到了: l LDAP术语和概念,并理解了LDAP 目录怎样组织以支持Spring Security; l 在Spring Security配置文件中配置单独的(嵌入式的)和外部的LDAP服务器; l 基于LDAP存储认证和授权用户,以及随后的匹配Spring Security角色; l LDAP中不同的认证模式、密码存储和安全机制——以及在Spring Security中怎样处理; l 匹配LDAP目录中的用户详细信息到UserDetails对象中,实现LDAP和使用Spring应用的属性交换; l LDAP的明确bean配置,以及这种方式的优劣。 http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 271 / 350 页 http://lengyun3566.iteye.com 1.41 《Spring Security3》第九章(LDAP)第三部分翻译(LDAP明确配置) 第 272 / 350 页 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 发表时间: 2012-01-19 关键字: Spring Security, 安全, 翻译, Java EE 第十章 使用中心认证服务(CAS)进行单点登录 在本章中,我们将会介绍使用中心认证服务(Central Authentication Service,CAS)为基于Spring Security的应用提供单点登录门户(single sign-on portal)。 在本章的内容中,我们将会: l 学习CAS,包括它的架构以及对于系统管理员和各种规模组织的好处; l 理解Spring Security怎样配置以处理认证请求拦截并重定向到CAS; l 配置JBCP Pets应用以使用CAS的单点登录; l 集成CAS和LDAP,并将数据通过CAS从LDAP传递到Spring Security; l 了解CAS 2.0和SAML1.1的区别。 CAS简介 中心认证服务(Central Authentication Service,CAS)是一个开源的单点登录门户,提供了中心访问 控制以及组织内基于web资源的认证。CAS对管理员的好处是很大的,它支持很多的应用和不同的用户社区: l 个人或组对资源(应用)的访问可以配置到一个地方; l 广泛支持各种认证存储(为了集中管理用户),这提供了单点认证和对广泛分布、跨机器环境的控制; l 通过CAS客户端类库支持基于web和非基于web的Java应用的广泛支持; l 只有一个地方有用户凭证信息(通过CAS),所以CAS的客户端应用不需要关于用户凭证信息的任何情况和如 何验证它们。 我们本章主要关注的不是如何管理CAS,而重点是认证以及CAS怎样为我们的站点的用户提供认证。尽管 CAS一般在企业或教育机构的intranet中见到,但是也能在一些面向公众的站点中见到如Sony Online Entertainment's 和Dun and Bradstreet's http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 273 / 350 页 CAS认证整体流程 CAS的基本认证流程包含以下的行为: l 用户试图访问站点上的受保护资源; l 站点的安全机制将用户重定向到CAS门户以要求用户提供凭证; l CAS门户负责用户认证。如果CAS认证成功,用户被重定向会受保护的资源并带有分配给该请求的一个唯一 CAS ticket; l 站点的安全机制重新访问CAS服务器来校验这个ticket是否可接受(合法且没有过期等等)。CAS服务器返回 一个assertion来标识已经建立起了信任关系。如果ticket被接受的话,信任已经建立,根据通常的授权检查用 户可能已经可以进行下一步的操作。 直观起见,以上的过程在下面的图中进行了阐述: 从上面可以看到CAS服务器和受保护应用的整体交互,在用户信任之前要进行几次的数据交换握手。这种 http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 274 / 350 页 单点登录协议的复杂性使得很难通过常用的技术进行破解(考虑一下其它网络安全技术的预防措施,如使用 SSL或网络监控)。 既然我们已经了解了CAS认证整体上如何工作,那看一下它怎样应用于Spring Security。 Spring与CAS Spring Security拥有与CAS集成的强大功能,尽管没有像我们前面介绍OpenID和LDAP那样集成到 security命名空间风格的配置中。作为替代,很多的配置依赖于bean织入和security命名空间中引用配置的 bean声明。 基本的CAS认证涉及到以下两点: l 替换标准的AuthenticationEntryPoint——它一般处理未认证用户重定向到登录页面——为将用户定向到CAS 门户的实现; l 当用户从CAS门户重定向回受保护资源时,处理分配的ticket,这通过使用一个自定义的过滤器实现。 关于CAS有一个很重要的事情要理解,在典型的部署环境中,CAS原来替换应用中所有的登录机制。这样, 一旦为Spring Security配置了CAS,你的用户必须只能使用CAS作为你应用的认证机制。在大多数场景下,这 不是什么问题,正如我们在前面讨论的,CAS设计用来代理认证请求到一个或多个的认证存储(类似于Spring Security委托给数据库或LDAP认证那样)。我们在这个图中可以看到,我们的应用不再检查认证存储以校验用 户(尽管还需要一个数据源用来完整填充认证用户的UserDetails对象)。 当我们完成CAS与Spring Security集成的基本配置,我们可以从首页中移除“Log In”链接,当我们访问 受限资源时会自动重定向到CAS的登录界面。当然,取决于应用,也可以允许用户明确进行登录(这样他们可 以看到自定义的内容等等。) CAS的安装和配置 CAS的好处是它背后有一个专业的团队不仅开发优秀的软件还提供精确完整的使用文档。如果你愿意练习 本章的例子,我们建议你阅读CAS系统的“Get Started”手册。我们假设你在本章中将CAS部署到以下的位 置:http://localhost:8080/cas/ 在本章中,我们建议将CAS和JBCP Pets运行在两个不同的Tomcat实例中。这会使得你在修改JBCP Pets 代码时,保持CAS正常运行。我们建议你配置CAS服务器在8080端口,JBCP Pets服务器在8081端口——我们 在本章的例子是这样的配置。另外,本章中的例子用的是CAS服务器的最新版本,在写本书的时候为3.3.5。 【要注意的是,在CAS的3.x版本中对于一些后台的类做了很大的修改,所以如果你使用更在版本的CAS服 务器,这些指令在你的环境下会有或多或少的不同。】 让我们继续并配置CAS认证所需要的组件。 http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 275 / 350 页 基本的CAS集成配置 以下的图表说明了要将CAS认证集成到JBCP Pets应用中所要配置的Spring Security组件之间的关系。这 应该会帮助你在后面配置bean时,对此有整体的认识。 添加CasAuthenticationEntryPoint 你可能会记得在第六章:高级配置与扩展中,AuthenticationEntryPoint的实现类用来将授权失败相关的 异常转换成正确的行为——例如将未认证的用户重定向到登录页或者给用户展现出缺乏相应权限的出错页。从 整体结构图中,我们可以看到有一个对用户访问受保护资源的拦截器,它将用户重定向到CAS门户页面——这 是通过使用专门用来处理这个问题的o.s.s.cas.web.CasAuthenticationEntryPoint来完成的。 我们在dogstore-base.xml中配置这个支持bean的行为,如下: http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 276 / 350 页 紧接着,在dogstore-security.xml文件的security命名空间配置中添加对这个bean的引用: 最后,我们需要声明在CasAuthenticationEntryPoint中的serviceProperties引用,它是一个 o.s.s.cas.ServiceProperties对象,声明了服务(应用)的属性(代表了我们的应用)。根据CAS认证过滤器的 定义,我们声明的URL如下: 我们可以看到ServiceProperties对象在不同的CAS组件间交换数据中扮演了重要角色——它被用作数据对象, 来存储Spring CAS不同参与者共享(同时相匹配)的CAS配置。 【CAS将要认证访问的资源成为服务。服务以唯一的URL来进行标识。在CAS的管理界面上,服务能够被 CAS管理员进行定义和配置。】 译者注:此处所谓的服务,指的就是各个使用CAS进行认证的应用。 其中service属性告知CAS,要对那个服务进行用户认证。回忆一下,CAS允许基于配置针对每个用户、每 个应用进行有选择的授权。我们稍后配置处理该URL的过滤器时,还会介绍这个特殊URL。 如果你此时重新启动应用并试图访问受保护的资源,你将会立即被重定向到CAS门户进行认证。在我们应 用中的一个受保护页面的例子是“My Account”页面——它要求用户被授予了ROLE_USER权限。CAS的默认 设置允许认证任何用户名和密码相同的用户,你可以使用用户名admin和密码admin(或guest/guest)登录。 http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 277 / 350 页 但是,你会发现,即使在登录后,你也会被立即再次重定向到CAS门户上。这是因为,尽管目标应用能够 收到ticket,但是它不能校验它,所以CAS会处理AccessDeniedException异常并认为拒绝该ticket。 启用CAS票据校验 从前面“基本的CAS集成配置”一节的图中 我们可以看到,Spring Security借助 FilterSecurityInterceptor来负责识别未认证的请求并将用户重定向到CAS。添加的 CasAuthenticationEntryPoint重写了重定向到登录页的标准功能,并提供了从应用到CAS服务器的重定向。现 在我们需要配置其它的东西,一旦经过了CAS的认证,用户应该在应用中也获得正确认证。 我们记得在第八章:对OpenID开放中讲到使用了类似的重定向方法,即将未认证的用户重定向到 OpenID提供者上进行认证,并在校验凭证后重新返回到应用中。CAS与OpenID的区别在于,基于用户请求返 回到应用中时(译者注:即在CAS上登录成功后),应用要请求CAS服务器来明确校验提供的凭证是合法准确 的。而OpenID使用的是基于时间的nonce和共享的基于key的签名,所以OpenID提供者传回的凭证信息能够 被独立进行校验。(译者注:即OpenID和CAS在各自认证成功后都会通知应用,但CAS还需要应用与其进行交 互以校验ticket的正确性,而使用OpenID的应用不需要在于OpenID提供者进行交互)。 CAS方式的好处在于CAS服务器传递回来的认证用户信息很简单——只是简单的从CAS服务器返回给应用 的一个URL参数。另外,应用本身并不需要跟踪和校验ticket,相反可以完全依赖CAS校验这个信息。与我们在 OpenID中看到的很类似,一个过滤器负责识别CAS的重定向并将其视为认证请求。我们先在dogstore- base.xml中,配置这个过滤器为一个Spring bean,如下: 接下来,我们需要在dogstore-security.xml文件的security命名空间配置中,添加我们的自定义过滤器: ... 最后,我们需要声明一个CasAuthenticationFilter需要的AuthenticationManager——这是通过在dogstore- security.xml中添加(如果还没有存在的话)元素的alias属性做到的: http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 278 / 350 页 你可能已经发现在配置ServiceProperties对象时,我们将CAS服务名字定义为如下的URL: http://localhost:8081/JBCPPets/j_spring_cas_security_check。正如我们在其它认证过滤器中所见过的那 样,/j_spring_cas_security_check URL表明的是CasAuthenticationFilter认识的URL签名,专门用于CAS服务 器的重定向响应。 【修改CAS服务URL。你可能希望修改默认的CAS服务URL /j_spring_cas_security_check——这能通过 设置CasAuthenticationFilter的filterProcessesUrl属性为任意的相对URL。在有些时候修改默认的URL会有利 于掩盖你的应用使用了Spring Security和/或CAS——尽管这种安全掩盖的并不能保证万无一失,但是能够预防 一些针对公众站点的攻击。】 CasAuthenticationFilter用一些特定的凭证填充Authentication(一个 UsernamePasswordAuthenticationToken)供下一个也即CAS最小化配置的最后一个元素识别。 使用CasAuthenticationProvider证明可靠性 如果你跟随本书了解Spring Security的逻辑流程,希望你会知道接下来是什么——Authentication token必须要用合适的AuthenticationProvider进行检验。CAS也不例外,所以最后的问题在于配置 o.s.s.cas.authentication.CasAuthenticationProvider到AuthenticationManager中。 首先,我们需要在dogstore-base.xml中声明一个Spring bean,如下: 接下来,我们要在dogstore-security.xml中配置对新AuthenticationProvider的引用,位于声明中: http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 279 / 350 页 如果你在还有前面练习的其它AuthenticationProvider,在于CAS共同使用时,记住将它们移走。现在,我们 需要处理CasAuthenticationProvider中的其它属性和bean引用。 属性ticketValidator引用了接口org.jasig.cas.client.validation.TicketValidator的实现——因为我们使用 的是CAS 2.0认证,我们要声明一个org.jasig.cas.client.validation.Cas20ServiceTicketValidator的实例,如 下: 这个类的构造参数要(再一次)引用访问CAS服务器的URL。在这里你会注意到,我们已经离开了 org.springframework.security包而到了org.jasig包中,这是CAS客户端JAR文件的一部分。我们稍后会看到 TicketValidator接口拥有支持其它CAS认证的实现(还是在CAS客户端JAR文件中),如SAML认证。 【外部URL和独立环境的设置。如果你使用CAS配置真正的应用,在这里你可能会想到在Spring配置文件中写 URL不是一个好主意。一般来说,对URL的存储和统一引用会抽取到单独的properties文件中,通过Spring PropertyPlaceholderConfigurer的占位符使用。这可以通过外部的properties文件重新设置特定环境的配置而 不用关心Spring配置文件,这通常被认为是好的方式。】 接下来,看key属性——它用来校验UsernamePasswordAuthenticationToken的完整性,因为后者是可 能随意定义的(译者注:即恶意用户仿造的)。 最后,authenticationUserDetailsService属性引用了一个 o.s.s.core.userdetails.AuthenticationUserDetailsService对象,而它是用来将Authentication提供的用户名 信息转换成完整的UserDetails。显然,这项技术只有在我们确认了Authentication的完整性后才会用到。我们 配置这个对象引用了UserDetailsService 的实现JdbcDaoImpl。 http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 280 / 350 页 你可能会问,为什么不直接引用UserDetailsService——这是因为还有更高级的配置选项,它允许用CAS服务 器返回的细节信息填充UserDetails对象。 到此时,我们可以完成并通过CAS进行登录并重定向回JBCP Pets应用。干得好! http://lengyun3566.iteye.com 1.42 《Spring Security3》第十章(CAS)第一部分翻译(CAS基本配置) 第 281 / 350 页 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 发表时间: 2012-01-19 关键字: Spring Security, 安全, 翻译, Java EE 高级CAS配置 CAS认证框架提供了高级的配置和与CAS服务的数据交换。在本节中,我们将会介绍CAS集成的高级配 置。在我们觉得重要的地方将会包含相关的CAS配置指令,但是要记住的是CAS配置是很复杂的并超出了本书的 范围。 从CAS assertion中获取属性 在CAS服务器传递ticket校验结果时,可以将基于CAS认证时查询到的信息进行传递(给CAS服务)。这 些信息以键值对的方式进行传递,并可以包含用户相关的任何数据。我们将会使用这个功能在CAS响应中传递 用户的属性,包括GrantedAuthority信息。 CAS内部如何工作 在进入CAS配置之前,我们简单介绍CAS认证过程的标准行为。下图将会帮助你理解CAS与嵌入式LDAP 服务器交互的配置步骤: http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 282 / 350 页 上图描述了CAS服务器内部的认证流程,如果你要实现Spring Security与CAS的集成,你可能要修改CAS服务 器的配置。所以,理解CAS认证的整体流程如何工作是很重要的。 CAS服务器的org.jasig.cas.authentication.AuthenticationManager(不要与SpringSecurity的同名类 相混淆)负责基于提供的凭证信息进行用户认证。与Spring Security很相似,实际的认证委托给了一个(或更 多)实现了org.jasig.cas.authentication.handler.AuthenticationHandler接口的处理类(在Spring Security 中对应的接口是AuthenticationProvider)。 最后,一个org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver用来将传递进来的 安全实体信息转换成完整的org.jasig.cas.authentication.principal.Principal(类似于Spring Security中 UserDetailsService实现所作的那样)。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 283 / 350 页 尽管没有没有完整介绍CAS服务器的完整功能,但是这些也能够帮助你理解下面几个练习中的配置步骤。 我们建议你阅读CAS的源码并学习网络上的文档,在JA-SIG CAS wiki上,地址为: http://www.ja-sig.org/ wiki/display/CAS。 配置CAS连接嵌入式LDAP服务器 CAS默认配置的 org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver并不允许我们在 与Spring Security CAS集成时,传递回属性信息,所以我们建议使用一个允许我们这样做的实现。 一个很容易配置和使用的认证处理(尤其是你学习了前章中的LDAP练习)是 org.jasig.cas.authentication.handler.AuthenticationHandler,它会与我们前章中的嵌入式LDAP服务器通 信。在后文中,我们将会介绍配置CAS返回LDAP属性。 所有CAS的配置都会在CAS安装后的WEB-INF/deployerConfigContext.xml文件中,将会涉及到插入类 声明到已经存在的配置文件片段中。 【如果这个文件的内容你感到熟悉,那是因为CAS使用Spring框架进行配置,就像JBCP Pets!如果你想 深入了解这些配置是做什么的,我们建议你使用一个较好的IDE来方便的查看CAS的源码。记住在本节及其余所 有章节中,我们提到的WEB-INF/deployerConfigContext.xml,指的是CAS安装环境中而不是JBCP Pets。】 首先,我们需要添加一个AuthenticationHandler来代替 SimpleTestUsernamePasswordAuthenticationHandler,它将会试图绑定用户到LDAP上(就像我们在第九 章:LDAP目录服务中做的那样)。AuthenticationHandler要放在authenticationManager bean的 authenticationHandlers属性中: 不要忘记移除SimpleTestUsernamePasswordAuthenticationHandler,或者至少将其定义移到 BindLdapAuthenticationHandler后面,否则你的CAS认证不会使用LDAP而是会使用这个默认实现。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 284 / 350 页 你可能会意识到这个bean引用了一个contextSource bean——它定义了 org.springframework.ldap.core.ContextSource实现,而CAS将会用它与LDAP交互(对,CAS也使用了 Spring LDAP)。我们将会在文件的最后进行定义,如下: ldap://127.0.0.1:33389 java.naming.security.authentication simple 与我们配置Spring Security连接外部的(非嵌入式的)LDAP服务器很类似,CAS需要一个用户DN作为管理用 户以首先绑定到LDAP目录上。在本例中,我们使用的管理员是在第九章的JBCPPets.ldif启动练习中定义的。 URL ldap://127.0.0.1:33389包含了端口33389,是Spring Security嵌入式LDAP服务器默认使用的。正如我们 在第九章讨论的,在产品配置中,你可能更会使用LDAPS以确保CAS LDAP请求的安全。 最后,我们需要配置一个新的 org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver,它负责将用户提供的凭证(CAS已 经使用BindLdapAuthenticationHandler进行了认证)转换成完整的 org.jasig.cas.authentication.principal.Principal认证实体。你会发现在这个类中有很多配置选项,我们会将其 略过,但是欢迎你深入了解CAS。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 285 / 350 页 在CAS authenticationManager bean中的credentialsToPrincipalResolvers属性中,添加如下的内联 bean定义: 你会发现就像Spring Security LDAP配置,相同的行为也在CAS中存在即基于DN中的目录子树根据属性匹配 搜索安全实体。 注意,我们还没有配置attributeRepository,它引用了 org.jasig.services.persondir.IpersonAttributeDao的实现。CAS提供了默认的配置,包含了这个接口的简单 实现org.jasig.services.persondir.support.StubPersonAttributeDao,这在LDAP属性的练习前是足够的(稍 后会讲到)。 所以,现在我们已经在CAS中配置了基本的LDAP认证,你能够重启CAS,启动JBCP Pets(如果它没有在 运行的话)并认证任何在第九章中使用的LDAP用户(用户名ldapguest,密码password就不错)。但是在返回 应用时,你会看到一个丑陋的403访问拒绝页面,这是因为我们的AuthenticationUserDetailsService在数据库 中没有找到LDAP用户。现在让我们来解决这个问题! 从CAS assertion中获取UserDetails 当首次建立CAS与Spring Security集成时,我们配置过UserDetailsByNameServiceWrapper,它会将 CAS提供的用户名转换成UserDetails,这是提过使用我们引用的UserDetailsService对象(我们的例子中,就 是JdbcDaoImpl)。现在CAS引用了LDAP服务器,我们可以使用LdapUserDetailsService就像我们在第九章 结束时讨论的那样,功能就会好用了。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 286 / 350 页 但是在这里,我们体验Spring Security CAS集成的另外一种能力,即从CAS assertion中填充 UserDetails。这只需简单的将AuthenticationUserDetailsService实现切换至 o.s.s.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService就可以了,它的工作就 是读取CAS assertion、寻找特定的属性并将属性值与用户的GrantedAuthority进行匹配。假设assertion返回 了一个名为role的属性。我们只需要在dogstore-base.xml中简单配置一个新的 authenticationUserDetailsService bean: role 接下来,要添加一些小的调试功能来允许我们查询assertion中的内容。 检查CAS assertion 为了辅助查询CAS返回给JBCP Pets应用的信息,我们修改AccountController以展现CAS登录用户的信 息以及CAS为我们提供的用户信息。首先,我们添加一个简单的URL处理方法到AccountController中: @RequestMapping(value="/account/viewCasUserProfile.do",method=RequestMethod.GET) public void showViewCasUserProfilePage(ModelMap model) { final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); model.addAttribute("auth", auth); if(auth instanceof CasAuthenticationToken) { model.addAttribute("isCasAuthentication", Boolean.TRUE); } } 接下来,我们要添加一个JSP,它会展现CasAuthenticationToken的一些信息,这个对象代表CAS认证过的用 户。它会放在WEB-INF/views/account/viewCasUserProfile.jsp。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 287 / 350 页

    View Profile

    Some information about you, from CAS:

    • Auth: ${auth}
    • Username: ${auth.principal}
    • Credentials: ${auth.credentials}
    • Assertion: ${auth.assertion}
    • Assertion Attributes: ${attr.key}:${attr.value}
    • Assertion Attribute Principal: ${auth.assertion.principal}
    • Assertion Principal Attributes: ${attr.key}:${attr.value}
    在账号首页添加一个简单的链接,在WEB-INF/views/account/home.jsp中:

    Welcome to Your Account

    • View CAS User Profile
    • 最后,在assertion属性认证好用之前,我们需要(临时的)将这个页面进行授权检查。(Finally, we'll have to (temporarily) disable authorization checks for this page, until we get assertion attribute-based authorization http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 288 / 350 页 working. )要做到这样只需要简单调整dogstore-security.xml,所以任何具有GrantedAuthority的登录用户都能 访问这个页面以及“My Account”页面: 在完成这些细小的UI更新后,重启JBCP Pets应用,并试图访问这个页面。你会发现在页面提供了很多 assertion包含的信息。 匹配LDAP属性到CAS属性 最后一个揭开的谜底是需要我们匹配LDAP属性到CAS assertion中(包括我们希望在GrantedAuthority 中包含的role属性)。 我们将会在CAS deployerConfigContext.xml中添加一点其它的配置。这些新的配置将会指导CAS怎样 从CAS Principal对象到CAS IPersonAttributes对象匹配属性,而后者将会最终序列化为ticket校验的一部分。 这个bean的配置应该替代同名的bean即attributeRepository。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 289 / 350 页 这个功能可能会很令人迷惑——本质上,这个类的目的将Principal与后端的LDAP目录进行匹配(这就是 queryAttributeMapping属性,将Principal的username域与LDAP查询的uid属性相匹配)。提供的baseDN 属性要使用LDAP进行查询(uid=ldapguest) ,并且属性要从匹配的条目进行读取。 匹配到Principal属性使 用resultAttributeMapping属性中的键值对——我们将LDAP的cn和sn属性匹配到有意义的名字,而 description属性匹配到role属性,而这个role属性就是 GrantedAuthorityFromAssertionAttributesUserDetailsService要进行查找的。 造成这样的复杂性很大程度上是因为这个功能的一部分被另外一个项目进行了封装即Person Directory(http://www.ja-sig.org/wiki/display/PD/Home),它的目的是从多个源收集关于某个人的信息并 集成到单个视图上。Person Directory的设计并没有直接关联到CAS服务器上,所以能够重用在其它应用中。 这种设计选择的不足之处在于它会使得CAS集成的配置比预想的更复杂。 【一些CAS中已有的LDAP属性。我们可能愿意建立与第九章Spring Security LDAP相同类型的LDAP查询,即 能够匹配Principal到一个完整的LDAP标识名,而后用这个DN来查询组信息(使用基本的uniqueMember属性 到一个groupOfUniqueNames条目)。但是,CAS LDAP代码并没有这种灵活性,这就使得更复杂的LDAP匹 配需要扩展CAS中的基本类。】 还是感到迷惑?我们承认这对我们来说也有些迷惑,因为在LDAP数据和CAS数据之间的来回交互中有不 同的方式。CAS的邮件列表和社区wiki(http://www.ja-sig.org/wiki/display/CASUM/Home)是了解别人经 验和与有知识的读者询问问题的好地方。 最后,返回CAS assertion中的属性 遗憾的是,CAS 2.0协议并没有明确定义返回数据的标准格式。在CAS的JIRA缺陷跟踪系统中,有很多尝 试实现这个功能,但是被拒绝了,因为CAS2.0是稳定的所以在最近并不会修改它。 也就是说,很多用户需要扩展CAS的响应来包含属性。最终,CAS到客户应用的ticket校验响应是通过一 个JSP渲染的,所以很容易修改你的CAS安装来包含一个响应,这个响应符合Cas20ServiceTicketValidator的 属性解析。在你的CAS部署中,编辑WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp,并 添加以下内容: http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 290 / 350 页 ${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)} ${fn:escapeXml(attr.value)} 这会产生如下的CAS响应格式: ldapguest LDAP Guest ROLE_USER Guest 当这些修改完成并重启CAS和JBCP Pets,你应该能够用ldapguest登录并访问使用ROLE_USER保护的区域。 另外,你应该看一下“View CAS Profile”页面,它展现了CAS assertion返回的属性。 【注意——我们可以看到LdapPersonAttributeDao.resultAttributeMapping提供的属性名被直接用到CAS响 应的XML中。这意味着它们不能是任意的,必须符合XML元素命名规则(例如,不能包含空格)。如果你想修 改这种行为,你可能需要比这里更复杂的JSP代码。】 干的漂亮——在这两个复杂的产品间有很多的配置工作。休息一下喝杯咖啡吧! 一些CAS开发人员选择的另一种方式是扩展Cas20ServiceTicketValidator(在Spring Security这一端) 来对CAS返回的响应进行自定义的处理。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 291 / 350 页 使用SAML 1.1进行替代的票据认证 SAML是一个标准的、跨平台的协议,它通过结构化的XML assertion进行身份校验。SAML被很多的产 品所支持,包括CAS(实际上,我们将会在稍后的章节中看到Spring Security里面对SAML的支持)。 SAML的安全assertion XML语法解决了我们前面讲到的CAS响应协议传递属性的问题。令人高兴的是, 切换CAS ticket校验和SAML ticket校验只需要修改dogstore-base.xml中的TicketValidator实现即可。添加一 个bean如下: 然后,替换CasAuthenticationProvider使用这个TicketValidator: 重启JBCP Pets应用就会使用SAML响应进行ticket校验了。注意的是,CAS ticket响应并没有被Spring Security进行日志记录,所以你要么对JA-SIG CAS客户端类启用日志(在log4j中对org.jasig启用日志)要么使 用调试查看响应的不同。 总之,建议使用SAML ticket校验而不是CAS 2.0 ticket校验,因为前者提供了更多的防治重造的功能, 包括时间戳校验并以标准的方式解决了属性问题。 属性查询的用处 要记住的是CAS为我们的应用提供了一层抽象,移除了我们引用对用户存储的直接访问,作为替代所有这 样的访问通过CAS作为代理来进行。 这个功能很强大!这意味着我们的应用不再关心用户在什么类型的存储中,也不用关心怎样访问它们的细 节——只需与CAS确认一个用户有权限访问应用。对于系统管理员来说,这意味着,如果LDAP改名、转移位置 或者修改的话,他们只需要在一个位置进行重新配置——CAS。通过CAS进行集中访问使得组织中安全架构更 高级别的灵活性和适应性成为可能。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 292 / 350 页 扩展这个话题到从CAS获取属性的用处——现在,所有通过CAS认证的应用对用户都有了相同的视角(译 者注:即获取的信息是一致的),从而能够在任何使用CAS的环境下以一致的格式显示信息。 注意的是,一旦认证完成,Spring Security CAS不会再次查询CAS除非用户需要重新认证。这意味着, 存储在应用本地用户Authentication对象中的属性和其它用户信息可能会失效且可能与CAS服务器不一致。注 意要设置session的超时时间以避免这种潜在的问题。 其它的CAS功能 除了通过Spring Security CAS包装暴露出来的功能,CAS还提供了高级配置功能。其中一些如下: l 对访问多个使用CAS安全的应用提供了透明的单点登录功能,这要在一个可配置(在CAS服务器端)的时间 内。通过在TicketValidator中将renew属性设置为true,应用可以强制用户到CAS进行认证——你可能希望在 自定义代码中有条件的设置这个属性,如当用户试图访问应用中高安全性的区域时; l 为不能直接访问CAS的二级应用提供了ticket代理功能。当从CAS请求一个ticket时,web应用可能通过二级代 理应用来请求授权ticket。关于这个的更多功能可以阅读CAS网站(http://www.jasig.org/cas/proxy- authentication); l 协调拥有活跃CAS session的多个参与应用间的单点登出(这不是通过Spring Security直接支持的,而是CAS 客户端通过联合使用HttpSessionListener和servlet过滤器)。 我们建议你去探索CAS客户端和服务端的所有功能,并在JA-SIG社区论坛中向大家提问题。 小结 在本章中,我们学习了中心认证服务(CAS)单点登录门户,以及它怎样与Spring Security集成,在本章 中,我们学到了如下内容: CAS架构以及在使用CAS的环境中各个参与角色的通信; 使用CAS的应用给开发人员和系统管理员带来的好处; 配置JBCP Pets与基本的CAS安装交互; 更新CAS与LDAP交互并实现LDAP与使用CAS的应用共享数据; 使用修改过的CAS 2.0协议和工业标准的SAML协议实现与CAS的属性交换。 我们希望本章的内容是关于单点登录的一个有趣开端。在市场上还有很多其它的单点登录系统,它们中大 多数是商用的,但是CAS在开源SSO领域无疑是领导者,并且是在任何组织中构建单点登录的绝佳平台。 http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 293 / 350 页 在下一章中,我们将会转回标准认证,但是这次我们将会移除用户名和密码而是依赖一种新的认证机制。 有趣吗?那就开始吧! http://lengyun3566.iteye.com 1.43 《Spring Security3》第十章(CAS)第二部分翻译(CAS高级配置) 第 294 / 350 页 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 发表时间: 2012-02-13 关键字: Spring Security, 安全, 翻译 第十一章 客户端证书认证(Client Certificate Authentication) 尽管用户名和密码认证特别常见,就像我们在第一章:一个不安全应用的剖析和第二章:Spring Security起步 所讨论的那样,form认证的存在允许用户提供各种类型的凭证。Spring Security也为这种需求提供了支持,在 本章中我们将不再讨论基于form的认证并探索使用可信任的客户端证书。 在本章的内容中将会包括: l 学习在客户端证书认证中,用户浏览器和服务器之间是如何交互的; l 配置Spring Security使用客户端证书认证用户; l 理解Spring Security中客户端证书认证的架构; l 探索客户端证书认证的高级配置选项; l 了解客户端证书的优劣以及在处理过程中的常见问题。 客户端证书认证是怎样工作的 客户端证书认证需要服务器向浏览器发送请求信息而浏览器进行响应,这样来建立用户(和他们的浏览 器)与服务端应用的可信任认证关系。这个可信任的关系是通过交换可信的以及可认证的凭证建立起来的,而 这也就是所谓的证书(certificates)。 与前面见到的不同,在客户端证书认证中,servlet容器或应用服务器本身要负责在浏览器和服务器之间建 立可信的关系,这是通过请求证书、评估并接受其合法性。 客户端证书认证也被成为共同认证(mutual authentication),是安全socket层(Secure Sockets Layer,SSL)协议及其后续协议传输层安全协议(Transport Layer Security,TLS)的一部分。因为共同认证 http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 295 / 350 页 是SSL和TLS协议的一部分,它需要HTTPS连接(通过SSL或TLS进行安全防护)进行客户端证书认证。关于 Spring Security中SSL/TLS的更多细节,请参考我们在第四章:凭证安全存储中对SSL/TLS的讨论和实现。为了 实现客户端证书认证需要在Tomcat(或者你正在使用要运行例子的应用服务器)中建立SSL/TLS。就像第四章 那样,在本章的后续内容中我们将会用SSL代替SSL/TLS。 下面的序列图展现了客户端浏览器与web服务器之间的交互,在这个交互中进行了SSL连接的建立并检验 用来做共同认证的客户端证书的可信性。 我们可以看到两个证书的交换(服务端证书和客户端证书)提供了两端参与者的认证并确信他们之间的继续会 话是安全的。清晰起见,我们省略了一些SSL握手和证书信任检查的细节,但是,我们建议你去阅读SSL和TLS 的协议和证书,关于这些课题有很多好的参考手册。RFC 5246, The Transport Layer Security (TLS) Protocol V1.2 (http://tools.ietf.org/html/rfc5246),是学习客户端证书的很好入门资料。如果你想了解更细节的东西, 那Eric Rescorla的SSL and TLS: Designing and Building Secure Systems是关于协议和实现的极佳细节材 料。 http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 296 / 350 页 基于客户端证书认证的另一个名称是X.509认证。术语X.509来源于X.509标准,最初由ITU-T组织发布, 用于在X.500标准下的目录(LDAP的起源,你可能会想起第九章:LDAP目录服务)下使用。后来,这个标准修 改后用在了安全的互联网通信上。 我们提及这些是因为在Spring Security中与这个主题有关的类都与X.509相关。要记住的是X.509并没有 定义共同认证协议本身,但是定义了证书的格式和结构并包含了可信任证书的权限。 建立客户端证书认证的设施 对于个体的开发人员来说不好的消息是,为了体验客户端证书认证需要一些并不简单的配置并事先建立起 与Spring Security的简单集成。鉴于这些建立步骤对于初学者来说可能会遇到很多问题,所以我们觉得给读者 进行介绍是很重要的。 我们假设你使用本地的、自认证的服务器证书以及自认证的客户端证书,Apache Tomcat也是如此。这 在大多数的开发环境中很常见,但是,你可能会访问合法的服务器证书、一个证书中心(CA)或者其它的应用 服务器。如果是这样的话,你需要使用它们的安装指令并以类似的方式配置你的环境。请参考第四章的SSL建立 指令来帮助配置Tomcat和Spring Security在单独的环境中使用SSL。 理解公钥设施的目的 本章会关注为了教学而建立完整的开发环境,但是在大多数的情况下,你需要将Spring Security集成到已 有的客户端校验安全环境中,那里会有很多的设施(通常会是硬件和软件的组合)来提供诸如证书授权和管 理、用户服务以及撤销等功能。这种类型的环境会有公钥设施——联合使用硬件、软件和安全策略以实现高度 安全、以认证为驱动力的网络系统。 为了进行web应用的认证,环境中的证书或硬件设备能用来进行安全、不可抵赖的电子邮件(使用 S/MIME),网络认证,甚至基于物理元件的访问(使用基于PKCS 11的硬件设施)。(In addition to being used for web application authentication, certificates or hardware devices in these environments can be used for secure, non-repudiated email (using S/MIME), network authentication, and even physical building access (using PKCS 11-based hardware devices)) 因为管理这样的环境会比较困难(需要IT和工序都能够很好完成),所以这无疑需要专业的安全人员来操 作这样的环境。 创建客户端证书密钥对 创建自签名的客户端证书与创建自签名的服务端证书是一样的,要通过keytool命令来创建密钥对。客户 端密钥对的不同在于它需要web浏览器能够访问key store并且需要将客户端的公钥加到服务器的trust store中 (我们稍后将会介绍它是什么)。 创建客户端的密钥对如下: http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 297 / 350 页 keytool -genkeypair -alias jbcpclient -keyalg RSA -validity 365 -keystore jbcppets_clientauth.p12 -storetype PKCS12 当提示为客户端证书输入名字(common name或DN即拥有者DN的一部分)时,确保第一个提示的输 入要与Spring Security JDBC存储中已有的用户相匹配,如admin: What is your first and last name? [Unknown]: admin ... etc Is CN=admin, OU=JBCP Pets, O=JBCP Pets, L=Anywhere, ST=NH, C=US correct? [no]: yes 当我们配置Spring Security访问证书授权用户信息时,将会看到为什么这(个设置)很重要。在Tomcat 中进行证书授权前,我们还有最后的一步。 配置Tomcat的trust store 回忆一下密钥对的定义包含一个私钥和公钥。与SSL证书校验和保护服务器通信类似,校验客户端证书需 要检查创建它的可信任者。 因为我们使用keytool命令自己创建的自签名客户端证书,所以java虚拟机不会像信任认证中心的那样信 任它。 这样我们就需要强制要求Tomcat将这个证书视为可信任的证书。我们要达到这个目的需要将密钥对的公 钥导出并将其添加到Tomcat的trust store中。 首先,我们要将公钥导出到一个标准的证书文件中,并将其命名为jbcppets_clientauth.cer,如下: http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 298 / 350 页 keytool -exportcert -alias jbcpclient -keystore jbcppets_clientauth.p12 - storetype PKCS12 -storepass password -file jbcppets_clientauth.cer 接下来,我们要将证书导入到trust store中(这将会创建一个trust store,但是在典型的部署环境中,你 可能会在trust store中拥有其它的证书): keytool -importcert -alias jbcpclient -keystore tomcat.truststore -file jbcppets_clientauth.cer 这将创建名为tomcat.truststore的trust store并提示你输入密码。你会看到一些证书相关的信息并最后被 询问是否信任这个证书: Owner: CN=admin, OU=JBCP Pets, O=JBCP Pets, L=Anywhere, ST=NH, C=US Issuer: CN=admin, OU=JBCP Pets, O=JBCP Pets, L=Anywhere, ST=NH, C=US Serial number: 4b3fb3d9 Valid from: Sat Jan 02 16:00:09 EST 2010 until: Sun Jan 02 16:00:09 EST 2011 Certificate fingerprints: MD5: 02:69:16:3B:D7:C2:74:9E:F7:FD:18:C9:C5:E4:C8:94 SHA1: 65:57:94:6D:D2:83:7E:51:19:CF:58:94:ED:43:11:F6:AC:D0:FB:EC Signature algorithm name: SHA1withRSA Version: 3 Trust this certificate? [no]: yes 复制这个新的tomcat.truststore文件到你使用Tomcat服务器的conf目录下。在最后的成功前还要一个最 后的配置! 【Key Store和Trust Store的区别是什么?Java Secure Socket Extension (JSSE)对key store进行了如下的定 义:私钥和对应公钥的存储机制。key store(包含密钥对)被用来加密或解密安全信息等功能。trust store原 http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 299 / 350 页 本用来只存储公钥以实现校验身份时的可信任通信(如我们在证书校验时看到trust store是怎样被使用的)。 但是,在很多常见的管理场景中,key store和trust store被结合到一个文件中(在Tomcat中,这可以通过使用 Connector的keystoreFile和truststoreFile属性来完成配置)。这些文件本身的格式完全相同(实际上,每个 文件可以是任意的JSSE支持的keystore格式,包括Java Key Store / JKS, PKCS 12等)。】 最后,我们需要为Tomcat指明trust store并启用客户端证书认证。这需要在Tomcat 的server.xml文件中 为SSL Connector添加三个额外的属性: 这是建立SSL时,触发Tomcat请求客户端证书的所需要的配置。到这里,我们可以重新启动Tomcat了。 【还有一个其它的方式来配置Tomcat使用客户端证书认证——我们将会在稍后启用。现在,我们需要在第一次 连接Tomcat服务器的时候就要使用客户端证书。这会更容易的判断出设置是否正确。】 最后一步是将客户端证书导入到客户端的浏览器中。 导入证书到浏览器中 根据你使用浏览器的不同,导入证书的过程会有所区别。我们将会提供Firefox和IE的指南,但是如果你使 用其它的浏览器,请查询其帮助文档或你喜欢的搜索引擎寻找帮助。 使用Firefox 按照以下的步骤来导入包含客户端证书密钥对的key 存储: 1. 打开“工具”菜单; 2. 点击“选项”菜单项; 3. 点击“高级”按钮/图标; http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 300 / 350 页 4. 点击“加密”tab标签; 5. 点击“查看证书”按钮。将会打开“证书管理器”; 6. 点击“您的证书”tab标签; 7. 点击“导入”按钮; 8. 找到你存储jbcppets_clientauth.p12文件的位置并将其选中; 9. 你需要输入创建这个文件时的密码。 客户端证书就导入了,你可以在列表中看见它。 使用IE IE与Windows操作系统紧密集成,所以它导入key存储会比较容易: 1. 在Windows Explorer中双击jbcppets_clientauth.p12文件。将会启动“证书导入向导”; 2. 点击“下一步”接受默认选项直到提示你输入证书密码; 3. 输入证书密码,并点击“下一步”; 4. 接受“自动选择证书存储选项”并点击“下一步”; 5. 点击“完成”。 为了校验证书正确安装,你需要进行一下的步骤: 1. 在IE中打开“工具”菜单; 2. 点击“internet选项”菜单项; 3. 点击“内容”tab标签; 4. 点击“证书”按钮; 5. 点击“个人”tab标签(如果它没有被选中的话)。你会看到证书列在此处。 测试 http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 301 / 350 页 现在你可以使用客户端证书连接到JBCP Pets。如果所有的都被成功设置,在访问站点时会提示要求证书 ——在Firefox下,证书展现如下: 但是,当你试图访问站点中受保护的区域,如“My Account”区域,你将会被重定向到登录页面。这是因为 我们没有配置Spring Security辨别证书中的信息——到这里,客户端和服务器的所有交互停止在Tomcat服务 器本身那里。 客户端证书认证的问题解决 不幸的是,如果我们说第一次就能将客户端认证配置正确且没有任何错误出现是很容易得,那我们是在骗 你。事实上,尽管这是一个很伟大和强大的安全设施,但是浏览器和web服务器的文档都很匮乏,出错信息在 好的情况下会比较令人费解而在不好情况下根本就是误导性的。 记住到此时我们还有涉及到Spring Security,所以调试器可能帮不上什么帮(除非你手头有Tomcat的源 码)。一些常见的错误和要检查的事情如下: l 当你访问站点时,没有提示你要求证书。有很多情况可能会导致如此,而这也是最令人迷惑的问题。以下是要 检查的事情: http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 302 / 350 页 u 确保你使用的客户端浏览器正确安装了证书。如果你访问上面的网站被拒绝,有时你需要重启整个浏览器 (关闭所以的窗口); u 确保你访问的是服务器的SSL端口(在开发环境下一般为8443),并在URL中选择了https协议。客户端证 书对不安全的浏览器链接不会提供。确保浏览器也信任服务器的SSL证书,即使你强制它信任一个自签名的证 书; u 确保你添加了clientAuth指令到Tomcat配置中(或者对等的任意你使用的应用服务器); u 如果以上的所有都失败的话,使用网络分析器或包探测器,如Wireshark (http://www.wireshark.org/) 或 Fiddler2 (http://www.fiddler2.com/) 来了解线路上的交流和SSL密钥交换。 l 如果你使用的是自签名的客户端证书,确保公钥被导入到服务器端的trust store中。如果你使用的是CA签发 的证书,确保JVM信任CA或者CA的证书被导入到了服务器trust store中。 l 特别在IE下,根本不会报告客户端证书失败的细节(只会报告一个“页面不能展现”的错误)。使用Firefox来 查看是否会有客户端证书相关的错误。 http://lengyun3566.iteye.com 1.44 《Spring Security3》第十一章(客户端证书认证)第一部分翻译 第 303 / 350 页 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 发表时间: 2012-02-13 关键字: Spring Security, 安全, 翻译 在Spring Security中配置客户端证书认证 不同于我们到目前为止所使用的认证机制,使用客户端证书认证会使得用户的请求已经被服务器预先认证 (pre-authenticated)了。因为服务器(Tomcat)已经确定用户提供了合法且可信的证书,所以Spring Security只需信任这个assertion的合法性。 安全登录过程的另一个组件还缺失,也就是对认证过的用户进行授权。这就是我们要配置Spring Security 的地方——我们必须添加一个组件到Spring Security中,它能够辨认出用户HTTP session(Tomcat填充进去 的)中的证书认证信息,并将提供的凭证信息与Spring Security的UserDetailsService进行校验。与所有的 UserDetailsService一样,这会确定对于证书中声明的用户,Spring Security是否了解(译者注:即Spring Security对应的存储中是否有该用户的信息),然后像其它登录的用户那样分配GrantedAuthority。 使用security命名空间配置客户端证书认证 由于LDAP和OpenID的配置的复杂性,配置客户端证书认证会稍好一些。如果你使用的是security命名空 间风格的配置,添加客户端证书认证只需一行配置变化,添加到声明中: 如果你重启应用,你会再次被提示要求客户端证书,但是这次你能够访问需要授权的内容了。你能够从日志中 看到(如果你启用的话)你已经以admin用户登录了!干得漂亮! Spring Security怎样使用证书信息 正如前面讨论的那样,Spring Security在证书交换中的相关内容是从提供的证书中提取信息并将用户的凭 证与用户服务进行匹配。我们在使用声明中没有看到的是使得这一切发生的魔力所在。回忆我们建立客 户端证书时,一个类似于LDAP DN的唯一标识名(distinguished name ,DN)会与这个证书关联: http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 304 / 350 页 Owner: CN=admin, OU=JBCP Pets, O=JBCP Pets, L=Anywhere, ST=NH, C=US Spring Security使用DN中的信息来确定安全实体的实际用户名并在UserDetailsService中进行查找。特 别指出的是,它允许指明一个正则表达式,它会用来匹配证书建立时的DN并用DN的这一部分作为安全实体的 用户名。默认配置的明确写法如下: 我们可以看到,这个正则表达式将会匹配admin作为用户名。这个正则表达式必须包含一个匹配值,但是它能 够被配置成支持用户名和DN以满足应用的需要——如,如果你们组织的证书包含email或userid域,正则表达 式可以修改成使用这些值作为已认证安全实体的名字。 Spring Securit证书认证怎样实现 让我们看一下检查和评估客户端证书以及将其转换成Spring Security认证session的参与者,如下图: http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 305 / 350 页 我们可以看到o.s.s.web.authentication.preauth.x509. X509AuthenticationFilter负责处理要求未认证用户提 供客户端证书的请求。如果这个请求包含了合法的证书,它将会使用 o.s.s.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor抽取出安全实体,如前文所描述, 这里会使用匹配证书自己DN的正则表达式。 【注意,尽管在图中描述了对未认证用户的证书检查,但是还有一个检查会进行即使用证书认证的用户是否为 已经认证过的用户。这会使用新提供的凭证进行一个新的认证。这个的目的很明确——任何时间一个用户提供 了新的凭证,应用必须能够意识到,并作出适当的响应以确保用户能够正确访问。】 一旦证书被接受(或拒绝/忽略),就像其它的认证机制那样,会构建一个Authentication token并传递 给AuthenticationManager进行认证。我们现在可以简单看一下 o.s.s.web.authentication.preauth.PreAuthenticatedAuthenticationProvider对认证token的处理: http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 306 / 350 页 如果你读过我们在第十章:使用CAS进行单点登录讲到的CAS认证,你会意识到客户端证书认证请求和CAS请 求的相似之处——这是有意的设计,事实上相似的设计影响到Spring Security支持的其它的预先认证机制(较 少使用),包括Java EE角色匹配和Site Minder风格的认证。如果你理解了客户端证书认证的流程,理解其它 的认证流程就容易多了。 其它未解决的问题 我们要解决的一个问题就是处理对认证的拒绝。在第六章:高级配置和扩展中,我们曾经学习过的功能 AuthenticationEntryPoint(我们在CAS章节曾重温过这个话题)。在典型的form登录场景下,如果用户试图 访问保护的资源却没有登录时,LoginUrlAuthenticationEntryPoint用来将用户重定向到登录页面。 与之相反,在典型的客户端证书认证环境下,并不支持替代的授权方法(要记住的是,Tomcat校验证书 在Spring Security的form登录前面)。所以,并不会再存在默认重定向到form登录页的行为。相反,我们将会 修改这个entry point,使用o.s.s.web.authentication.Http403ForbiddenEntryPoint简单返回一个HTTP 403 禁止访问的信息。我们将会在dogstore-base.xml文件中配置这个bean,其它的Spring bean也位于此处,如 下: http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 307 / 350 页 接下来,通过在声明上设置一个简单的属性就会使得新entry point马上生效: 如果一个用户试图访问受保护的资源,而不能提供合法证书的话,他们将会看到如下的页面 不像我们在第六章看到的配置AccessDeniedHandler那样,Http403ForbiddenEntryPoint不能将用户重定向 到一个友好页面或Spring管理的URL。作为替代,这样的配置应该在web应用部署描述符的声明 中配置,这是Java EE servlet规范声明的,或者实现Http403ForbiddenEntryPoint的子类以改写这个行为。 在常用的客户端证书认证中,其它的配置或应用流程要进行的调整如下: l 同时移除基于form的登录页面; l 移除所有的“Log out”链接(没有理由退出了,因为浏览器会始终提供用户的证书); l 移除用户重命名和修改密码功能; l 移除用户注册功能(除非你能将其与发放新证书关联)。 支持双模认证(Dual-Mode authentication) http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 308 / 350 页 在一些环境下,可能会同时支持基于证书和基于form的认证。如果你的环境就是如此,也可以(很简单 的)通过Spring Security3来进行支持。我们需要使用默认的AuthenticationEntryPoint(重定向到form登录 页)与用户交互并允许用户在没有提供客户端证书的情况下使用标准的form进行登录。 如果你选择这种方式配置你的应用,你需要调整Tomcat的SSL设置(对于你的应用服务器也要适当调 整)。只需将clientAuth属性的值设为want而不是true: 我们也需要移除前面练习中添加的entry-point-ref,这样如果浏览器在初始查询时不能提供一个合法的证书将 会启用标准的基于form的认证流程。 尽管这很便利,但还是要记住几件关于双模认证(基于form和基于证书)的事情。 【一旦证书校验失败,大多数的浏览器不会提示用户再次要求证书,所以确保你的用户注意的是他们可能 需要重新进入浏览器以再次提供证书。】 要注意的是使用证书认证用户的时候并不需要密码;但是,如果你还继续使用JDBC UserDetailsService 来支持基于form的认证,可能会需要你使用UserDetailsService提供一些关于用户的信息给 PreAuthenticatedAuthenticationProvider。这会有潜在的安全风险,因为你只想让其进行证书认证的用户可 能会有使用form登录认证的潜在问题。有几个方法来解决这个问题,描述如下: l 确保证书认证的用户有一个合适的强密码在数据库中; l 考虑自定义你的JDBC用户存储以明确可以使用form登录的用户。这可以通过在保存用户信息的表上添加一个 新的域,并细微调整JdbcDaoImpl使用的SQL查询; l 为证书认证的用户配置一个单独的用户存储,与允许基于form登录的用户完全隔离。 双模认证可以是对你站点的一个很大增强功能,并能够有效部署和保证安全,这当然要以你记住用户能够 允许访问的条件。 http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 309 / 350 页 使用Spring bean配置客户端证书认证 在前面,我们曾经讲过客户端证书认证相关类的流程。所以,对于只使用基于bean的配置就会更容易 了,它们都会在dogstore-explicit-base.xml文件中。我们将会添加如下的bean定义,这就对应我们迄今所讨 论的所有bean: 我们还需要添加这个过滤器到我们的过滤器链中(比移除登录和退出相关的过滤器): http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 310 / 350 页 最后,我们需要添加AuthenticationProvider的实现到ProviderManager中,并移除原来存在的所有其它类: 到此为止,我们基于bean的配置就准备好了。如果你想实验它,记住要切换web.xml中的(配置文件)引用来 使用只基于bean的配置机制。 基于bean配置的其它功能 Spring基于bean的配置通过暴露bean属性为我们提供了一些其它的功能,而这些通过security命名空间 风格的配置是没有暴露的。 X509AuthenticationFilter的其它属性如下: 属性 描述 默认值 continueFilterChainOnUnsuccessfulAuthentication 如果为false,一个失败 的认证将会抛出异常而 不是允许请求继续。这 通常会在需要合法证书 才能访问的安全站点中 设置。如果为true,即 使认证失败,过滤器链 将会继续。 true checkForPrincipalChanges 如果为true,过滤器将 会检查当前认证的用户 名与客户端证书提供的 false http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 311 / 350 页 用户名是否有所不同。 如果这样的话,对于新 证书的将会进行认证并 且HTTP session将会 失效(可选的,参照下 一属性)。如果为false 的话,一旦用户认证 过,将会一直处于合法 状态即便他修改了凭 证。 invalidateSessionOn PrincipalChange 如果true并且请求中的 安全实体发生变化,用 户的HTTP session在 重新认证前将会失效。 如果为false,session 将会保持——注意这可 能会引入安全风险。 true PreAuthenticatedAuthenticationProvider为我们提供了两个有意思的属性,如下: 属性 描述 默认值 preAuthenticated UserDetailsService 用从证书中获取的用户名构建完整的 UserDetails对象 None throwExceptionWhenTokenRejected 如果为true,当token不能正确构建时 (证书中不包含用户名或没有证书)会 抛出一个BadCredentialsException错 误。在证书要明确要使用的环境中,一 般设置为true。 false 除了这些属性,还有很多的机会来实现接口或扩展证书认证相关的类来对你的应用进行更进一步的自定义。 http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 312 / 350 页 实现客户端证书认证要考虑的事情 客户端证书认证,尽管非常安全,但是并不是适合所有人,也并不适合于所有的环境。 益处: l 证书对双方(客户端和服务器)都建立起了相互信任并确保各自就是其所宣称的(参与者)(译者注:即能够 确定提供证书的是谁); l 基于证书的认证,如果能够很好地实现,相对于其它的form认证很难被模仿或窜改; l 如果使用良好支持的浏览器并且正确设置,通过透明登录到所有证书安全的应用,客户端证书认证能够有效地 作为单点登录的手段。 弊端: 这种方式(使用证书)一般会要求所有的用户都要拥有证书。这会导致用户培训负担以及管理负担。 对于大量使用证书认证的组织必须要有足够的服务和支持,以进行证书管理、过期跟踪以及处理用户求助; 使用证书一般是一个全有或全无的事情,意味着一般不会对没有认证的用户提供混合模式的认证,这主要是 因为web服务器的复杂配置以及应用的较差支持; 你的所有用户(包含移到设备)并不一定很好的支持证书; 正确配置证书认证的所有设施可能会需要高级的IT知识。 正如我们所看到的,客户端证书认证有利有弊。当它正确实现时,它对于所有的用户会是一种很便利的访问 模式,并具有很吸引人的安全性和不可抵赖属性(non-repudiation)。你需要根据特定的情况来决定这种类型 的认证是否适合。 小结 本章中,我们了解了基于客户端证书认证的架构、流程以及Spring Security提供的支持。我们学到了如下 的内容: l 了解客户端证书(相互的)认证的概念和整体流程; l 学习配置Apache Tomcat支持自签名SSL和客户端证书的重要步骤; l 配置Spring Security能够理解客户端提供的证书认证凭证; l 理解Spring Security与证书认证相关的类; http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 313 / 350 页 l 学习怎样配置Spring bean风格的客户端证书环境; l 权衡这种方式认证的利弊。 对于不熟悉客户端证书的开发者来说,可能会被这种环境的复杂性所困扰。我们希望本章的内容能够使得 这个复杂的话题更容易理解和实现。 http://lengyun3566.iteye.com 1.45 《Spring Security3》第十一章(客户端证书认证)第二部分翻译 第 314 / 350 页 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 发表时间: 2012-02-13 关键字: Spring Security, 安全, 翻译 第十二章 Spring Security扩展 在本章中,我们将会探索一个Spring Security扩展项目的功能——这是很令人兴奋的功能即将Windows Active Directory认证(或其它支持Kerberos的设施)与Spring Security集成以为你的Intranet用户提供完善的 单点登录体验。 在本章中,我们将会: l 学习Kerberos认证协议及其基于web的凭证; l 理解使用Kerberos的web应用怎样适用于基于Kerberos的安全设施; l 配置JBCP Pets应用通过使用Active Directory认证存储为Windows用户提供单点登录认证功能; l 探索使用Active Directory作为LDAP UserDetailsService从而在唯一的位置存储用户数据。 Spring Security扩展 Spring Security扩展(Spring Security Extensions)项目(可以通过以下地址访问: http://static.springsource.org/spring-security/site/extensions.html )作为Spring Security扩展功能的孵 化器,基于Spring Security的核心框架构建。尽管这个项目相对很新,但是它已经有三个令人兴奋的模块包括 Kerberos authentication(Spring Security 2中NTLM的继续),Security Assertion Markup Language 2.0 认证以及Portlet认证。 在本章中,我们将会介绍Kerberos SPNEGO认证的基本配置和使用。在写这些内容的时候,Spring Security扩展都还没有官方释放,但是Kerberos项目足够稳定所以你读到这些内容的时候应该不会有明显的配 置变化。通过这些非官方、社区开发的扩展,我们可以将本章的内容视为对Spring Security实验功能的探究。 Kerberos和SPNEGO认证入门 Kerberos是一个相互的认证协议,用来对认证客户端——独立的用户或网络资源——这通过使用名为 KDC(key distribution center)的中心凭证存储。客户端与KDC之间的交互很复杂,在一些互联网标准中有很 好的文档(主要是RFC 4120, The Kerberos Network Authentication Service (V5),可以在以下地址查 阅:http://tools.ietf.org/html/rfc4120)。 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 315 / 350 页 在本章中为了进行讨论,我们将会简化Kerberos设施检查凭证活动的细节。Kerberos认证和Kerberos设施 的主要目的是对安全实体(principal)提供安全可信的认证,而这里的安全实体可以是用户、网络资源或软件 应用。Kerberos的协议本身和软件实现最初是由麻省理工学院(MIT)开发的。鉴于Kerberos有这样一个光辉 的背景,有很多不错的书详细讲解Kerberos的细节。 在Kerberos认证协议之上,开发出了Generic Security Service Application Program Interface (GSS- API),这也是基于RFC标准(RFC 2078, Generic Security Service Application Program Interface, Version 2, http://tools.ietf.org/html/rfc2078)。GSS-API提供了标准的、跨平台、跨语言的认证和安全接口,并包装 了Kerberos(以及其它的认证提供者)。它的开发目标是为安全开发人员提供一致的认证和安全编程接口而不 需要了解特定安全协议的细节。 GSS-API在Java语言中的标准实现在另一个RFC标准中定义(RFC 2853, Generic Security Service API Version 2: Java Bindings, http://tools.ietf.org/html/rfc2853),它在Sun JVM的org.ietf.jgss包中实现。 【在java支持Kerberos和GSS-API中,有一个很重要的事情是API的实现以及API本身是JRE的一部分,不需 要引入任何第三方的库。实际上,你可以查询Sun的站点(http://java.sun.com/javase/6/docs/technotes/ guides/security/jgss/tutorials/index.html)来了解这些Sun JVM功能的更多文档。注意的是,其它的JVM实 现可能不会像Sun JVM那样提供相同的功能。】 将GSS-API认证集成到web浏览器中是通过使用一个名为Simple and Protected GSS-API Negotiation Mechanism (SPNEGO)的信息交换标准做到的。SPNEGO是明确两个GSS-API实现间行为的算法,确定了它们 之间是否可以通过一个通用的安全协议进行通信。就Kerberos SPNEGO而言,我们希望客户端和服务端交互的 通用安全协议是Kerberos。 尽管SPNEGO可以被用来进行支持GSS-API的应用或系统通信,但是对于web应用开发人员和安全设计人员 来说,这个词汇更多是意味着web客户端认证。Microsoft支持的RFC 4559 (SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows, http://tools.ietf.org/html/rfc4559)描述了服务器通 过HTTP协议使用SPNEGO请求来要求GSS-API认证。客户端浏览器就可以响应SPNEGO请求并利用原生的操作 系统支持从终端用户那里收集凭证信息。Microsoft为它的Windows操作系统开发了这个规范以支持两个GSS- API的实现——其专有的NT LAN Manager (NTLM)协议和开放的Kerberos协议。最终,这种从基于Window 的客户端到Kerberos服务端资源的便利单点登录方式使得其他的浏览器也适应了这种基于浏览器的SPNEGO, 包括Mozilla Firefox和Apple Safari。 希望我们没有使你掉进术语汤中(译者注:即被这些术语所迷惑)!让我们现实一些了解这些在基于web认 证的场景下是如何工作的。 下图展现了在SPNEGO Kerberos认证过程中,三个参与者之间的交互: http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 316 / 350 页 在进入Spring Security实现Kerberos的SPNEGO认证之前,理解客户端浏览器和应用服务器之间的两个重要 HTTP交互是很重要的。 l 当需要认证时,应用服务器必须发送一个包含WWW-Authenticate: Negotiate 值的HTTP响应头给客户端。 这会告诉客户端需要使用SPNEGO协议进行认证; l 客户端要确定用户的凭证(可能会使用弹出提示,这取决于浏览器实现和操作系统),并将凭证信息编码到 HTTP Authorize请求头中响应给服务器。 在这里我们更多关注了浏览器和应用服务器的交互,因为这是大多数Spring Security集成所关注的部分。 【尽管SPNEGO成功实现并没有要求,但是我们建议在生产环境中使用SSL,这样SSO凭证能够在web客户 端认证过程中保持安全。】 另一个要考虑的重要设施是Kerberos KDC实现。存在开源的选择,其中最主要的就是MIT(著名的大学) 的Kerberos实现,这也是Kerberos技术的最初贡献者之一,但是,在企业环境中,开发人员最常见的实现是 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 317 / 350 页 Microsoft Active Directory (AD)。Windows服务器的Active Directory可以作为Kerberos KDC的功能, 所以如果一个组织使用了AD,那也就会自动启用了基于Kerberos的认证。 在Spring Security实现Kerberos认证 与我们已经看到过的CAS和客户端证书认证类似,对于使用Kerberos安全的站点Kerberos Single Sign- On (SSO)将会作为唯一支持的认证机制(尽管在本章临近结束的时候我们将会看待一种替代的配置以支持form 登录)。让我们看一下为Spring Security配置支持Kerberos的基本步骤,随后,我们将会介绍Kerberos环境中 其它参与者所需要的配置。 Kerberos Spring Security认证整体流程 正如我们在整体介绍SPNEGO协议设计时所讲的,关键实现在于客户端浏览器与安全应用之间的信息交 换。这就是Spring Security's Kerberos Extension介入的地方——来处理认证凭证的交互。 如同典型的外部认证存储,如我们看到过的CAS(在第十章:使用CAS进行单点登录和第十一章:客户端 证书认证),联合使用一个servlet过滤器和自定义的AuthenticationProvider来进行外部存储的认证并校验返 回响应的可靠性和可验证性。 让我们看一下使用Spring Security进行SPNEGO认证相关的重要类,如下图: http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 318 / 350 页 我们可以看到o.s.s.extensions.kerberos.KerberosServiceAuthenticationProvider主要负责协调校验浏览器 SPNEGO响应的合法性。稍后,我们将会介绍将这些类组织在一起的Spring Bean配置细节。 准备工作 配置任何的Kerberos环境,尤其是Microsoft Active Directory后台,而你又没有Kerberos经验的话会很 耗时和复杂。如果你要配置使用Kerberos的应用到一个环境中去,在开始之前要确保环境被正确配置了,否则 你将花费更多的时间在诊断环境上而不是开发你的应用上。 在本课程中,我们假设你写的应用要到Microsoft Active Directory(AD)环境中进行认证,这(对于大 多数用户)是结合Spring Security和Kerberos的最常见场景。如果你就是这样,当你读到“Kerberos安全实 体”(Kerberos principal),只需将其等同于AD用户。 在开始之前,要确保: http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 319 / 350 页 你已经为web应用本身建立的一个Kerberos安全实体。我们将会使用这个账号进行应用服务器的附加配 置; 连接站点的电脑(也就是web浏览器运行的机器)必须是Kerberos认证域(Kerberos authentication realm)的一部分。对于Windows用户,这意味着这个电脑必须是AD域(AD domain)的一部分; 运行web浏览器的电脑运行web应用的电脑不能是同一台机器。对你来说,虚拟机是很便利的选择。 记住的是Kerberos SSO一般部署在intranet应用中,并运行在AD或Kerberos Realm里,而不会用在面 向公众的web应用中。 例子的假设 在本章的Kerberos例子中,我们假设要建立的环境如下: 配置 值 描述 域名(Domain Name) jbcppets.com 我们公司网络和站点的域名 AD域(AD domain) corp.jbcppets.com Active Directory域——与Kerberos 域名匹配(简介起见缩写为CORP) Web站点安全实体 (Website principal) CORP\website 对应于web站点服务的AD用户 创建keytab文件 你需要创建一个keytab文件,它用来认证要访问KDC的web应用并允许对用户提供的凭证进行认证。 keytab文件是一个Kerberos安全实体私钥的加密备份,在向Kerberos服务器认证安全实体的时候能够代替密 码。 我们的应用使用基于web的SPNEGO Kerberos认证。相关的协议RFC(RFC 4559)规定了keytab必须匹 配安全实体且以HTTP/fully.qualified.web.server.name作为标识符(这必须与规范声明完全一致)。在我们实 例的配置中,我们假设JBCP Pets应用运行在web.jbcppets.com上并在Kerberos域corp.jbcppets.com中,所 以在keytab中的实体名应该是HTTP/web.jbcppets.com@CORP.JBCPPETS.COM。 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 320 / 350 页 因为我们以AD作为Kerberos KDC,所以我们需要将AD用户CORP\website与需要的安全实体进行匹 配。我们可以使用Microsoft的ktpass工具(在AD域控制器中)来完成,如下: ktpass -princ HTTP/web.jbcppets.com@CORP.JBCPPETS.COM -mapuser CORP\website -out website.keytab 你会看到命令的参数与我们以下描述的配置场景所匹配: l HTTP/web.jbcppets.com@CORP.JBCPPETS.COM:Kerberos安全实体的名字(HTTP/ web.jbcppets.com)与域(CORP.JBCPPETS.COM); l CORP\website:与Kerberos安全实体相匹配的域用户名。 注意的是Kerberos域是大小写敏感的,所以要确保在所有环境中大小写一致。便利起见,Kerberos域通常 声明为大写。 这将会在当前目录下创建名为website.keytab的文件。你需要安全的将这个文件传输到运行web应用 的机器上以在Spring Security中配置Kerberos。将其放在JBCP Pets web应用的WEB-INF/classes目录下 ——当我们稍后配置Spring Security Kerberos bean的时候会用到。 【留意这个keytab文件——它包含了到Kerberos域信息,这些信息与预认证凭证相同。不要使用不安全的 文件传输技术来复制这个文件到目标服务器,因为这会使你有被网络监听的风险。如果这个文件是缺乏安全 的,恶意用户可以使用这些凭证登录你的Kerberos域,就像web应用用户那样。】 注意,ktpass包含在Windows 2008 Server和以后的版本中。以前版本的Windows Server可能需要安 装Kerberos支持文件才能使用这些命令行工具。 配置Kerberos相关的Spring bean 鉴于大多数的Kerberos认证发生在Sun JVM里面,所以在Spring Security Kerberos认证中没有太多的配 置(甚至代码)。 首先,我们要配置AuthenticationEntryPoint来负责发送WWW-Authenticate头,它会首先进入客户端 机器并响应Kerberos凭证。在dogstore-base.xml中的bean声明如下: http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 321 / 350 页 接下来,我们要声明解析传入Authorization HTTP请求头并将其按照SPNEGO登录请求进行处理的过滤器: 要记住的是,与我们之前看到的其它SSO实现相同,这个过滤器负责解析SSO头并基于这个头信息来尝试认证 用户。SpnegoAuthenticationProcessingFilter将会基于HTTP Authorization头中的凭证填充一个 o.s.s.extensions.kerberos.KerberosServiceRequestToken对象。 现在,我们需要一个AuthenticationProvider,它要提取被填充的KerberosServiceRequestToken对象 里面的凭证信息并进行校验。与我们看到的CAS认证类似,Kerberos AuthenticationProvider要负责将token 与ticket授予者进行校验。 KerberosServiceAuthenticationProvider需要引用o.s.s.extensions.kerberos.KerberosTicketValidator代理 实现,后者用来校验从认证token所获取的Kerberos ticket。Spring Security Kerberos Extension中提供的唯 一实现是使用Sun JVM's GSS-API来校验keytab的内容并为服务实体执行对Kerberos ticket的校验。 我们可以看到servicePrincipal和keyTabLocation引用了我们在前面Kerberos server中所作的配置。 【keytab文件应该放在哪里?我们记得keytab文件是高安全风险的元素,所以,不建议将其放在应用的 classpath中(尽管这部署起来很方便)。相反,建议将其放在web应用部署目录以外的文件系统中。你可以使 用Spring标准的file:语法来引用这个文件。】 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 322 / 350 页 你可以看到我们配置引用了JDBC UserDetailsService(临时的)。你需要记住往JDBC启动SQL中添加一 个新的用户,其用户名要与试图登录的Kerberos用户相匹配。例如,如果你想以用户kerbuser登录应用,所以 数据库中完整的用户名应该是包含完整Kerberos实体的kerbuser@CORP.JBCPPETS.COM。将其添加到WEB- INF/classes/test-users-groups-data.sql中,如下: insert into users(username, password, enabled, salt) values ('kerbuser@CORP.JBCPPETS.COM','unused',true,CAST(RAND()*1000000000 AS varchar)); insert into group_members(group_id, username) select id,'kerbuser@CORP.JBCPPETS.COM' from groups where group_name='Administrators'; 需要注意的是使用Active Directory,一般会用到LDAP UserDetailsService,甚至一个自定义的来适应你的业 务需求。稍后我们将会介绍这种风格的配置。 织入SPNEGO到security命名空间中 现在我们需要将已经配置的bean织入到security命名空间配置元素中。首先,我们需要添加对我们新 AuthenticationEntryPoint的引用(很像我们在第十章的CAS配置),如下: 我们需要将SPNEGO过滤器插入到Spring Security过滤器链中。一个适当位置应该是用SPNEGO过滤器来取代 form登录过滤器,如下: 最后,我们需要一个引用新的AuthenticationProvider来负责处理SPNEGO tickets。现在我们添加它: 这是为我们应用使用SPNEGO SSO安全所需要的额外配置。 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 323 / 350 页 如果你的机器已经正确配置Kerberos认证,现在你可以重启web应用并在域中的另一台机器尝试进行认 证。 首先来尝试使用IE,IE几乎不需要任何配置来使得用户手动响应SPNEGO请求。你在访问受保护页面如 “My Account”页面时会看到一个类似于下面的弹出框: 祝贺你——如果你看到了这个弹出提示,在你提供正确的凭证后就能访问“My Account”页面了,你已经为 你的web应用成功配置了Kerberos认证。 接下来是配置IE基于请求传递用户的网络凭证。必须设置如下的配置IE才能透明的响应SPNEGO SSO请 求: l 确保站点添加到“本地Intranet”安全区域中。你可以使用“Internet选项”中的“安全”tab标签手动添加你 的站点到安全区域中; l 通过检查“启用保护模式”复选框来确保对于intranet区域启用了IE保护模式; l 在“高级”tab标签中,确保“启动集成windows验证”复选框是选中的。 通过以上明确指明以上的配置(并将IE重启),你应该看到登录用户的凭证会通过SPNEGO认证自动发送到 站点上,用户马上就会登录成功。 添加应用服务器所在机器到Kerberos域中 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 324 / 350 页 如果你是一台新的机器上工作还有最后的一步——配置系统范围内的Kerberos配置,以使得Sun GSS- API能够确定到哪里寻找对域中用户进行认证的Kerberos KDC。这需要创建一个krb5.ini文件,它通常放在 Windows安装目录下(c:\Windows或等价目录)。 因为这个文件的配置语法超出了本书的范围,以下的基本配置在单个域的环境中就足够了,其中KDC位于 corp.jbcppets.com。 [domain_realm] .jbcppets.com = CORP.JBCPPETS.COM [libdefaults] default_realm = CORP.JBCPPETS.COM [logging] [realms] CORP.JBCPPETS.COM = { kdc = corp.jbcppets.com } 作为krb5.ini文件的替代方式,也可以使用JVM的属性来声明默认的域和KDC,如下表: 参数 描述 例子 - Djava.security.krb5.realm 默认域 CORP.JBCPPETS.COM -Djava.security.krb5.kdc 默认KDC corp.jbcppets.com 一般来说,我们建议使用krb5.ini,因为这个文件也可以被其它启用Kerberos的应用所使用。 对Firefox用户的特殊考虑 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 325 / 350 页 默认情况下,Firefox不支持Kerberos认证。这有其历史原因(有些人说这增加了安全性),但是不管怎 样,默认情况下,当收到WWW-Authenticate: Negotiate请求头,firefox不会提示用户。 Firefox的用户必须在about:config页面中修改一个设置以明确添加凭证自动发送到的域,这通过在 network.negotiate-auth.trusted-uris参数提供域名,如下面的截图所述: 通过修改默认的(为空)设置,Firefox会自动的将你的Windows域凭证基于请求发送到站点上。这个属性对 于不熟悉的用户是隐藏的,如果这进行了修改的话,配置Firefox支持SPNEGO要比IE容易得多。 问题解决 不幸的是,Kerberos的正确配置特别复杂(特别对于第一次接触的开发者或管理员),而启用Kerberos 的web应用增加了这种复杂性。 使用标准工具测试连接 首先也是最重要的,确认底层的Kerberos基础设施能够正确运行。这意味着你应该使用标准的Kerberos 工具(如kinit或ktab)或者图形化的客户端如MIT Kerberos for Windows(KfW)客户端(可在这里获取: http://web.mit.edu/Kerberos/ )来进行校验。 kinit和ktab是标准JDK发布版的一部分——ktab的实例命令行如下 ktab -a kerbuser@CORP.JBCPPETS.COM -k kerbuser.keytab http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 326 / 350 页 Password for kerbuser@CORP.JBCPPETS.COM:xxxxx Done! Service key for kerbuser@CORP.JBCPPETS.COM is saved in kerbuser.keytab MIT Kerberos实现提供了图形化的登录和配置界面,这对于新用户来说可能会更容易掌握。 启用Java GSS-API调试 在Java GSS-API中没有太多的日志,而这个库在通过Kerberos进行认证时做了大量的工作,所以如果能 够得到其内部如何工作的将会很有用处。你可以设置JVM属性-Dsun.security.krb5.debug=true或者设置 SunJaasKerberosTicketValidator bean的debug属性如下: 这两种方式都会在失败的情况下在应用服务器的控制台打印出一些有用的信息。 其它问题解决步骤 在使用Spring Security Kerberos Extension以kerberizing(将Kerberos用到)你的web应用时,解决 常见问题的一些建议如下: l 确保对Spring Security设置标准级别的日志,对the org.springframework.security.extensions.kerberos包 至少是WARN级别或更高。如果应用服务器存在配置问题或ticket校验问题,它们一般会在Spring日志中看 到; l 使用网络监控工具或抓包工具来监控应用服务器和KDC之间的通信。这种工具在监控客户端浏览器和应用服务 器时,也会很有用处; l 如果你使用的是Windows,不要在运行服务器的机器上使用浏览器来进行测试——它将不会好用。这是因为 Windows将会自动使用NTLM认证。NTLM认证使用相似的request/response头交换,但是使用不同的数据格 式。校验这个问题的一个方式就是查看客户端浏览器返回Negotiate头这部分的Spring日志。如果这个头信息以 TlRM开头,它就是一个NTLM头而不是SPNEGO。SPNEGO头会以YII开头并且很长; http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 327 / 350 页 l 确保你以域用户而不是本地用户登录Windows,否则Windows将会尝试进行NTLM认证; l 确保DNS方案对于Kerberos认证过程所有参与者都能运行,包括服务端和客户端机器。 即使有了这些解决问题的步骤,可能也会发现成功部署SPNEGO和Kerberos也很有挑战——不要放弃, 你会成功的! 配置LDAP UserDetailsService使用Kerberos 简便起见,我们配置了JDBC UserDetailsService并硬编码Kerberos用户ID。通常,在配置Kerberos认证 (尤其是使用Active Directory),用户的细节信息是通过LDAP从AD中得到的。我们将了解使用Kerberos认 证时,与Microsoft AD比较的常见的LDAP UserDetailsService配置。我们将会略过对Spring Security与LDAP 集成的描述,建议你跳回到第九章:LDAP目录服务中,在那里我们详细介绍过LDAP并包括关于通过LDAP集成 AD的特别建议。 我们可以配置认证提供者(authentication provider)引用LDAP UserDetailsService,而后者定义在 dogstore-security.xml文件中,如下: 这里的关键是user-search-filter,它被userPrincipalName LDAP属性所搜索——这与SPNEGO认证过 程中提供的Kerberos安全实体名字相匹配。注意,你要提供一个manager-dn,它只是有访问AD的权限而实际 上并不是域的管理员。 接下来,简单调整KerberosServiceAuthenticationProvider引用UserDetailsService,在dogstore- base.xml文件中,如下: http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 328 / 350 页 配置技术中唯一要说明的是给认证过的用户匹配权限。回忆一下第九章中,Spring Security LDAP期望用户权 限能以一种特殊的方式在LDAP中查询出来——如果你AD的设置与LDAP内置的匹配不相容,你可能需要写自己 的LdapAuthoritiesPopulator实现。如果你需要这方面的指导,请查询这个接口的实现类作为指导和起点。 还要注意,Active Directory本身可能很复杂并很难在任何情况下直接匹配。你可能需要与试图集成 Active Directory的IT人员进行交流,以确定业务需求和合适的应用模型以满足你的用户和业务需要。 使用Kerberos与form登录 尽管使用SPNEGO为浏览器提供SSO是集成Kerberos和Spring Security的常见驱动力,但是你可能希望 只是将Kerberos登录本身作为一个AuthenticationProvider机制(类似于LDAP的绑定认证方式——参考第九 章以了解细节)。 以这种方式配置Kerberos的好处是我们可以同时支持Kerberos认证用户和使用其它的 AuthenticationProviders(LDAP,JDBC等等)来进行用户认证。 【如果你想尝试这个练习,确保移除在本章第一节中的配置——最重要的是,SpnegoEntryPoint必须移除以使 得用户可以被重定向到登录form,否则没有SPNEGO的用户将会被拒绝访问。】 现在,让我们了解一下配置步骤。在dogstore-base.xml中,要配置的Spring Bean实际上与SPNEGO风 格登录的bean没有任何的重叠之处,所以你尽可以同时对其进行配置(尽管在实际中,你不会很容易地同时拥 有SPNEGO和form登录,所以你可能不会这么做)。 需要如下的bean: http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 329 / 350 页 在上面我们强调的配置是krbConfLocation属性,它指向了一个Kerberos V5文件。这个文件可以像前面章节 提供的krb5.ini文件那样格式化(如果你想知道这个文件的内容,请查阅相关的Kerberos V5文档页面 http://web.mit.edu/kerberos/krb5-1.5/krb5-1.5.1/doc/krb5-admin/krb5.conf.html)。 【请注意,不同于我们前面引用Kerberos keytab文件,这个参数的值必须是文件系统中的物理绝对路径 名(如c:\spring\krb5.conf),并不是Spring风格的file:或classpath:引用。这是因为这个文件路径是直接传递 给Sun JVM的Kerberos子系统,并不会被Spring bean来解释,如SunJaasKerberosTicketValidator所作那 样。同样的,要注意的是这种风格的配置回修改JVM属性设置,所以可能会影响到部署在同一个JVM的其它启 用Kerberos的应用。】 配置文件中列出的default_realm用来认证通过用户界面登录的用户,他们没有提供域名。用户也可以在 用户名带上域名(如kerbuser@jbcppets.com),通常的Kerberos KDC方案规则都会基于Kerberos配置。 因为这种风格的Kerberos配置是用来支持基于form的登录,我们只需配置像配置其它 AuthenticationProvider那样配置KerberosAuthenticationProvider,这在security命名空间的配置文件 dogstore-security.xml里,如下: 在这些配置修改完成后,你可以重启应用并使用form来登录使用Kerberos的后台应用。记住你也需要一个 UserDetailsService实现来处理GrantedAuthority与用户的匹配。记住你可以将这种风格的认证用在其它的 AuthenticationProvider后台认证技术上,包括basic认证。 小结 在本章中,我们学习了怎样构建Kerberos环境,如Windows Domain提供的Microsoft Active Directory,来提供对Windows操作系统用户的集成单点登录。这为用户提供了统一且用户体验良好的(登录方 式),并且减轻了系统管理员的负担。在本章中,我们: l 学习Kerberos SPNEGO认证协议重要元素的整体状况; l 学习了启用Kerberos的web应用所需要的设施和重要步骤; http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 330 / 350 页 l 配置JBCP Pets来支持Kerberos后台的SPNEGO单点登录认证; l 了解启用Kerberos web应用的常见问题解决办法; l 学习了使用AD作为LDAP后台存储用户信息所需要的配置; l 学习了如何配置组合使用基于form的登录以及Kerberos后台系统。 下一章也是最后一章将会涵盖从较早版本的Spring Security进行迁移。我们希望你能喜欢。 http://lengyun3566.iteye.com 1.46 《Spring Security3》第十二章翻译(Spring Security扩展) 第 331 / 350 页 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 发表时间: 2012-02-13 关键字: Spring Security, 安全, 翻译 第十三章 迁移到Spring Security 3 在最后一章中,我们将会了解从Spring Security2迁移到Spring Security3时常见问题的相关情况。 在本章中,我们将会: l 了解Spring Security 3的重要增强; l 理解已有的Spring Security 2应用迁移到Spring Security 3时所需要的配置修改; l 阐述Spring Security 3中重要类和包的移动。 一旦你学完本章的内容,你将会很熟悉怎样将Spring Security 2应用迁移到Spring Security 3上。 从Spring Security2进行迁移 你可能计划将现存的Spring Security 2应用迁移到Spring Security 3,或者试图为Spring Security 2应 用添加功能并想在本书中寻找一些参考。我们将会在本章中尽可能解决你所关注的这两个问题。 首先,我们将会了解Spring Security 2和Spring Security 3的重要区别——包括功能和配置。其次,我 们将会对配置匹配和类名变化提供一些参考。你可以将本书中的例子从Spring Security 3转换到Spring Security 2(这是可行的)。 一个重要的迁移注意事项是Spring Security 3需要Spring框架3和Java5(1.5)或更高。注意的是,在很 多情况下,迁移其它的组件比升级Spring Security会对你的应用产生更大的影响。 Spring Security3的增强 Spring Security 3比Spring Security 2有了很重要的增强,包括: l 增加了Spring表达式语言(Spring Expression Language,SpEL)对访问声明的支持,包括URL和方法访问 声明,我们在第二章:Spring Security起步和第五章:精确的访问控制中已经介绍了; l 添加了对认证和访问成功及失败添加了精确的配置,我们在第二章,第五章进行了简单介绍,而在第六章:高 级配置和扩展中进行了详细介绍; http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 332 / 350 页 l 增强的方法访问声明,包括基于注解的调用事先和事后访问检查和过滤,以及security命名空间配置对自定义 bean行为的良好支持。这些功能我们在第五章中进行了介绍; l 使用security命名空间对session访问和并发控制进行精确管理,这些在第六章中进行了介绍; l 值得一提的是ACL模块,移除了o.s.s.acl中的遗留代码并解决了ACL框架中一些重要问题。ACL的配置和支持 在第七章:访问控制列表中进行了介绍; l 支持OpenID属性交换,以及对OpenID健壮性的其它增强,这在第八章:对OpenID开放中进行了介绍; l 通过Spring Security Extension项目对Kerberos和SAML提供了新的支持,这些我们在第十二章:Spring Security扩展中进行了讨论。 其它重要的良好变化包括对代码和框架的配置进行了重构和清理,所以整体结构和用法都有所变化。 Spring Security的作者努力增加以前所没有的可扩展性,尤其是在登录和URL重定向方面。 如果你已经在Spring Security2环境下工作,如果不是遇到该框架功能的边界你可能找不到强制升级的理 由。但是,如果你发现Spring Security2在可用扩展点、代码结构或可配置性方面的限制,欢迎阅读我们在本章 剩余部分详细讲到的细微变化。 Spring Security配置的变化 Spring Security 3的很多变化在security命名空间风格的配置。尽管本章不能详细涵盖所有的细微变化, 但是我们尽力包含迁移至Spring Security3时会影响到你的变化。 AuthenticationManager配置的重新组织 Spring Security 3最重要的变化在于AuthenticationManager的配置和AuthenticationProvider相关的 元素。在Spring Security 2中,AuthenticationManager和AuthenticationProvider的配置是完全不相关的 ——声明一个AuthenticationProvider不需要任何AuthenticationManager的概念。 在Spring Security 2中,声明元素是作为AuthenticationProvider的兄弟节点 的。 http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 333 / 350 页 在Spring Security 3中,所有的AuthenticationProvider元素必须是的子节点,所以 应该重写为如下格式: 当然这意味着现在元素需要在任何security命名空间配置中都要存在。 在Spring Security 2中,如果你有自定义的AuthenticationProvider,你应该在其bean声明中用 元素来进行包装,例如,我们在第六章中实现的自定义 AuthenticationProvider: 但是将这个自定义AuthenticationProvider迁移到Spring Security 3时,我们需要移除包装元素并使用 元素的ref属性来配置AuthenticationProvider,就像我们在第三章中所看到的,如 下: http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 334 / 350 页 当然,自定义provider的源码会因为Spring Security 3的类更换位置和改名而有所变化——在本章后面会有基 本介绍而在本章的源码中会有更为细节的匹配关系。 Session管理选项的新配置语法 除了继续支持框架前面版本的session固化和并发控制功能,Spring Security 3为自定义URL以及session 和并发控制功能相关的类添加了新的配置功能,我们在第六章中进行了详细介绍。如果你的旧应用配置了 session固化和并发session控制,这些配置有了新的位置,即在中的指令 中。 在Spring Security 2中,这些选项将会配置如下: 在Spring Security 3的配置语法中,从元素中移除了session-fixation-protection属性,配置如下: 你可以看到,这些选项的新的逻辑组织更为合理并为进一步的扩展留下了空间。 自定义过滤器配置的变化 很多的Spring Security 2用户开发过自定义的认证过滤器(或其它过滤器来改变安全请求的流程)。如同 自定义的认证provider,以前这些过滤器也是通过带有元素的bean来声明的。这使得在一些 场景下将过滤器直接配置到Spring Security配置中有点困难。 http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 335 / 350 页 在Spring Security 2的环境下,让我们看一下第六章中签名请求头过滤器的配置示例。 将其与Spring Security 3相同的配置进行对比,你可以看到bean的声明和安全织入是独立完成的。自定义的过 滤器在元素中声明,如下: 尽管bean的声明与Spring Security 2保持相同,但是你可能会预料到,自定义过滤器的代码差别很大。我们在 本章包含了过滤器的示例代码(使用Spring Security 2),为你了解自定义过滤器在转换成Spring Security 3 之前和之后是什么样子提供指导。 另外,一些过滤器的逻辑名在Spring Security 3中发生了变化。我们在下面提供了一个变化列表而在附 录:参考材料中提供了完整的列表。 Spring Security 2 Spring Security 3 SESSION_CONTEXT_INTEGRATION_FILTER SECURITY_CONTEXT_FILTER CAS_PROCESSING_FILTER CAS_FILTER AUTHENTICATION_PROCESSING_FILTER FORM_LOGIN_FILTER http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 336 / 350 页 OPENID_PROCESSING_FILTER OPENID_FILTER BASIC_PROCESSING_FILTER BASIC_AUTH_FILTER NTLM_FILTER 在Spring Security中移除了 你在定位元素时,在你的配置文件中必须进行这些修改。 CustomAfterInvocationProvider的变化 Spring Security 2中的最后一个bean包装被直接、内联的元素声明所取代了,这就是元素声明的CustomAfterInvocationProvider。 类似于我们在前面看到其它Spring Security 2 bean包装,在Spring Security 3中,这个元素被移到声明中,只会有一个简单的bean引用。 我们已经在第五章中介绍了方法安全的各个方面,包括在Spring Security 3中新增的一些关于方法安全的有趣 选项。 小的配置变化 以下简单介绍了Spring Security 2 和3之间的其它配置属性变化: l 在Spring Security 3中使用auto-config属性,将不会默认配置remember me服务。你需要在元素中 明确添加声明; http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 337 / 350 页 l 对于LDAP配置,在Spring Security 3中group-search-base-attribute的默认值(用来进行LDAP权限查找) 从ou=Groups变成了空字符串(LDAP的根)。我们在第九章:LDAP目录服务中用过该属性; l 在Spring Security 3中,用来手动配置过滤器链的包装元素被重命名 为。我们在第六章使用明确bean配置的时候用过这种类型的设置; l 在Spring Security 3中,元素相关的exception-if-maximum-exceeded属 性被移到并重命名为元素的error-if-maximum-exceeded属性; l 在Spring Security 3中,当使用内存DAO UserDetailsService在配置文件中声明用户时,password属性不再 是必须的; l 内置的NTLM认证支持已经在Spring Security 3中移除了,而是用Kerberos认证进行了替换(请查阅第十二 章了解配置Kerberos的细节)。这是升级用户比较关注的事情,而在Spring Security社区中有一些活动想让 Spring Security 3支持NTLM。也许在本书出版的时候,可能会有一个Spring Security扩展项目支持Spring Security 3中的NTLM。 Spring Security 3中剩余的security命名空间XML配置语法变化为功能的增加,并不会对已有应用带来迁 移的问题。 包和类的变化 尽管在比较简单的Spring Security 2应用中,类在包中的位置关系不大,但是大多数的Spring Security 应用不会与底层的代码无关。所以,我们感觉为你指出Spring Security 2 和3之间的总体的包迁移和类重命名 会有所用处。 不管在什么地方,我们都试图尽可能精确的匹配类——在这里我们提供了整体的主要包迁移,并且(如果 你需要)更复杂的列表可以随源码一起下载。下表展现了Spring Security 2到Spring Security 3最大的类位置 变更——我们已经压缩了这个表以使其包含大多数的你可能希望看到的变化: 类的数量 Spring 2中的位置 Spring 3中的位置 13 o.s.s o.s.s.authentication 13 o.s.s.acls o.s.s.acls.model 13 o.s.s.event.authentication o.s.s.authentication.event http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 338 / 350 页 12 o.s.s.vote o.s.s.access.vote 11 o.s.s.ui.rememberme o.s.s.web.authentication.rememberme 10 o.s.s.providers.jaas o.s.s.authentication.jaas 10 o.s.s.securechannel o.s.s.web.access.channel 10 o.s.s.userdetails.ldap o.s.s.ldap.userdetails 9 o.s.s.providers.encoding o.s.s.authentication.encoding 8 o.s.s.config o.s.s.config.authentication 8 o.s.s.util o.s.s.web.util 7 o.s.s.config o.s.s.config.http 7 o.s.s.context o.s.s.core.context 7 o.s.s.userdetails o.s.s.core.userdetails 6 o.s.s o.s.s.access 6 o.s.s.afterinvocation o.s.s.acls.afterinvocation 6 o.s.s.event.authorization o.s.s.access.event 6 o.s.s.util o.s.s.web 5 o.s.s.annotation o.s.s.access.annotation http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 339 / 350 页 5 o.s.s.authoritymapping o.s.s.core.authority.mapping 5 o.s.s.providers o.s.s.authentication 5 o.s.s.token o.s.s.core.token 5 o.s.s.ui o.s.s.web.authentication 如果你发现类移动相当大,你是正确的!在Spring Security 3包的重新组织中,很少有类不被接触到。希 望这个整体的介绍能够在你寻找类的时候为你指明方向。再一次强调,请查询本章的下载内容来了解更详细的 类和类之间的匹配。 对整个框架重新组织的好处在于现在更加模块化,并将JAR文件分割为特定功能的元素,介绍如下(我们 使用nnn待代替释放版本号): JAR名 功能 spring-security-acl-nnn.jar 支持ACL(见第七章) spring-security-cas-client-nnn.jar 支持CAS(见第十章) spring-security-config-nnn.jar 整体配置支持 spring-security-core-nnn.jar 核心框架和类 spring-security-ldap-nnn.jar 支持LDAP(见第九章) spring-security-openid-nnn.jar 支持OpenID(见第八章) spring-security-taglibs-nnn.jar 支持JSP标签库(见第三、五、七章) http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 340 / 350 页 spring-security-web-nnn.jar 支持web层 模块化意味着,例如,可以部署Spring Security到一个非web的应用而不需要任何web相关的依赖 (spring-security-config和spring-security-core可能足够满足你的需求)。 小结 本章中我们了解了将已有的Spring Security 2项目升级到Spring Security 3会遇到的大小变化。在本章 中,我们: l 了解了我们将要升级的框架所拥有的明显功能增强; l 学习了可能阻碍升级的需求、依赖以及常见的代码和配置变化; l 调查了(整体上)Spring Security的作者在代码重构时代码重新组织的变化。 如果这是你阅读的第一章,我们希望你能转向本书的其它部分,将本章作为平滑升级到Spring Security 3 的一个指导。 http://lengyun3566.iteye.com 1.47 《Spring Security3》第十三章翻译(迁移到Spring Security 3) 第 341 / 350 页 1.48 《Spring Security3》附录翻译(参考资料) 发表时间: 2012-02-13 关键字: Spring Security, 安全, 翻译 附录:参考材料 在本附录中,将会涉及到一些我们感觉有用的参考材料(并相当缺乏文档),而将其插入到章节的内容中 又会觉得过于综合。 JBCP Pets示例代码起步 就像我们在第一章:一个不安全应用的剖析中所描述的那样,我们假设你已有了Eclipse 3.4(或 3.5)IDE,并包含Web Tools Package(WTP)。示例代码按每章被组织成了不同的ZIP文件,并有一个较大 的ZIP文件其中包含了编译和运行示例应用所需要的所有依赖(注意的是,当你阅读本书的时候对应于最新版本 的Spring Security它们可能已经较旧了,但是鉴于示例代码和依赖是一个静态的快照,它们能够永远运行)。 我们建议你对每一章建立新的Eclipse工作空间,这样你能够切换工作空间而不用打开或关闭项目。 以下的步骤帮助你建立新的工作空间。首先,我们要导入Dependencies项目到工作空间中: l 选择“File”按钮,接下来是“Import…”选项。选择“General”文件夹以及“Existing Projects into Workspace”并点击“Next”; l 确保“Select root directory”选项被选中,点击“Browse...”按钮到文本框。定位到你解压 Dependencies.zip的目录并点击“OK”; l 你应该可以看到Dependencies项目被列了出来。点击“Finish”。 接下来,我们需要导入每章的源码ZIP文件。假设你已经解压了第二章:Spring Security起步的源码到目 录中。 l 选择“File”菜单以及“Import…”选项。选择“General”文件夹以及“Existing Projects into Workspace”。点击“Next”; l 确保“Select root directory”选项被选中并点击“Browse…”按钮到文本框。定位到你解压第二章源码ZIP 文件的目录并点击“OK”; l 你可以看到“JBCPPets”项目和“Servers”项目被裂了出来。将它们都选中然后点击“Finish”。 最后,我们需要将JBCP Pets web应用部署到一个Tomcat实例上(或者你喜欢的应用服务器)。 http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 342 / 350 页 l 右键点击“JBCPPets”项目并选择“Run As”菜单。在子菜单中选择“Run on Server”菜单项; l 这时候你可能需要创建一个新的应用服务器实例。只需要按照你应用服务器的提示直到web应用部署完成。 此时,你应该可以运行JBCP Pets应用了。如果你遇到问题,可查看如下的列表: l Eclipse是否列出了构建错误?如果有任何的Java错误或classpath错误,它们是应该被解决的真正错误。有时 候,Spring IDE插件会报出假的错误或警告; l 查看Eclipse的应用服务器启动控制台,这里包含不成功部署的web应用的错误。最常见的问题是缺失 classpath条目,或者忘记将所有的classpath条目添加到JBCPPets 项目的“Java EE Module Dependencies”。尽管这些应该已经为你做好,但是我们不能保证在任何版本的Eclipse下均好用。 对于示例代码有任何问题请联系我们——理解代码和它的概念对你来说很重要! 可用的应用事件 下面的表格,在第六章:高级配置和扩展曾经提到,列出了各种Spring Security元素所发布的全部事件并 提供了第二章中引用到的认证异常。为了简便,我们移除了包名,因为所有的时间都在 o.s.s.authentication.event(认证相关的事件)和o.s.s.access.event(授权相关的事件)中。 类名 何时触发 匹配的异常 AbstractAuthenticationEvent 所有认证时间的通用父类。注意这 是一个从来不会被抛出的抽象异常 (尽管其可以被捕获) AbstractAuthenticationFailureEvent 所有认证失败事件的通用父类。注 意这是一个从来不会被抛出的抽象 异常(尽管其可以被捕获) AuthenticationFailureBadCredentialsEvent 当提供的凭证(如用户名和密码) 不合法时。它能够用来(有意的) 掩盖 UsernameNotFoundException。 BadCredentialsException UsernameNotFoundException http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 343 / 350 页 AuthenticationFailureConcurrentLoginEvent 当并发session最大值超出时。 ConcurrentLoginException AuthenticationFailureCredentialsExpiredEvent 当UserDetails标识用户凭证过期 时。 CredentialsExpiredException AuthenticationFailureDisabledEvent 当UserDetails标识用户凭证不可 用时。 DisabledException AuthenticationFailureExpiredEvent 当UserDetails标识用户账号过期 时。 AccountExpiredException AuthenticationFailureLockedEvent 当UserDetails标识用户账号被锁 定时。 LockedException AuthenticationFailureProviderNotFoundEvent 配置错误,当不能找到 Authentication Provider认证用 户请求时。 ProviderNotFoundException AuthenticationFailureProxyUntrustedEvent 当CAS代理ticket不可信时。 AuthenticationFailureServiceExceptionEvent 当底层服务(如DAO Provider) 失败时,抛出的一般异常。 AuthenticationServiceException AuthenticationSuccessEvent 当用户成功认证时。 AuthenticationSwitchUserEvent 当成功完成用户切换行为时。 InteractiveAuthenticationSuccess Event 当用户通过提供完整凭证认证成功 时(类似于 IS_FULLY_AUTHENTICATED GrantedAuthority语法) AbstractAuthorizationEvent 所有认证事件的通用父类。 http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 344 / 350 页 AuthenticationCredentialsNot FoundEvent 当用户没有认证而试图触发需要访 问检查的方法时。 AuthorizationFailureEvent 当方法前或后的访问检查失败时。 AuthorizedEvent 当方法前或后的访问检查成功时。 PublicInvocationEvent 当未认证的安全对象请求成功时。 SessionCreationEvent HttpSession创建时。 SessionDestroyedEvent HttpSession销毁时。 Spring Security的虚拟URL 以下的URL被Spring Security视为虚拟URL,并独立于你的代码作为servelt过滤器流程的一部分进行监视 (并处理)。记住的是这些URL是相对于你的web应用上下文根的。 l /j_spring_security_check——被UsernamePasswordAuthenticationFilter检查进行用户名/密码form认 证; l /j_spring_openid_security_check——被OpenIDAuthenticationFilter检查OpenID返回认证信息(从 OpenID provider处); l /j_spring_cas_security_check——基于CAS SSO登录的返回,进行CAS认证 l /spring_security_login——当配置自动生成登录页面时,DefaultLoginPageGeneratingFilter使用的URL; l /j_spring_security_logout——LogoutFilter使用来检测退出行为; l /saml/SSO——Spring Security SAML SSO extension SAMLProcessingFilter使用来进行SAML SSO登录 请求; l /saml/logout——Spring Security SAML SSO extension SAMLLogoutFilter使用来进行SAML SSO退出请 求; http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 345 / 350 页 l /j_spring_security_switch_user——SwitchUserFilter使用来将用户切换至另一用户; l /j_spring_security_exit_user——用来退出切换用户功能。 注意的是,有一些功能在本书中没有涵盖,但是在这里我们为了完整性全部包括了。 方法安全的明确bean配置 第六章源码的dogstore-explicit-base.xml文件中包含了这里bean声明的全集。我们之所以在第六章本身 中没有包含它,是因为它与要表述的没有太大关系(参考第五章:精确的访问控制来了解相关bean的功能)。 以下是通过Spring bean声明启用方法安全的完整配置: http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 346 / 350 页 http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 347 / 350 页 请注意,明确的bean配置与你使用的Spring Security版本密切相关(就像我们在第六章提到的)。如果在你版 本的Spring Security中使用列出的bean遇到问题,请参考 o.s.s.config.method.GlobalMethodSecurityBeanDefinitionParser。 http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 348 / 350 页 这个配置启用了JSR-250的@Secured和@Pre/@Post注解。如果你不使用它们要注释掉或移除相关的支 持bean(如@Secured)。记住,SecurityMetadataSource和AccessDecisionVoter都要移除。 逻辑过滤器名字迁移参考 正如在第十三章:迁移到Spring Security 3所讨论的,很多逻辑过滤器名(在用到)在 从Spring Security 2升级到Spring Security 3时发生了变化。这里我们提供了所有的变化,来方便你从Spring Security 2 到3对自定义过滤器的配置: Spring Security 2 Spring Security 3 CHANNEL_FILTER CHANNEL_FILTER CONCURRENT_SESSION_FILTER CONCURRENT_SESSION_FILTER SESSION_CONTEXT_INTEGRATION_ FILTER SECURITY_CONTEXT_FILTER LOGOUT_FILTER LOGOUT_FILTER PRE_AUTH_FILTER PRE_AUTH_FILTER CAS_PROCESSING_FILTER CAS_FILTER AUTHENTICATION_PROCESSING_FILTER FORM_LOGIN_FILTER OPENID_PROCESSING_FILTER OPENID_FILTER Spring Security 2没有提供 LOGIN_PAGE_FILTER LOGIN_PAGE_FILTER Spring Security 2没有提供 DIGEST_AUTH_FILTER DIGEST_AUTH_FILTER http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 349 / 350 页 BASIC_PROCESSING_FILTER BASIC_AUTH_FILTER Spring Security 2没有提供 REQUEST_CACHE_FILTER REQUEST_CACHE_FILTER SERVLET_API_SUPPORT_FILTER SERVLET_API_SUPPORT_FILTER REMEMBER_ME_FILTER REMEMBER_ME_FILTER ANONYMOUS_FILTER ANONYMOUS_FILTER Spring Security 2没有提供 SESSION_MANAGEMENT_FILTER SESSION_MANAGEMENT_FILTER EXCEPTION_TRANSLATION_FILTER EXCEPTION_TRANSLATION_FILTER NTLM_FILTER Spring Security 3中移除了 NTLM_FILTER FILTER_SECURITY_INTERCEPTOR FILTER_SECURITY_INTERCEPTOR SWITCH_USER_FILTER SWITCH_USER_FILTER http://lengyun3566.iteye.com 1.48 《Spring Security3》附录翻译(参考资料) 第 350 / 350 页
    还剩349页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

    下载pdf

    pdf贡献者

    ryan夏天

    贡献于2015-10-21

    下载需要 10 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf