资源描述:
《spring中文api文档》由会员上传分享,免费在线阅读,更多相关内容在应用文档-天天文库。
这次发布的SpringSecurity-3.0.1是一个bugfix版,主要是对3.0中存在的一些问题进行修正。文档中没有添加新功能的介绍,但是将之前拼写错误的一些类名进行了修正,建议开发者以这一版本的文档为参考。另:SpringSecurity从2010-01-01以后,版本控制从SVN换成了GIT,我们在翻译文档的时候,主要是根据SVN的变化来进行文档内容的比对,这次换成GIT后,感觉缺少了之前那种文本比对工具,如果有对GIT熟悉的朋友,还请推荐一下文本比对的工具,谢谢。序言I.入门1.介绍1.1.SpringSecurity是什么?1.2.历史1.3.发行版本号1.4.获得SpringSecurity1.4.1.项目模块1.4.1.1.Core-spring-security-core.jar1.4.1.2.Web-spring-security-web.jar1.4.1.3.Config-spring-security-config.jar1.4.1.4.LDAP-spring-security-ldap.jar1.4.1.5.ACL-spring-security-acl.jar1.4.1.6.CAS-spring-security-cas-client.jar1.4.1.7.OpenID-spring-security-openid.jar1.4.2.获得源代码2.Security命名空间配置2.1.介绍2.1.1.命名空间的设计2.2.开始使用安全命名空间配置2.2.1.配置web.xml2.2.2.最小配置2.2.2.1.auto-config包含了什么?2.2.2.2.表单和基本登录选项2.2.3.使用其他认证提供器2.2.3.1.添加一个密码编码器2.3.高级web特性2.3.1.Remember-Me认证2.3.2.添加HTTP/HTTPS信道安全2.3.3.会话管理2.3.3.1.检测超时2.3.3.2.同步会话控制2.3.3.3.防止Session固定攻击2.3.4.对OpenID的支持2.3.4.1.属性交换2.3.5.添加你自己的filter 2.3.5.1.设置自定义AuthenticationEntryPoint2.4.保护方法2.4.1.元素2.4.1.1.使用protect-pointcut添加安全切点2.5.默认的AccessDecisionManager2.5.1.自定义AccessDecisionManager2.6.验证管理器和命名空间3.示例程序3.1.Tutorial示例3.2.Contacts3.3.LDAP例子3.4.CAS例子3.5.Pre-Authentication例子4.SpringSecurity社区4.1.任务跟踪4.2.成为参与者4.3.更多信息II.结构和实现5.技术概述5.1.运行环境5.2.核心组件5.2.1.SecurityContextHolder,SecurityContext和Authentication对象5.2.1.1.获得当前用户的信息5.2.2.UserDetailsService5.2.3.GrantedAuthority5.2.4.小结5.3.验证5.3.1.什么是SpringSecurity的验证呢?5.3.2.直接设置SecurityContextHolder的内容5.4.在web应用中验证5.4.1.ExceptionTranslationFilter5.4.2.AuthenticationEntryPoint5.4.3.验证机制5.4.4.在请求之间保存SecurityContext。5.5.SpringSecurity中的访问控制(验证)5.5.1.安全和AOP建议5.5.2.安全对象和AbstractSecurityInterceptor5.5.2.1.配置属性是什么?5.5.2.2.RunAsManager5.5.2.3.AfterInvocationManager5.5.2.4.扩展安全对象模型5.6.国际化6.核心服务6.1.TheAuthenticationManager,ProviderManager和AuthenticationProviders 6.1.1.DaoAuthenticationProvider6.2.UserDetailsService实现6.2.1.内存认证6.2.2.JdbcDaoImpl6.2.2.1.权限分组6.3.密码加密6.3.1.什么是散列加密?6.3.2.为散列加点儿盐6.3.3.散列和认证III.web应用安全7.安全过滤器链7.1.DelegatingFilterProxy7.2.FilterChainProxy7.2.1.绕过过滤器链7.3.过滤器顺序7.4.使用其他过滤器——基于框架8.核心安全过滤器8.1.FilterSecurityInterceptor8.2.ExceptionTranslationFilter8.2.1.AuthenticationEntryPoint8.2.2.AccessDeniedHandler8.3.SecurityContextPersistenceFilter8.3.1.SecurityContextRepository8.4.UsernamePasswordAuthenticationFilter8.4.1.认证成功和失败的应用流程9.Basic(基本)和Digest(摘要)验证9.1.BasicAuthenticationFilter9.1.1.配置9.2.DigestAuthenticationFilter9.2.1.Configuration10.Remember-Me认证10.1.概述10.2.简单基于散列标记的方法10.3.持久化标记方法10.4.Remember-Me接口和实现10.4.1.TokenBasedRememberMeServices10.4.2.PersistentTokenBasedRememberMeServices11.会话管理11.1.SessionManagementFilter11.2.SessionAuthenticationStrategy11.3.同步会话12.匿名认证12.1.概述12.2.配置 12.3.AuthenticationTrustResolverIV.授权13.验证架构13.1.验证13.2.处理预调用13.2.1.AccessDecisionManager13.2.2.基于投票的AccessDecisionManager实现13.2.2.1.RoleVoter13.2.2.2.AuthenticatedVoter13.2.2.3.CustomVoters13.3.处理后决定14.安全对象实现14.1.AOP联盟(MethodInvocation)安全拦截器14.1.1.精确的MethodSecurityIterceptor配置14.2.AspectJ(JoinPoint)安全拦截器15.基于表达式的权限控制15.1.概述15.1.1.常用内建表达式15.2.Web安全表达式15.3.方法安全表达式15.3.1.@Pre和@Post注解15.3.1.1.访问控制使用@PreAuthorize和@PostAuthorize15.3.1.2.过滤使用@PreFilter和@PostFilter16.acegi到springsecurity的转换方式16.1.SpringSecurity是什么16.2.目标16.3.步骤16.4.总结V.高级话题17.领域对象安全(ACLs)17.1.概述17.2.关键概念17.3.开始18.预认证场景18.1.预认证框架类18.1.1.AbstractPreAuthenticatedProcessingFilter18.1.2.AbstractPreAuthenticatedAuthenticationDetailsSource18.1.2.1.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource18.1.3.PreAuthenticatedAuthenticationProvider18.1.4.Http403ForbiddenEntryPoint18.2.具体实现18.2.1.请求头认证(Siteminder)18.2.1.1.Siteminder示例配置18.2.2.J2EE容器认证 19.LDAP认证19.1.综述19.2.在SpringSecurity里使用LDAP19.3.配置LDAP服务器19.3.1.使用嵌入测试服务器19.3.2.使用绑定认证19.3.3.读取授权19.4.实现类19.4.1.LdapAuthenticator实现19.4.1.1.常用功能19.4.1.2.BindAuthenticator19.4.1.3.PasswordComparisonAuthenticator19.4.1.4.活动目录认证19.4.2.链接到LDAP服务器19.4.3.LDAP搜索对象19.4.3.1.FilterBasedLdapUserSearch19.4.4.LdapAuthoritiesPopulator19.4.5.SpringBean配置19.4.6.LDAP属性和自定义UserDetails20.JSP标签库20.1.声明Taglib20.2.authorize标签20.3.authentication标签20.4.accesscontrollist标签21.Java认证和授权服务(JAAS)供应器21.1.概述21.2.配置21.2.1.JAASCallbackHandler21.2.2.JAASAuthorityGranter22.CAS认证22.1.概述22.2.CAS是如何工作的22.3.配置CAS客户端23.X.509认证23.1.概述23.2.把X.509认证添加到你的web系统中23.3.为tomcat配置SSL24.替换验证身份24.1.概述24.2.配置A.安全数据库表结构A.1.User表A.1.1.组权限A.2.持久登陆(Remember-Me)表 A.3.ACL表A.3.1.HypersonicSQLA.3.1.1.PostgreSQLB.安全命名空间B.1.Web应用安全-元素B.1.1.属性B.1.1.1.servlet-api-provisionB.1.1.2.path-typeB.1.1.3.lowercase-comparisonsB.1.1.4.realmB.1.1.5.entry-point-refB.1.1.6.access-decision-manager-refB.1.1.7.access-denied-pageB.1.1.8.once-per-requestB.1.1.9.create-sessionB.1.2.B.1.3.元素B.1.3.1.patternB.1.3.2.methodB.1.3.3.accessB.1.3.4.requires-channelB.1.3.5.filtersB.1.4.元素B.1.5.元素B.1.5.1.login-pageB.1.5.2.login-processing-urlB.1.5.3.default-target-urlB.1.5.4.always-use-default-targetB.1.5.5.authentication-failure-urlB.1.5.6.authentication-success-handler-refB.1.5.7.authentication-failure-handler-refB.1.6.元素B.1.7.元素B.1.7.1.data-source-refB.1.7.2.token-repository-refB.1.7.3.services-refB.1.7.4.token-repository-refB.1.7.5.key属性B.1.7.6.token-validity-secondsB.1.7.7.user-service-refB.1.8.元素B.1.8.1.session-fixation-protectionB.1.9.元素B.1.9.1.max-sessions属性 B.1.9.2.expired-url属性B.1.9.3.error-if-maximum-exceeded属性B.1.9.4.session-registry-alias和session-registry-ref属性B.1.10.元素B.1.11.元素B.1.11.1.subject-principal-regex属性B.1.11.2.user-service-ref属性B.1.12.元素B.1.13.元素B.1.13.1.logout-url属性B.1.13.2.logout-success-url属性B.1.13.3.invalidate-session属性B.1.14.元素B.2.认证服务B.2.1.元素B.2.1.1.元素B.2.1.2.使用来引用一个AuthenticationProviderBeanB.3.方法安全B.3.1.元素B.3.1.1.secured-annotations和jsr250-annotations属性B.3.1.2.安全方法使用B.3.1.3.元素B.3.2.LDAP命名空间选项B.3.2.1.使用元素定义LDAP服务器B.3.2.2.元素B.3.2.3.元素PartI.入门本指南的后面部分提供对框架结构和实现类的深入讨论,了解它们,对你进行复杂的定制是十分重要的。在这部分,我们将介绍SpringSecurity3.0,简要介绍该项目的历史,然后看看如何开始在程序中使用框架。特别是,我们将看看命名控件配置提供了一个更加简单的方式,在使用传统的springbean配置时,你不得不实现所有类。我们也会看看可用的范例程序。它们值得试着运行,实验,在你阅读后面的章节之前-你可以在对框架有了更多连接之后再回来看这些例子。也请参考项目网站获得构建项目有用的信息,另外链接到网站,视频和教程。Chapter1.介绍1.1.SpringSecurity是什么?SpringSecurity为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-spring框架开发的企业软件项目。如果你没有使用Spring开发企业软件,我们热情的推荐你仔细研究一下。熟悉Spring-尤其是依赖注入原理-将帮助你更快的掌握SpringSecurity。人们使用SpringSecurity有很多种原因,不过通常吸引他们的是在J2EEServlet规范或EJB规范中找不到典型企业应用场景的解决方案。提到这些规范,特别要指出的是它们不能在WAR或EAR级别进行移植。这样,如果你更换服务器环境,就要,在新的目标环 境进行大量的工作,对你的应用系统进行重新配置安全。使用SpringSecurity解决了这些问题,也为你提供了很多有用的,可定制的其他安全特性。你可能知道,安全包括两个主要操作,“认证”和“验证”(或权限控制)。这就是SpringSecurity面向的两个主要方向。“认证”是为用户建立一个他所声明的主体的过程,(“主体”一般是指用户,设备或可以在你系统中执行行动的其他系统)。“验证”指的一个用户能否在你的应用中执行某个操作。在到达授权判断之前,身份的主体已经由身份验证过程建立了。这些概念是通用的,不是SpringSecurity特有的。在身份验证层面,SpringSecurity广泛支持各种身份验证模式。这些验证模型绝大多数都由第三方提供,或正在开发的有关标准机构提供的,例如InternetEngineeringTaskForce。作为补充,SpringSecurity也提供了自己的一套验证功能。SpringSecurity目前支持认证一体化和如下认证技术:HTTPBASICauthenticationheaders(一个基于IEFTRFC的标准)HTTPDigestauthenticationheaders(一个基于IEFTRFC的标准)HTTPX.509clientcertificateexchange(一个基于IEFTRFC的标准)LDAP(一个非常常见的跨平台认证需要做法,特别是在大环境)Form-basedauthentication(提供简单用户接口的需求)OpenIDauthentication基于预先建立的请求头进行认证(比如ComputerAssociatesSiteminder)JA-SIGCentralAuthenticationService(也被称为CAS,这是一个流行的开源单点登录系统)TransparentauthenticationcontextpropagationforRemoteMethodInvocation(RMI)andHttpInvoker(一个Spring远程调用协议)Automatic"remember-me"authentication(这样你可以设置一段时间,避免在一段时间内还需要重新验证)Anonymousauthentication(允许任何调用,自动假设一个特定的安全主体)Run-asauthentication(这在一个会话内使用不同安全身份的时候是非常有用的)JavaAuthenticationandAuthorizationService(JAAS)JEEContainerautentication(这样,你可以继续使用容器管理认证,如果想的话)KerberosJavaOpenSourceSingleSignOn(JOSSO)*OpenNMSNetworkManagementPlatform*AppFuse*AndroMDA*MuleESB*DirectWebRequest(DWR)*Grails*Tapestry*JTrac*Jasypt*Roller*ElasticPlath*AtlassianCrowd*你自己的认证系统(向下看)(*是指由第三方提供,查看我们的整合网页,获得最新详情的链接。) 许多独立软件供应商(ISVs,independentsoftwarevendors)采用SpringSecurity,是因为它拥有丰富灵活的验证模型。这样,无论终端用户需要什么,他们都可以快速集成到系统中,不用花很多功夫,也不用让用户改变运行环境。如果上述的验证机制都没有满足你的需要,SpringSecurity是一个开放的平台,编写自己的验证机制是十分简单的。SpringSecurity的许多企业用户需要整合不遵循任何特定安全标准的“遗留”系统,SpringSecurity在这类系统上也表现的很好。有时基本的认证是不够的。有时你需要根据在主体和应用交互的方式来应用不同的安全措施。比如,你可能,为了保护密码,不被窃听或受到中间人攻击,希望确保请求只通过HTTPS到达。这在防止暴力攻击保护密码恢复过程特别有帮助,或者简单的,让人难以复制你的系统的关键字内容。为了帮助你实现这些目标,SpringSecurity完全支持自动“信道安全”,整合jcaptcha一体化进行人类用户检测。SpringSecurity不仅提供认证功能,也提供了完备的授权功能。在授权方面主要有三个领域,授权web请求,授权被调用方法,授权访问单个对象的实例。为了帮你了解它们之间的区别,对照考虑授在Servlet规范web模式安全,EJB容器管理安全,和文件系统安全方面的授权方式。SpringSecurity在所有这些重要领域都提供了完备的能力,我们将在这份参考指南的后面进行探讨。1.2.历史SpringSecurity开始于2003年年底,““spring的acegi安全系统”。起因是Spring开发者邮件列表中的一个问题,有人提问是否考虑提供一个基于spring的安全实现。在当时Spring的社区相对较小(尤其是和今天的规模比!),其实Spring本身是从2003年初才作为一个sourceforge的项目出现的。对这个问题的回应是,这的确是一个值得研究的领域,虽然限于时间问题阻碍了对它的继续研究。有鉴于此,一个简单的安全实现建立起来了,但没有发布。几周之后,spring社区的其他成员询问安全问题,代码就被提供给了他们。随后又有人请求,在2004年一月左右,有20人在使用这些代码。另外一些人加入到这些先行者中来,并建议在sourceforge上建立一个项目,项目在2004年3月正式建立起来。在早期,项目本身没有自己的认证模块。认证过程都是依赖容器管理安全的,而acegi则注重授权。这在一开始是合适的,但随着越来越多用户要求提供额外的容器支持,基于容器认证的限制就显现出来了。还有一个有关的问题,向容器的classpath中添加新jar,常常让最终用户感到困惑,又容易出现配置错误。随后acegi加入了认证服务。大约一年后,acegi成为spring的官方子项目。经过了两年半在许多生产软件项目中的活跃使用和数以万计的改善和社区的贡献,1.0.0最终版本发布于2006年5月。acegi在2007年年底,正式成为spring组合项目,被更名为“SpringSecurity”。现在,SpringSecurity成为了一个强大而又活跃的开源社区。在SpringSecurity支持论坛上有成千上万的信息。有一个积极的核心开发团队专职开发,一个积极的社区定期共享补丁并支持他们的同伴。1.3.发行版本号了解springSecurity发行版本号是非常有用的。它可以帮助你判断升级到新的版本是否需要花费很大的精力。我们使用apache便携式运行项目版本指南,可以在以下网址查看http://apr.apache.org/versioning.html。为了方便大家,我们引用页面上的一段介绍:“版本号是一个包含三个整数的组合:主要版本号.次要版本号.补丁。基本思路是主要版本是不兼容的,大规模升级API。次要版本号在源代码和二进制要与老版本保持兼容,补丁则意味着向前向后的完全兼容。” 1.4.获得SpringSecurity你可以通过多种方式获得SpringSecurity。你可以下载打包好的发行包,从Spring的网站下载页,下载单独的jar(和实例WAR文件)从Maven中央资源库(或者SpringSourceMaven资源库,获得快照和里程碑发布)。可选的,你可以通过源代码创建项目。参考项目网站获得更多细节。1.4.1.项目模块在SpringSecurity3.0中,项目已经分割成单独的jar,这更清楚的按照功能进行分割模块和第三方依赖。如果你在使用Maven来构建你的项目,你需要把这些模块添加到你的pom.xml中。即使你没有使用Maven,我们也推荐你参考这个pom.xml文件,来了解第三方依赖和对应的版本。可选的,一个好办法是参考实例应用中包含的依赖库。1.4.1.1.Core-spring-security-core.jar包含了核心认证和权限控制类和接口,运程支持和基本供应API。使用SpringSecurity所必须的。支持单独运行的应用,远程客户端,方法(服务层)安全和JDBC用户供应。包含顶级包:org.springframework.security.coreorg.springframework.security.accessorg.springframework.security.authenticationorg.springframework.security.provisioningorg.springframework.security.remoting1.4.1.2.Web-spring-security-web.jar包含过滤器和对应的web安全架构代码。任何需要依赖servletAPI的。你将需要它,如果你需要SpringSecurityWeb认证服务和基于URL的权限控制。主包是org.springframework.security.web。1.4.1.3.Config-spring-security-config.jar包含安全命名控制解析代码(因此我们不能直接把它用在你的应用中)。你需要它,如果使用了SpringSecurityXML命名控制来进行配置。主包是org.springframework.security.config。1.4.1.4.LDAP-spring-security-ldap.jarLDAP认证和实现代码,如果你需要使用LDAP认证或管理LDAP用户实体就是必须的。顶级包是org.springframework.security.ldap。1.4.1.5.ACL-spring-security-acl.jar处理领域对象ACL实现。用来提供安全给特定的领域对象实例,在你的应用中。顶级包是org.springframework.security.acls。1.4.1.6.CAS-spring-security-cas-client.jarSpringSecurity的CAs客户端集成。如果你希望使用SpringSecurityweb认证整合一个CAS单点登录服务器。顶级包是org.springframework.security.cas。1.4.1.7.OpenID-spring-security-openid.jarOpenIDweb认证支持。用来认证用户,通过一个外部的OpenID服务。org.springframework.security.openid。需要OpenID4Java。1.4.2.获得源代码SpringSecurity是一个开源项目,我们大力推荐你从subversion获得源代码。这样你可以获得所有的示例,你可以很容易的建立目前最新的项目。获得项目的源代码对调试也有很大的帮助。异常堆栈不再是模糊的黑盒问题,你可以直接找到发生问题的那一行,查 找发生了什么额外难题。源代码也是项目的最终文档,常常是最简单的方法,找出这些事情是如何工作的。要像获得项目最新的源代码,使用如下subversion命令:svncheckouthttp://acegisecurity.svn.sourceforge.net/svnroot/acegisecurity/spring-security/trunk/你可以获得特定版本的源代码http://acegisecurity.svn.sourceforge.net/svnroot/acegisecurity/spring-security/tags/.Security命名空间配置2.1.介绍从Spring-2.0开始可以使用命名空间的配置方式。使用它呢,可以通过附加xml架构,为传统的springbeans应用环境语法做补充。你可以在spring参考文档得到更多信息。命名空间元素可以简单的配置单个bean,或使用更强大的,定义一个备用配置语法,这可以更加紧密的匹配问题域,隐藏用户背后的复杂性。简单元素可能隐藏事实,多种bean和处理步骤添加到应用环境中。比如,把下面的security命名元素添加到应用环境中,将会为测试用途,在应用内部启动一个内嵌LDAP服务器:这比配置一个Apache目录服务器bean要简单得多。最常见的替代配置需求都可以使用ldap-server元素的属性进行配置,这样用户就不用担心他们需要设置什么,不用担心bean里的各种属性。[1]。使用一个良好的XML编辑器来编辑应用环境文件,应该提供可用的属性和元素信息。我们推荐你尝试一下SpringSource工具套件因为它具有处理spring组合命名空间的特殊功能。要开始在你的应用环境里使用security命名空间,你所需要的就是把架构声明添加到你的应用环境文件里:...在许多例子里,你会看到(在示例中)应用,我们通常使用"security"作为默认的命名空间,而不是"beans",这意味着我们可以省略所有security命名空间元素的前缀,使上下文更容易阅读。如果你把应用上下文分割成单独的文件,让你的安全配置都放到其中一个文件里,这样更容易使用这种配置方法。你的安全应用上下文应该像这样开头...就在这一章里,我们都将假设使用这种语法。2.1.1.命名空间的设计命名空间被用来设计成,处理框架内最常见的功能,提供一个简化和简洁的语法,使他们在一个应用程序里。这种设计是基于框架内的大型依赖,可以分割成下面这些部分:Web/HTTP安全-最复杂的部分。设置过滤器和相关的服务bean来应用框架验证机制,保护URL,渲染登录和错误页面还有更多。业务类(方法)安全-可选的安全服务层。AuthenticationManager-通过框架的其它部分,处理认证请求。AccessDecisionManager-提供访问的决定,适用于web以及方法的安全。一个默认的主体会被注册,但是你也可以选择自定义一个,使用正常的springbean语法进行声明。AuthenticationProviders-验证管理器验证用户的机制。该命名空间提供几种标准选项,意味着使用传统语法添加自定义bean。UserDetailsService-密切相关的认证供应器,但往往也需要由其他bean需要。下一章中,我们将看到如何把这些放到一起工作。2.2.开始使用安全命名空间配置在本节中,我们来看看如何使用一些框架里的主要配置,建立一个命名空间配置。我们假设你最初想要尽快的启动运行,为已有的web应用添加认证支持和权限控制,使用一些测试登录。然后我们看一下如何修改一下,使用数据库或其他安全信息参数。在以后的章节里我们将引入更多高级的命名空间配置选项。2.2.1.配置web.xml我们要做的第一件事是把下面的filter声明添加到web.xml文件中:springSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxyspringSecurityFilterChain/*这是为SpringSecurity的web机制提供了一个调用钩子。DelegatingFilterProxy是一个SpringFramework的类,它可以代理一个applicationcontext中定义的Springbean所实现的filter。这种情况下,bean的名字是"springSecurityFilterChain",这是由命名空间创建的用于处理web安全的一个内部的机制。注意,你不应该自己使用这个bean的名字。一旦你把这个添加到你的web.xml中,你就准备好开始编辑呢的applicationcontext文件了。web安全服务是使用元素配置的。2.2.2.最小配置只需要进行如下配置就可以实现安全配置:这表示,我们要保护应用程序中的所有URL,只有拥有ROLE_USER角色的用户才能访问。 元素是所有web相关的命名空间功能的上级元素。元素定义了pattern,用来匹配进入的请求URL,使用一个ant路径语法。access属性定义了请求匹配了指定模式时的需求。使用默认的配置,这个一般是一个逗号分隔的角色队列,一个用户中的一个必须被允许访问请求。前缀“ROLE_”表示的是一个用户应该拥有的权限比对。换句话说,一个普通的基于角色的约束应该被使用。SpringSecurity中的访问控制不限于简单角色的应用(因此,我们使用不同的前缀来区分不同的安全属性).我们会在后面看到这些解释是可变的[2]Note你可以使用多个元素为不同URL的集合定义不同的访问需求,它们会被归入一个有序队列中,每次取出最先匹配的一个元素使用。所以你必须把期望使用的匹配条件放到最上边。你也可以添加一个method属性来限制匹配一个特定的HTTPmethod(GET,POST,PUT等等)。对于一个模式同时定义在定义了method和未定义method的情况,指定method的匹配会无视顺序优先被使用。要是想添加一些用户,你可以直接使用下面的命名空间直接定义一些测试数据:如果你熟悉以前的版本,你很可能已经猜到了这里是怎么回事。元素会创建一个FilterChainProxy和filter使用的bean。以前常常出现的,因为filter顺序不正确产生的问题,不会再出现了,现在这些过滤器的位置都是预定义好的。元素创建了一个DaoAuthenticationProviderbean,元素创建了一个InMemoryDaoImpl。所有authentication-provider元素必须作为的子元素,它创建了一个ProviderManager,并把authenticationprovider注册到它里面。你可以在命名空间附录中找到关于创建这个bean的更新信息。很值得去交叉检查一下这里,如果你希望开始理解框架中哪些是重要的类以及它们是如何使用的,特别是如果你希望以后做一些自定义工作。上面的配置定义了两个用户,他们在应用程序中的密码和角色(用在权限控制上)。也可以从一个标准properties文件中读取这些信息,使用user-service的properties属性。参考in-memoryauthentication获得更多信息。使用元素意味着用户信息将被认证管理用作处理认证请求。你可以拥有多个元素来定义不同的认证数据,每个会被需要时使用。现在,你可以启动程序,然后就会进入登录流程了。试试这个,或者试试工程里的“tutorial”例子。上述配置实际上把很多服务添加到了程序里,因为我们使用了auto-config属性。比如,表单登录和"remember-me"服务自动启动了。2.2.2.1.auto-config包含了什么?我们在上面用到的auto-config属性,其实是下面这些配置的缩写: 这些元素分别与form-login,基本认证和注销处理对应。[3]他们拥有各自的属性,来改变他们的具体行为。2.2.2.2.表单和基本登录选项你也许想知道,在需要登录的时候,去哪里找这个登录页面,到现在为止我们都没有提到任何的HTML或JSP文件。实际上,如果我们没有确切的指定一个页面用来登录,SpringSecurity会自动生成一个,基于可用的功能,为这个URL使用标准的数据,处理提交的登录,然后在登陆后发送到默认的目标URL。然而,命名空间提供了许多支持,让你可以自定义这些选项。比如,如果你想实现自己的登录页面,你可以使用:注意,你依旧可以使用auto-config。这个form-login元素会覆盖默认的设置。也要注意我们需要添加额外的intercept-url元素,指定用来做登录的页面的URL,这些URL应该可以被匿名访问。[4]否则,这些请求会被/**部分拦截,它没法访问到登录页面。这是一个很常见的配置错误,它会导致系统出现无限循环。SpringSecurity会在日志中发出一个警告,如果你的登录页面是被保护的。也可能让所有的请求都匹配特定的模式,通过完全的安全过滤器链:主要的是意识到这些请求会被完全忽略,对任何SpringSecurity中web相关的配置,或额外的属性,比如requires-channel,所以你会不能访问当前用户信息,或调用被保护方法,在请求过程中。使用access='IS_AUTHENTICATED_ANONYMOUSLY'作为一个选择方式如果你还想要安全过滤器链起作用。如果你希望使用基本认证,代替表单登录,可以把配置改为:基本身份认证会被优先用到,在用户尝试访问一个受保护的资源时,用来提示用户登录。在这种配置中,表单登录依然是可用的,如果你还想用的话,比如,把一个登录表单内嵌到其他页面里。2.2.2.2.1.设置一个默认的提交登陆目标如果在进行表单登陆之前,没有试图去访问一个被保护的资源,default-target-url就会起 作用。这是用户登陆后会跳转到的URL,默认是"/"。你也可以把always-use-default-target属性配置成"true",这样用户就会一直跳转到这一页(无论登陆是“跳转过来的”还是用户特定进行登陆)。如果你的系统一直需要用户从首页进入,就可以使用它了,比如:2.2.3.使用其他认证提供器现实中,你会需要更大型的用户信息源,而不是写在applicationcontext里的几个名字。多数情况下,你会想把用户信息保存到数据库或者是LDAP服务器里。LDAP命名空间会在LDAP章里详细讨论,所以我们这里不会讲它。如果你自定义了一个SpringSecurity的UserDetailsService实现,在你的applicationcontext中名叫"myUserDetailsService",然后你可以使用下面的验证。如果你想用数据库,可以使用下面的方式这里的“securityDataSource”就是DataSourcebean在applicationcontext里的名字,它指向了包含着SpringSecurity用户信息的表。另外,你可以配置一个SpringSecurityJdbcDaoImplbean,使用user-service-ref属性指定:你也可以使用标准的AuthenticationProvider类,像下面这里myAuthenticationProvider是你的applicationcontext中的一个bean的名字,它实现了AuthenticationProvider。查看Section2.6,“验证管理器和命名空间”了解更多信息,AuthenticationManager使用命名空间在SpringSecurity中是如何配置的。2.2.3.1.添加一个密码编码器 你的密码数据通常要使用一种散列算法进行编码。使用元素支持这个功能。使用SHA加密密码,原始的认证供应器配置,看起来就像这样:在使用散列密码时,用盐值防止字典攻击是个好主意,SpringSecurity也支持这个功能。理想情况下,你可能想为每个用户随机生成一个盐值,不过,你可以使用从UserDetailsService读取出来的UserDetails对象中的属性。比如,使用username属性,你可以这样用:你可以通过password-encoder的ref属性,指定一个自定义的密码编码器bean。这应该包含applicationcontext中一个bean的名字,它应该是SpringSecurity的PasswordEncoder接口的一个实例。2.3.高级web特性2.3.1.Remember-Me认证参考Remember-Me章获得remember-me命名空间配置的详细信息。2.3.2.添加HTTP/HTTPS信道安全如果你的同时支持HTTP和HTTPS协议,然后你要求特定的URL只能使用HTTPS,这时可以直接使用的requires-channel属性:...使用了这个配置以后,如果用户通过HTTP尝试访问"/secure/**"匹配的网址,他们会先被重定向到HTTPS网址下。可用的选项有"http","https"或"any"。使用"any"意味着使用HTTP或HTTPS都可以。如果你的程序使用的不是HTTP或HTTPS的标准端口,你可以用下面的方式指定端口对应关系:... 你可以在???找到更详细的讨论。2.3.3.会话管理2.3.3.1.检测超时你可以配置SpringSecurity检测失效的sessionID,并把用户转发到对应的URL。这可以通过session-management元素配置:...2.3.3.2.同步会话控制如果你希望限制单个用户只能登录到你的程序一次,SpringSecurity通过添加下面简单的部分支持这个功能。首先,你需要把下面的监听器添加到你的web.xml文件里,让SpringSecurity获得session生存周期事件:org.springframework.security.web.session.HttpSessionEventPublisher然后,在你的applicationcontext加入如下部分:...这将防止一个用户重复登录好几次-第二次登录会让第一次登录失效。通常我们更想防止第二次登录,这时候我们可以使用...第二次登录将被阻止,通过“注入”,我们的意思是用户会被转发到authentication-failure-url,如果使用了form-based登录。如果第二次验证使用了其他非内置的机制,比如“remember-me”,一个“未认证”(402)错误就会发送给客户端。如果你希望使用一个错误页面替代,你可以在session-management中添加session-authentication-error-url属性。如果你为form-based登录使用了自定义认证,你就必须特别配置同步会话控制。更多的细节可以在会话管理章节找到。2.3.3.3.防止Session固定攻击Session固定攻击是一个潜在危险,当一个恶意攻击者可以创建一个session访问一个网站的时候,然后说服另一个用户登录到同一个会话上(比如,发送给他们一个包含了session标识参数的链接)。SpringSecurity通过在用户登录时,创建一个新session来防止这个问题。如果你不需要保护,或者它与其他一些需求冲突,你可以通过使用中的session-fixation-protection属性来配置它的行为,它有三个选项migrateSession-创建一个新session,把原来session中所有属性复制到新session中。这是默认值。 none-什么也不做,继续使用原来的session。newSession-创建一个新的“干净的”session,不会复制session中的数据。2.3.4.对OpenID的支持命名空间支持OpenID登录,替代普通的表单登录,或作为一种附加功能,只需要进行简单的修改:你应该注册一个OpenID供应器(比如myopenid.com),然后把用户信息添加到你的内存中:你应该可以使用myopenid.com网站登录来进行验证了。也可能选择一个特定的UserDetailsServicebean来使用OpenID,通过设置元素。查看上一节认证提供器获得更多信息。请注意,上面用户配置中我们省略了密码属性,因为这里的用户数据只用来为数据读取数据。内部会生成一个随机密码,放置我们使用用户数据时出现问题,无论在你的配置的地方使用认证信息。2.3.4.1.属性交换支持OpenID的属性交换。作为一个例子,下面的配置会尝试从OpenID提供器中获得email和全名,这些会被应用程序使用到:每个OpenID的“type”属性是一个URI,这是由特定的schema决定的,在这个例子中是http://axschema.org/。如果一个属性必须为了成功认证而获取,可以设置required。确切的schema和对属性的支持会依赖于你使用的OpenID提供器。属性值作为认证过程的一部分返回,可以使用下面的代码在后面的过程中获得:OpenIDAuthenticationTokentoken=(OpenIDAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();Listattributes=token.getAttributes();OpenIDAttribute包含的属性类型和获取的值(或者在多属性情况下是多个值)。我们将看到更多关于SecurityContextHolder如何使用的信息,只要我们在技术概述章节浏览核心SpringSecurity组件。2.3.5.添加你自己的filter如果你以前使用过SpringSecurity,你就应该知道这个框架里维护了一个过滤器链,来提供服务。你也许想把你自己的过滤器添加到链条的特定位置,或者使用一个SpringSecurity的过滤器,这个过滤器现在还没有在命名空间配置中进行支持(比如CAS)。或者你想要使用一个特定版本的标准命名空间过滤器,比如创建的UsernamePasswordAuthenticationFilter,从而获得一些额外的配置选项的优势,这些可以通过直接配置bean获得。你如何在命名空间配置里实现这些功能呢?过滤器链现在 已经不能直接看到了。过滤器顺序在使用命名空间的时候是被严格执行的。当applicationcontext创建时,过滤器bean通过namespace的处理代码进行排序,标准的springsecurity过滤器都有自己的假名和一个容易记忆的位置。Note在之前的版本中,排序是在过滤器实例创建后执行的,在applicationcontext的执行后的过程中。在3.0+版本中,执行会在bean元元素级别被执行,在bean实例化之前。这会影响到你如何添加自己的过滤器,实体过滤器列表必须在解析元素的过程中了解这些,所以3.0中的语法变化的很明显。有关创建过滤器的过滤器,假名和命名空间元素,属性可以在Table2.1,“标准过滤器假名和顺序”中找到。过滤器按照次序排列在过滤器链中。Table2.1.标准过滤器假名和顺序假名过滤器累命名空间元素或属性CHANNEL_FILTERChannelProcessingFilterhttp/intercept-url@requires-channelCONCURRENT_SESSION_FILTERConcurrentSessionFiltersession-management/concurrency-controlSECURITY_CONTEXT_FILTERSecurityContextPersistenceFilterhttpLOGOUT_FILTERLogoutFilterhttp/logoutX509_FILTERX509AuthenticationFilterhttp/x509PRE_AUTH_FILTERAstractPreAuthenticatedProcessingFilterSubclassesN/ACAS_FILTERCasAuthenticationFilterN/AFORM_LOGIN_FILTERUsernamePasswordAuthenticationFilterhttp/form-loginBASIC_AUTH_FILTERBasicAuthenticationFilterhttp/http-basicSERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareFilterhttp/@servlet-api-provisi onREMEMBER_ME_FILTERRememberMeAuthenticationFilterhttp/remember-meANONYMOUS_FILTERSessionManagementFilterhttp/anonymousSESSION_MANAGEMENT_FILTERAnonymousAuthenticationFiltersession-managementEXCEPTION_TRANSLATION_FILTERExceptionTranslationFilterhttpFILTER_SECURITY_INTERCEPTORFilterSecurityInterceptorhttpSWITCH_USER_FILTERSwitchUserAuthenticationFilterN/A你可以把你自己的过滤器添加到队列中,使用custom-filter元素,使用这些名字中的一个,来指定你的过滤器应该出现的位置:你还可以使用after或before属性,如果你想把你的过滤器添加到队列中另一个过滤器的前面或后面。可以分别在position属性使用"FIRST"或"LAST"来指定你想让你的过滤器出现在队列元素的前面或后面。避免过滤器位置发生冲突如果你插入了一个自定义的过滤器,而这个过滤器可能与命名空间自己创建的标准过滤器放在同一个位置上,这样首要的是你不要错误包含命名空间的版本信息。避免使用auto-config属性,然后删除所有会创建你希望替换的过滤器的元素。注意,你不能替换那些元素自己使用而创建出的过滤器,比如SecurityContextPersistenceFilter,ExceptionTranslationFilter或FilterSecurityInterceptor。如果你替换了一个命名空间的过滤器,而这个过滤器需要一个验证入口点(比如,认证过程是通过一个未通过验证的用户访问受保护资源的尝试来触发的),你将也需要添加一个自定义的入口点bean。2.3.5.1.设置自定义AuthenticationEntryPoint如果你没有通过命名空间,使用表单登陆,OpenID或基本认证,你可能想定义一个认证 过滤器,并使用传统bean语法定义一个入口点然后把它链接到命名空间里,就像我们已经看到的那样。对应的AuthenticationEntryPoint可以使用元素中的entry-point-ref属性来进行设置。CAS示例程序是一个在命名空间中使用自定义bean的好例子,包含这种语法。如果你对认证入口点并不熟悉,可以在技术纵览章中找到关于它们的讨论。2.4.保护方法从版本2.0开始SpringSecurity大幅改善了对你的服务层方法添加安全。它提供对JSR-250安全注解的支持,这与框架提供的@secured注解相似。从3.0开始,你也可以使用新的基于表达式的注解。你可以提供安全给单个bean,使用intercept-methods来装饰bean的声明,或者你可以控制多个bean,通过实体服务层,使用AspectJ演示的切点功能。2.4.1.元素这个元素用来在你的应用程序中启用基于安全的注解(通过在这个元素中设置响应的属性),也可以用来声明将要应用在你的实体applicationcontext中的安全切点组。你应该只定义一个元素。下面的声明同时启用SpringSecurity的@Secured和JSR-250注解:向一个方法(或一个类或一个接口)添加注解,会限制对这个方法的访问。SpringSecurity原生注解支持为方法定义一系列属性。这些属性将传递给AccessDecisionManager,进行决策:publicinterfaceBankService{@Secured("IS_AUTHENTICATED_ANONYMOUSLY")publicAccountreadAccount(Longid);@Secured("IS_AUTHENTICATED_ANONYMOUSLY")publicAccount[]findAccounts();@Secured("ROLE_TELLER")publicAccountpost(Accountaccount,doubleamount);}为了使用新的基于表达式的预付,你可以好似用对应的代码将会是这样publicinterfaceBankService{@PreAuthorize("isAnonymous()")publicAccountreadAccount(Longid);@PreAuthorize("isAnonymous()")publicAccount[]findAccounts();@PreAuthorize("hasAuthority('ROLE_TELLER')")publicAccountpost(Accountaccount,doubleamount);}2.4.1.1.使用protect-pointcut添加安全切点protect-pointcut是非常强大的,它让你可以用简单的声明对多个bean的进行安全声明。参考下面的例子:这样会保护applicationcontext中的符合条件的bean的所有方法,这些bean要在com.mycompany包下,类名以"Service"结尾。ROLE_USER的角色才能调用这些方法。就像URL匹配一样,指定的匹配要放在切点队列的最前面,第一个匹配的表达式才会被用到。2.5.默认的AccessDecisionManager这章假设你有一些SpringSecurity权限控制有关的架构知识。如果没有,你可以跳过这段,以后再来看,因为这章只是为了自定义的用户设置的,需要在简单基于角色安全的基础上加一些客户化的东西。当你使用命名空间配置时,默认的AccessDecisionManager实例会自动注册,然后用来为方法调用和webURL访问做验证,这些都是基于你设置的intercept-url和protect-pointcut权限属性内容(和注解中的内容,如果你使用注解控制方法的权限)。默认的策略是使用一个AffirmativeBasedAccessDecisionManager,以及RoleVoter和AuthenticatedVoter。可以在authorization中获得更多信息。2.5.1.自定义AccessDecisionManager如果你需要使用一个更复杂的访问控制策略,把它设置给方法和web安全是很简单的。对于方法安全,你可以设置global-security里的access-decision-manager-ref属性,用对应AccessDecisionManagerbean在applicationcontext里的id:...web安全安全的语法也是一样,但是放在http元素里:...2.6.验证管理器和命名空间主要接口提供了验证服务在SpringSecurity中,是AuthenticationManager。通常是SpringSecurity中ProviderManager类的一个实例,如果你以前使用过框架,你可能已经很熟悉了。如果没有,它会在稍后被提及,在#tech-intro-authentication。bean实例被使用authentication-manager命名空间元素注册。你不能好似用一个自定义的AuthenticationManager如果你使用HTTp或方法安全,在命名空间中,但是它不应该是一个问题,因为你完全控制了使用的AuthenticationProvider。你可能注册额外的AuthenticationProviderbean,在ProviderManager中,你可以使用做这些事情,使用ref属性,这个属性的值,是你希望添加的provider的bean的名字,比如:... 另一个常见的需求是,上下文中的另一个bean可能需要引用AuthenticationManager。你可以为AuthenticationManager注册一个别名,然后在applicationcontext的其他地方使用这个名字。......[1]你可以在LDAP的章节里,找到更多有关使用的ldap-server的元素。[2]access中逗号分隔的值的解释依赖使用的AccessDecisionManager的实现。在SpringSecurity3.0中,这个属性也可以使用EL表达式。[3]在3.0之前按,这里列表中还包含remember-me功能。这是因为一些配置上容易冲突的问题所以在3.0中被移除了。在3.0中,AnonymousAuthenticationFilter已经成为了默认的配置的一部分,所以元素无论是否设置auto-config都会被添加到配置中。[4]参考匿名认证章节和AuthenticatedVoter类获得更多细节,和IS_AUTHENTICATED_ANONYMOUSLY如何被处理。示例程序项目中包含了很多web实例程序。为了不让下载包变得太大,我们只把"tutorial"和"contacts"两个例子放到了zip发布包里。你可以自己编译部署它们,也可以从Maven中央资源库里获得单独的war文件。我们建议你使用前一种方法。你可以按照简介里的介绍获得源代码,使用maven编译它们也很简单。如果你需要的话,可以在http://www.springsource.org/security/网站上找到更多信息。3.1.Tutorial示例这个tutorial示例是带你入门的很好的一个基础例子。它完全使用了简单命名空间配置。编译好的应用就放在zip发布包中,已经准备好发布到你的web容器中(spring-security-samples-tutorial-2.0.x.war)。使用了form-based验证机制,与常用的remember-me验证提供器相结合,自动使用cookie记录登录信息。我们推荐你从tutorial例子开始,因为XML非常小,也很容易看懂。更重要的是,你很容易就可以把这个XML文件(和它对应的web.xml入口)添加到你的程序中。只有做基本集成成功的时候,我们建议你试着添加方法验证和领域对象安全。3.2.ContactsContacts例子,是一个很高级的例子,它在基本程序安全上附加了领域对象的访问控制列表,演示了更多强大的功能。要发布它,先把SpringSecurity发布中的war文件按复制到你的容器的webapps目录下。这个war文件应该叫做spring-security-samples-contacts-2.0.0.war(后边的版本号,很大程度上依赖于你使用的发布版本)。在启动你的容器之后,检测一下程序是不是加载了,访问http://localhost:8080/contacts(或是其他你把war发布后,对应于你web容器的URL)。下一步,点击"Debug"。你将看到需要登录的提示,这页上会有一些测试用的用户名和密 码。随便使用其中的一个通过认证,就会看到结果页面。它应该会包含下面这样的一段成功信息:SecurityDebugInformationAuthenticationobjectisoftype:org.springframework.security.authentication.UsernamePasswordAuthenticationTokenAuthenticationobjectasaString:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@1f127853:Principal:org.springframework.security.core.userdetails.User@b07ed00:Username:rod;Password:[PROTECTED];Enabled:true;AccountNonExpired:true;credentialsNonExpired:true;AccountNonLocked:true;GrantedAuthorities:ROLE_SUPERVISOR,ROLE_USER;Password:[PROTECTED];Authenticated:true;Details:org.springframework.security.web.authentication.WebAuthenticationDetails@0:RemoteIpAddress:127.0.0.1;SessionId:8fkp8t83ohar;GrantedAuthorities:ROLE_SUPERVISOR,ROLE_USERAuthenticationobjectholdsthefollowinggrantedauthorities:ROLE_SUPERVISOR(getAuthority():ROLE_SUPERVISOR)ROLE_USER(getAuthority():ROLE_USER)Success!Yourwebfiltersappeartobeproperlyconfigured!一旦你成功的看到了上面的信息,就可以返回例子程序的主页,点击"Manage"了。然后你就可以尝试这个程序了。注意,只有当前登录的用户对应的联系信息会显示出来,而且只有ROLE_SUPERVISOR权限的用户可以授权删除他们的联系信息。在这场景后面,MethodSecurityInterceptor保护着业务对象。陈程序允许你修改访问控制列表,分配不同的联系方式。确保自己好好试用过,看看程序里的上下文XML文件,搞明白它是如何运行的。3.3.LDAP例子LDAP例子程序提供了一个基础配置,同时使用命名空间配置和使用传统方式bean的配置方式,这两种配置方式都写在applicationcontext文件里。这意味着,在这个程序里,其实是配置了两个定义验证提供器。3.4.CAS例子CAS示例要求你同时运行CAS服务器和CAS客户端。它没有包含在发布包里,你应该使用简介中的介绍来获得源代码。你可以在sample/cas目录下找到对应的文件。这里还有一个Readme.txt文件,解释如何从源代码树中直接运行服务器和客户端,提供完全的SSL支持。你应该下载CAS服务器web程序(一个war文件)从CAS网站,把它放到samples/cas/server目录下。3.5.Pre-Authentication例子这个例子演示了如何从pre-authentication框架绑定bean,从J2EE容器中获得有用的登录信息。用户名和角色是由容器设置的。代码在samples/preauth目录下。 SpringSecurity社区4.1.任务跟踪SpringSecurity使用JIRA管理bug报告和扩充请求。如果你发现一个bug,请使用JIRA提交一个报告。不要把它放到支持论坛上,邮件列表里,或者直接发邮件给项目开发者。这样做法是特设的,我们更愿意使用更正式的方式管理bug。如果有可能,最好为你的任务报告提供一个Junit单元测试,演示每一种不正确的行为。或者,更好的是,提供一个不定来修正这个问题。一般来说,扩充也也可以提交到任务跟踪系统里,虽然我们只接受提供了对应的单元测试的扩充请求。因为保证项目的测试覆盖率是非常必要的,它需要适当的进行维护。你可以访问任务跟踪的网址http://jira.springsource.org/browse/SEC。4.2.成为参与者我们欢迎你加入到SpringSecurity项目中来。这里有很多贡献的方式,包括在论坛上阅读别人的帖子发表回复,写新代码,提升旧代码,帮助写文档,开发例子或指南,或简单的提供建议。4.3.更多信息欢迎大家为SpringSecurity提出问题或评论。你可以使用Spring社区论坛网址http://forum.springsource.org同框架的其他用户讨论SpringSecurity。记得使用JIRA提交bug,这部分在上面提到过了。技术概述5.1.运行环境SpringSecurity3.0需要运行在Java5.0或更高版本环境上。因为SpringSecurity的目标是自己容器内管理,所以不需要为你的Java运行环境进行什么特别的配置。特别是,不需要特别配置一个JavaAuthenticationandAuthorizationService(JAAS)政策文件,也不需要把SpringSecurity放到server的classLoader下。相同的,如果你使用了一个EJB容器或者是Servlet容器,都不需要把任何特定的配置文件放到什么地方,也不需要把SpringSecurity放到server的classloader下。所有必须的文件都可以配置在你的应用中。这些设计确保了发布时的最大轻便性,你可以简单把你的目标文件(JAR或WAR或EAR)从一个系统复制到另一个系统,它会立即正常工作。5.2.核心组件在SpringSecurity3.0中,spring-security-corejar的内容已经被缩减到最小。它不再包含任何与web应用安全,LDAP或命名空间相关的代码。我们会看一下这里,看看你在核心模块中找到的Java类型。它们展示了框架的构建基础,所以如果你需要超越简单的命名空间配置,那么理解它们就是很重要的,即便你不需要直接操作他们。5.2.1.SecurityContextHolder,SecurityContext和Authentication对象最基础的对象就是SecurityContextHolder。我们把当前应用程序的当前安全环境的细节存储到它里边了,它也包含了应用当前使用的主体细节。默认情况下,SecurityContextHolder使用ThreadLocal存储这些信息,这意味着,安全环境在同一个线程执行的方法一直是有效的,即使这个安全环境没有作为一个方法参数传递到那些方法里。这种情况下使用ThreadLocal是非常安全的,只要记得在处理完当前主体的请求以后,把这个线程清除就行了。当然,SpringSecurity自动帮你管理这一切了,你就不用担心什么了。有些程序并不适合使用ThreadLocal,因为它们处理线程的特殊方法。比如,swing客户 端也许希望JVM里的所有线程都使用同一个安全环境。SecurityContextHolder可以使用一个策略进行配置在启动时,指定你想让上下文怎样被保存。对于一个单独的应用系统,你可以使用SecurityContextHolder.MODE_GLOBAL策略。其他程序可能想让一个线程创建的线程也使用相同的安全主体。这时可以使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL。想要修改默认的SecurityContextHolder.MODE_THREADLOCAL模式,可以使用两种方法。第一个是设置系统属性。另一个是调用SecurityContextHolder的静态方法。大多数程序不需要修改默认值,但是如果你需要做修改,先看一下SecurityContextHolder的JavaDoc中的详细信息。5.2.1.1.获得当前用户的信息我们把安全主体和系统交互的信息都保存在SecurityContextHolder中了。SpringSecurity使用一个Authentication对应来表现这些信息。虽然你通常不需要自己创建一个Authentication对象,但是常见的情况是,用户查询Authentication对象。你可以使用下面的代码-在你程序中的任何位置-来获得已认证用户的名字,比如:Objectprincipal=SecurityContextHolder.getContext().getAuthentication().getPrincipal();if(principalinstanceofUserDetails){Stringusername=((UserDetails)principal).getUsername();}else{Stringusername=principal.toString();}调用getContext()返回的对象是一个SecurityContext接口的实例。这个对象是保存在thread-local中的。如我们下面看到的,大多数SpringSecurity的验证机制都返回一个UserDetails的实例作为主体。5.2.2.UserDetailsService从上面的代码片段中还可以看出另一件事,就是你可以从Authentication对象中获得安全主体。这个安全主体就是一个对象。大多数情况下,可以强制转换成UserDetails对象。UserDetails是一个SpringSecurity的核心接口。它代表一个主体,是扩展的,而且是为特定程序服务的。想一下UserDetails章节,在你自己的用户数据库和如何把SpringSecurity需要的数据放到SecurityContextHolder里。为了让你自己的用户数据库起作用,我们常常把UserDetails转换成你系统提供的类,这样你就可以直接调用业务相关的方法了(比如getEmail(),getEmployeeNumber()等等)。现在,你可能想知道,我应该什么时候提供这个UserDetails对象呢?我怎么做呢?我想你说这个东西是声明式的,我不需要写任何代码,怎么办?简单的回答是,这里有一个特殊的接口,叫UserDetailsService。这个接口里的唯一一个方法,接收String类型的用户名参数,返回UserDetails:UserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException;这是获得从SpringSecurity中获得用户信息的最常用方法,你会看到它在框架中一直被用到。当需要获得一个用户的信息的时候。当成功通过验证时,UserDetails会被用来建立Authentication对象,保存在SecurityContextHolder里。(更多的信息可以参考下面的#tech-intro-authentication-mgr)。好消息是我们提供了好几个UserDetailsService实现,其中一个使用了内存中的map(InMemoryDaoImpl)另一个而是用了JDBC(JdbcDaoImpl)。虽然,大多数用户倾向于写自己的,使用这些实现常常放到已有的数据访问对象(DAO)上,表示它们的雇员,客户或其他企业应用中的用户。记住这个优势, 无论你用什么UserDetailsService返回的数据都可以通过SecurityContextHolder获得,就像上面的代码片段讲的一样。5.2.3.GrantedAuthority除了主体,另一个Authentication提供的重要方法是getAuthorities()。这个方法提供了GrantedAuthority对象数组。毫无疑问,GrantedAuthority是赋予到主体的权限。这些权限通常使用角色表示,比如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。这些角色会在后面,对web验证,方法验证和领域对象验证进行配置。SpringSecurity的其他部分用来拦截这些权限,期望他们被表现出现。GrantedAuthority对象通常使用UserDetailsService读取的。通常情况下,GrantedAuthority对象是应用程序范围下的授权。它们不会特意分配给一个特定的领域对象。因此,你不能设置一个GrantedAuthority,让它有权限展示编号54的Employee对象,因为如果有成千上网的这种授权,你会很快用光内存(或者,至少,导致程序花费大量时间去验证一个用户)。当然,SpringSecurity被明确设计成处理常见的需求,但是你最好别因为这个目的使用项目领域模型安全功能。5.2.4.小结简单回顾一下,SpringSecurity主要是由一下几部分组成的:SecurityContextHolder,提供几种访问SecurityContext的方式。SecurityContext,保存Authentication信息,和请求对应的安全信息。HttpSessionContextIntegrationFilter,为了在不同请求使用,把SecurityContext保存到HttpSession里。Authentication,展示SpringSecurity特定的主体。GrantedAuthority,反应,在应用程序范围你,赋予主体的权限。UserDetails,通过你的应用DAO,提供必要的信息,构建Authentication对象。UserDetailsService,创建一个UserDetails,传递一个String类型的用户名(或者证书ID或其他)。现在,你应该对这种重复使用的组件有一些了解了。让我们贴近看一下验证的过程。5.3.验证SpringSecurity可以用在多种不同的验证环境下。我们推荐人们使用SpringSecurity进行验证,而不是与现存的容器管理验证相结合,然而这种方式也是被支持的-作为与你自己的验证系统相整合的一种方式。5.3.1.什么是SpringSecurity的验证呢?让我们考虑一种标准的验证场景,每个人都很熟悉的那种。一个用户想使用一个账号和密码进行登陆。系统(成功的)验证了密码对于这个用户名是正确的。这个用户对应的信息呗获取(他们的角色列表以及等等)。为用户建立一个安全环境。用户会执行一些操作,这些都是潜在被权限控制机制所保护的,通过对操作的授权,使用当前的安全环境信息。前三个项目执行了验证过程,所以我们可以看一下SpringSecurity的作用。用户名和密码被获得,并进行比对,在一个UsernamePasswordAuthenticationToken的实例中(它是Authentication接口的一个实例,我们在之前已经见过了)。这个标志被发送给一个AuthenticationManager的实例进行校验。AuthenticationManager返回一个完全的Authentication实例,在成功校验后。安全环境被建立,通过调用 SecurityContextHolder.getContext().setAuthentication(...),传递到返回的验证对象中。从这一点开始,用户已经通过校验了。让我们看一些代码作为例子。importorg.springframework.security.authentication.*;importorg.springframework.security.core.*;importorg.springframework.security.core.authority.GrantedAuthorityImpl;importorg.springframework.security.core.context.SecurityContextHolder;publicclassAuthenticationExample{privatestaticAuthenticationManageram=newSampleAuthenticationManager();publicstaticvoidmain(String[]args)throwsException{BufferedReaderin=newBufferedReader(newInputStreamReader(System.in));while(true){System.out.println("Pleaseenteryourusername:");Stringname=in.readLine();System.out.println("Pleaseenteryourpassword:");Stringpassword=in.readLine();try{Authenticationrequest=newUsernamePasswordAuthenticationToken(name,password);Authenticationresult=am.authenticate(request);SecurityContextHolder.getContext().setAuthentication(result);break;}catch(AuthenticationExceptione){System.out.println("Authenticationfailed:"+e.getMessage());}}System.out.println("Successfullyauthenticated.Securitycontextcontains:"+SecurityContextHolder.getContext().getAuthentication());}}classSampleAuthenticationManagerimplementsAuthenticationManager{staticfinalListAUTHORITIES=newArrayList();static{AUTHORITIES.add(newGrantedAuthorityImpl("ROLE_USER"));}publicAuthenticationauthenticate(Authenticationauth)throwsAuthenticationException{if(auth.getName().equals(auth.getCredentials())){returnnewUsernamePasswordAuthenticationToken(auth.getName(),auth.getCredentials(),AUTHORITIES);}thrownewBadCredentialsException("BadCredentials");}}这里我们写了一些程序,询问用户输入一个用户名和密码,然后执行上面的顺序。我们实 现的AuthenticationManager会验证所有用户名和密码一样的用户。它为每个永固分配一个单独的角色。上面输出的信息将会像这样:Pleaseenteryourusername:bobPleaseenteryourpassword:passwordAuthenticationfailed:BadCredentialsPleaseenteryourusername:bobPleaseenteryourpassword:bobSuccessfullyauthenticated.Securitycontextcontains:org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230:Principal:bob;Password:[PROTECTED];Authenticated:true;Details:null;GrantedAuthorities:ROLE_USER注意,你没必要写这些代码。这些处理都是发生在内部的,比如在一个web验证过滤器中。我们只是使用了这些代码,来演示真实情况下的问题,SpringSecurity提供了一个简单的答案。一个用户被验证,当SecurityContextHolder包含了完整的Authentiation对象。5.3.2.直接设置SecurityContextHolder的内容实际上,SpringSecurity不知道你怎么把Authentication对象放到SecurityContextHolder里。唯一关键的要求是SecurityContextHolder包含了一个Authentication表示了一个主体,在AbstractSecurityInterceptor之前(我们以后会看到更多)需要验证一个用户操作。你可以(许多人都这样做)写自己的过滤器,或MVC控制器来提供验证系统,不基于SpringSecurity。比如你可能使用容器管理的验证,让当前用户有效在TheadLocal或JNDI位置。或者你可能为一个公司工作,你没有什么控制力。这种情况下,使用SpringSecurity很简单,还是提供验证功能。你需要做的是些一个过滤器(或什么设备)从一个地方读取第三方用户信息,构建一个SpringSecurity特定的Authentication对象,把它放到SecurityContextHolder里。如果你想知道AuthenticationManager是如何实现的,我们会在***看到。5.4.在web应用中验证现在让我们研究一下情景,当我们在web应用中使用SpringSecurity(不使用web.xml安全)。一个用户如何验证,安全环境如何创建?考虑一个典型的web应用的验证过程:你访问主页,点击链接。一个请求发送给服务器,服务器决定你是否在请求一个被保护的资源。如果你还没有授权,服务器发回一个相应,提示你必须登录。响应会是一个HTTP响应代码,或重定向到特定的web页面。基于验证机制,你的浏览器会重定向到特殊的web页面,所以你可以填写表单,或者浏览器会验证你的身份(通过一个BASIC验证对话框,一个cookie,一个X.509验证,等 等)。浏览器会发送回一个响应到服务器。这会是一个HTTPPOST包含你填写的表单中的内容,或者一个HTTP头部包含你的验证细节。下一步,服务器决定,当前证书是否有效。如果它们有效,下一步会执行。如果它们无效,通常你的浏览器会被询问再试一次(所以initial返回上两步)。你的原始请求会引发验证过程。希望你验证了获得了授予的权限来访问被保护的资源。如果你完允许访问,请求会成功。否则,你会收到一个HTTP错误代码403,意思是“拒绝访问”。SpringSecurity拥有不同的泪,对应很多常用的上面所说的步骤。主要的部分(使用的次序)是ExceptionTranslationFilter,一个AuthenticationEntryPoint和一个“验证机制”,对应着AuthenticationManager的调用我们在上一章见过。5.4.1.ExceptionTranslationFilterExceptionTranslationFilter是一个SpringSecurity过滤器负责检测任何一个SpringSecurity抛出的异常。这些异常会被AbstractSecurityInterceptor抛出,这是一个验证服务的主要提供器。我们会在下一章讨论AbstractSecurityInterceptor,而现在我们需要知道它产生Java异常,不知道HTTP,也不知道如何验证一个主体。对应的ExceptionTranslationFilter负责这个服务,特别负责返回错误代码403(如果主体已经通过授权,但是权限不足-像上面的第七步),或者启动一个AuthenticationEntryPoint(如果主体还没有授权,因此我们会进入上面的第三步)。5.4.2.AuthenticationEntryPointAuthenticationEntryPoint负责上面的步骤三。像你想的那样,每个web应用会有一个默认的验证策略(好,这可能像其他东西一样在SpringSecurity里配置,但现在让我们保持简单)。每个主要的验证系统会有他们自己的AuthenticationEntryPoint实现,典型的执行一个动作,描述在第三步。5.4.3.验证机制一旦你的浏览器提交了你的验证证书(像HTTP表单POST或者HTTP头)这些需要一些服务器的东西保存这些权限信息。但是现在我们进入上面的第六步。在SpringSecurity中我们有一个特定的名称,为了收集验证信息的操作。从一个用户代码中(通常是浏览器),引用它作为一个“验证机制”。例子是基于表单的登录和BASIC验证。一旦验证细节被从用户代理处收集到,一个Authentication请求对象就会被建立,然后放到AuthenticationManager。在验证机制获得完全的Authentication后,它会认为请求合法,把Authentication放到SecurityContextHolder里,然后让原始请求重试(上面第七步)。如果,其他可能,AuthenticationManager拒绝了请求,请求机制会让用户代理重试(上面第二步)。5.4.4.在请求之间保存SecurityContext。依照应用类型,这里需要一个策略,在用户操作之间保存安全环境。在一个典型的web应用中,一个用户日志,一次或顺序被它的sessionid。服务器缓存主体信息在session整个过程中,在SpringSecurity中,保存SecurityContext,从请求失败SecurityContextPersistenceFilter,默认保存到HttpSession里的一个属性,在HTTP请求之间。它重新保存环境到SecurityContextHolder,为每个请求。然后为每个请求清空SecurityContextHolder。你不应该为了安全目的,直接操作HttpSession。这里有简单的方法实现-一直使用SecurityContextHolder代替。很多其他类型的应用(比如,一个无状态的RESTweb服务)不会使用HTTP会话,会在 每次请求时,重新验证。然而,这对SecurityContextPersistenceFilter也很重要,确保包含在SecurityContextHolder中,在每次请求后清空。Note在一个单一会话接收同步请求的应用里,相同的SecurityContext实例会在线程之间共享。即使使用一个ThreadLocal,也是使用了来自HttpSession的相同实例。如果你希望暂时改变一个线程的上下文就会造成影响。如果你只是使用SecurityContextHolder.getContext().setAuthentication(anAuthentication),然后Authentication对象会反应到所有并发线程,共享相同的SecurityContext实例。你可以自定义SecurityContextPersistenceFilter的行为来创建完全新的一个线程避免影响其他的。还可以选择的是,你可以创建一个新实例,只在你暂时修改上下文的时候。这个方法SecurityContextHolder.createEmptyContext()总会返回一个新的上下文实例。5.5.SpringSecurity中的访问控制(验证)主要接口,负责访问控制的决定,在SpringSecurity中是AccessDecisionMananger。它有一个decide方法,可以获得一个Authentication对象。展示主体的请求权限,一个“secureobject”(看下边)和一个安全元数据属性队列,为对象提供了(比如一个角色列表,为访问被授予的请求)。5.5.1.安全和AOP建议如果你熟悉AOP的话,就会知道有几种不同的拦截方式:之前,之后,抛异常和环绕。其中环绕是非常有用的,因为advisor可以决定是否执行这个方法,是否修改返回的结果,是否抛出异常。SpringSecurity为方法调用提供了一个环绕advice,就像web请求一样。我们使用Spring的标准AOP支持制作了一个处理方法调用的环绕advice,我们使用标准filter建立了对web请求的环绕advice。对那些不熟悉AOP的人,需要理解的关键问题是SpringSecurity可以帮助你保护方法的调用,就像保护web请求一样。大多数人对保护服务层里的安全方法非常按兴趣。这是因为在目前这一代J2EE程序里,服务器放了更多业务相关的逻辑(需要澄清,作者不建议这种设计方法,作为替代的,而是应该使用DTO,集会,门面和透明持久模式压缩领域对象,但是使用贫血领域对象是当前的主流思路,所以我们还是会在这里讨论它)。如果你只是需要保护服务层的方法调用,Spring标准AOP平台(一般被称作AOP联盟)就够了。如果你想直接保护领域对象,你会发现AspectJ非常值得考虑。可以选择使用AspectJ还是SpringAOP处理方法验证,或者你可以选择使用filter处理web请求验证。你可以不选,选择其中一个,选择两个,或者三个都选。主流的应用是处理一些web请求验证,再结合一些在服务层里的SpringAOP方法调用验证。5.5.2.安全对象和AbstractSecurityInterceptor所以,什么是“secureobject”?SpringSecurity使用应用任何对象,可以被安全控制(比如一个验证决定)提供到它上面。最常见的例子是方法调用和web请求。SpringSecurity支持的每个安全对象类型都有它自己的类型,它们都是AbstractSecurityInterceptor的子类。很重要的是,如果主体是已经通过了验证,在AbstractSecurityInterceptor被调用的时候,SecurityContextHolder将会包含一个有效的Authentication。AbstractSecurityInterceptor提供了一套一致的工作流程,来处理对安全对象的请求,通常是:查找当前请求里分配的"配置属性"。把安全对象,当前的Authentication和配置属性,提交给AccessDecisionManager,来进行以此认证决定。 有可能在调用的过程中,对Authentication进行修改。允许安全对象进行处理(假设访问被允许了)。在调用返回的时候执行配置的AfterInvocationManager。5.5.2.1.配置属性是什么?一个"配置属性"可以看做是一个字符串,它对于AbstractSecurityInterceptor使用的类是有特殊含义的。它们通过框架中的ConfigAttribute接口表现。它们可能是简单的角色名称或拥有更复杂的含义,这就与AccessDecisionManager实现的先进程度有关了。AbstractSecurityInterceptor和配置在一起的SecurityMetadataSource用来为一个安全对象搜索属性。通常这个属性对用户是不可见的。配置属性将以注解的方式设置在受保护方法上,或者作为受保护URL的访问属性。比如,当我们查看一些像在命名空间介绍里,这就是在说这些配置属性ROLE_A和ROLE_B应用到web请求匹配到指定的模式中。实际上,使用默认的AccessDecisionManager配置,这意味着任何人拥有GrantedAuthority匹配任何这些两个属性中的一个会被允许访问。严格意义上,他们只是树形,解释是基于AccessDecisionManager实现的。前缀ROLE_的使用标记了这些属性是角色,会被SpringSecurity的RoleVoter处理。它只与基于角色的AccessDecisionManager有关。我们会在验证章节看到AccessDecisionManager是如何实现的。5.5.2.2.RunAsManager假设AccessDecisionManager决定允许执行这个请求,AbstractSecurityInterceptor会正常执行这个请求。话虽如此,罕见情况下,用户可能需要把SecurityContext的Authentication换成另一个Authentication,通过访问RunAsManager。这也许在,有原因,不常见的情况下有用,比如,服务层方法需要调用远程系统,表现不同的身份。因为SpringSecurity自动传播安全身份,从一个服务器到另一个(假设你使用了配置好的RMI或者HttpInvoker远程调用协议客户端),就可以用到它了。5.5.2.3.AfterInvocationManager按照下面安全对象执行和返回的方式-可能意味着完全的方法调用或过滤器链的执行。这种状态下AbstractSecurityInterceptor对有可能修改返回对象感兴趣。你可能想让它发生,因为验证决定不能“关于如何在”一个安全对象调用。高可插拔性,AbstractSecurityInterceptor通过控制AfterInvocationManager,实际上在需要的时候,修改对象。这里类实际上可能替换对象,或者抛出异常,或者什么也不做。AbstractSecurityInterceptor和相关对象展示在Figure5.1,“关键"secureobject"模型”中。Figure5.1.关键"secureobject"模型5.5.2.4.扩展安全对象模型只有开发者才会关心使用全心的方法,进行拦截和验证请求,将直接使用安全方法。比如,可能新建一个安全方法,控制对消息系统的权限。安全需要的任何事情,也可以提供一种拦截的方法(好像AOP的环绕advice语法那样)有可能在安全对象里处理。这样说的话,大多数Spring应用简单拥有三种当前支持的安全类型(AOP联盟的MethodInvocation,AspectJJoinPoint和web请求FilterInterceptor)完全透明的。5.6.国际化SpringSecurity支持异常信息的国际化,最终用户希望看到这些信息。如果你的应用被设计成给讲英语的用户的,你不需要做任何事情,因为默认情况下SpringSecurity的信息都是引用的。如果你需要支持其他语言。你所需要做的事情都包含在这一章节中的。所有的异常信息都支持国际化,包括验证失败和访问被拒绝的相关信息(授权失败)。应该被开发者和系统开发者关注(包括不正确的属性,接口契约,使用非法构造方法,开始时 间校验,调试级日志等等)的异常和日志没有被国际化,而是使用英语硬编码到SpringSecurity的代码中。从spring-security-core-xx.jar中,你可以找到org.springframework.security包下,包含了一些messages.properties文件,这应该引用到你的ApplicationContext中,因为SpringSecurity的类都实现了spring的MessageSourceAware接口,期待的信息处理器会在applicationcontext启动的时候注入进来。通常所有你需要做的就是在你的applicationcontext中注册一个bean来引用这些信息。下面是一个例子:messages.properties是按照标准资源束命名的,里边包括了Springsecurity所使用的默认语言的信息。默认的文件是英文的。如果你没有注册一个信息源,SpringSecurity也会正常工作,并使用硬编码的英文版本的信息。如果你想自定义messages.properties文件,或者支持其他语言,你需要复制这个文件,正确的把它重新命名,再把它注册到bean定义中。这个文件中并没有太多的信息。所以国际化应该不是很繁重的工作。如果你国际化了这个文件,请考虑一下把你的工作和社区分享,通过记录一个JIRA任务把你翻译的messages.properties版本作为一个附件发送上去。围绕国际化的讨论,spring的ThreadLocal是org.springframework.context.i18n.LocaleContextHolder。你应该把LocaleContextHolder设置成为每个用户对应的Locale。SpringSecurity会尝试从信息源中寻找信息,根据ThreadLocal中获得的Locale。请参考Spring的文档,来获得更多使用LocaleContextHolder的信息。Chapter6.核心服务现在,我们对SpringSecurity的架构和核心类有了高层次的了解了,让我们近距离看看这些核心接口和他们的实现,特别是AuthenticationManager,UserDetailsService和AccessDecisionManager。它们的信息都在这个文档的后面,所以重要的是我们要知道如何配置,如何操作。6.1.TheAuthenticationManager,ProviderManager和AuthenticationProvidersAuthenticationManager只是一个接口,所以呢,它的实现可以让我们随便选择,但是实际上它是如何工作的呢?如果我们需要检查多个授权数据库或者将不同的授权服务结合起来,比如数据库和lDAP服务器?在SpringSecurity中的默认实现是ProviderManager不只是处理授权请求自己,它委派了一系列配置好的AuthenticationProvider,每个按照顺序查看它是否可以执行验证。每个供应器会跑出一个异常,或者返回一个完整的Authentication对象。要记得我们的好朋友,UserDetails和、UserDetailsService。如果不记得了,返回到前面的章节刷新一下你的记忆。最常用的方式是验证一个授权请求读取对应的UserDetails,并检查用户录入的密码。这是通过DaoAuthenticationProvider实现的(见下面),加载的UserDetails对象-特别是包含的GrantedAuthority-会在建立Authentication时使用,这回返回一个成功验证,保存到SecurityContext中。如果你使用了命名空间,一个ProviderMananger的实例会被创建并在内部进行维护,你可以使用命名空间验证元素,或给一个bean添加一个 元素。(参考命名空间章节)。在这里,你不应该在你的applicationcontext中声明一个ProviderManagerbean。然而,如果你没有使用命名空间,你应该像下面这样进行声明:
在上面的例子中,我们有三个供应器。它们按照顺序显示(使用List实现),每个供应器能够尝试进行授权,或通过返回null跳过授权。如果所有的实现都返回null。ProviderManager会跑出一个ProviderNotFoundException异常。如果你对链状供应器感兴趣,请参考ProviderManager的javadoc。验证机制,比如表单登陆处理过滤器被注入一个ProviderManager,会被用来处理它们的认证请求。你需要的供应器有时需要被认证机制内部改变的,当在其他时候,他们会以来一个特定的认证机制,比如DaoAuthenticationProvider和LdapAuthenticationProvider可疑对应任何一个提交简单username/password的认证请求,所以可以和基于表单登陆和HTTP基本认证一起工作。其他时候,一些认证机制创建了一个认证请求对象,只可以被单个类型的AuthenticationProvider拦截。一个例子就是JA-SIGCAS,它使用一个提醒的服务票据,所以只可以被CasAuthenticationProvider认证。你不需要很了解这些,因为如果你忘记了注册合适的供应器,你会得到一个ProviderNotFoundException当这个验证尝试起作用的时候。6.1.1.DaoAuthenticationProviderspringsecurity中最简单的AuthenticationProvider实现是DaoAuthenticationProvider,这也是框架中最早支持的功能之一。它是UserDetailsService的杠杆(作为DAO),为了获得username,password和GrantedAuthority。它认证用户,通过简单比较密码,在UsernamePasswordAuthenticationToken中,和UserDetailsService中加载的信息。配置供应器十分简单:PasswordEncoder和SaltSource都是可选的,一个PasswordEncoder提供了编码和解码密码,在UserDetails对象中,被返回自配置好的UserDetailsService。一个SaltSource可以让密码使用"盐值"生成,这可以提高授权仓库中密码的安全性。更多的细节会在下面进行讨论。 6.2.UserDetailsService实现像在前面提及的一样,大多数认证供应器都是用了UserDetails和UserDetailsService接口。调用UserDetailsService中的单独的方法:UserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException;返回的UserDetails是一个接口,它提供了获得保证非空的认证信息,比如用户名,密码,授予的权限和用户账号是可用还是禁用。大多数认证供应器会使用UserDetailsService,即使username和password没有实际用在这个认证决策中。它们可以使用返回的UserDetails对象,获得它的GrantedAuthority信息,因为一些其他系统(比如LDAP或者X.509或CAS等等)了解真实验证证书的作用。这里的UserDetailsService也很简单实现,它应该为用户简单的获得认证信息,使用它们选择的持久化策略。这样说,SpringSecurity包含了很多有用的基本实现,下面我们会看到。6.2.1.内存认证创建一个自定义的UserDetailsService的实现是很容易的,可以从选择的持久化引擎中获得信息,但是许多应用没有那么复杂。尤其是如果你建立一个原型应用或只是开始集成SpringSecurity的时候,当我们不是真的需要耗费时间配置数据库或者写UserDetailsService实现。为了这些情况,一个简单的选择是使用安全命名空间中的user-service元素:也支持使用外部的属性文件:属性文件需要包含下面格式的内容username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]比如jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabledbob=bobspassword,ROLE_USER,enabled6.2.2.JdbcDaoImplSpringSecurity也包含了一个UserDetailsService,它包含从一个JDBC数据源中获得认证信息。内部使用了SpringJDBC,所以它避免了负责的功能完全的对象关系映射(ORM)只用来保存用户细节。如果你的应用使用了一个ORM工具,你应该写一个自己的UserDetailsService重用你已经创建了的映射文件。返回到JdbcDaoImpl,一个配置的例子如下所示:你可以使用不同的关系数据库管理系统,通过修改上面的DriverManagerDataSource。你也可以使用通过JNDI获得的全局数据源,使用其他的Spring配置。6.2.2.1.权限分组默认情况下,JdbcDaoImpl会假设用户的权限都保存在authorities表中。(参考数据库结构附录).还有一种选择是把权限分组,然后让用户加入这些用户组。一些人更喜欢使用这种方法来管理用户的权限。参考JdbcDaoImpl的Javadoc以获得更多的信息,了ijeruhe启用权限分组。用户组使用的数据库结构也包含在附录中。6.3.密码加密SpringSecurity的PasswordEncoder接口用来支持对密码通过一些方式进行加密,并保存到媒介中。这通常意味着密码被“散列加密”,使用一个加密算法,比如MD5或者SHA。6.3.1.什么是散列加密?密码加密不是SpringSecurity唯一的,但这对一个不了解这个概念的用户来说还是一个很容易搞混的来源。一个散列(或摘要)算法是一个单向方法提供了一小段固定长度的输出数据(散列)从一些输入数据中,比如一个密码。作为一个例子,字符串“password”的MD5散列(16进制)是5f4dcc3b5aa765d61d8327deb882cf99散列是“单向的”在这种情况下,很难(基本上不可能)根据给出的散列值获得原始输入,或是找出任何可能的输入将生成散列值。这个特点让散列值对权限方面很有用。它们可以保存在你的用户数据库中作为原始明文密码的替换,假设这些值被泄露了也无法立即盗取登录的密码。注意这也意味着你没有办法把编码后的密码还原。6.3.2.为散列加点儿盐使用密码加密的一个潜在的问题是,因为散列是单向的,如果输入是一个常用的单词的话找到输入值就相对容易很多了。比如,如果我们查找散列值5f4dcc3b5aa765d61d8327deb882cf99通过google。我们会很快找到原始词是“password”。简单的方法,一个攻击者可以建立一个散列值的字典把标准单词排列,使用它来查找原始密码。一个方法来帮助防止这种问题是使用高强度的密码策略来防止使用常用单词。另一个是在计算散列时使用“盐值”。这是一个对每个用户都知道的附加字符串,它会结合到密码中,在计算散列之前。注意这个数值应该是尽可能的随机数,但是实际中任何盐值通常都是不可取的。SpringSecurity有一个SaltSource接口,可以被验证供应器用来为特定的用户生成一个盐值。使用盐值,意味着攻击者必须创建单独的散列字典,为不同的盐值,这让攻击更难了(但不是不可能)。6.3.3.散列和认证当一个认证供应器(比如SpringSecurity的DaoAuthenticationProvider)需要检验密码,在提交认证请求中,与用户知道的数据进行比较,保存的密码通过一些方式进行了加密,然后提交的数据必须也使用相同的算法进行加密。这要求你去检查兼容性,因为SpringSecurity对持久化的没有任何控制。如果你在SpringSecurity的认证配置中添加了密码散列功能,你的数据库包含原始明文密码,那么认证就绝对不可能成功。如果你在数据库中使用MD5对密码加密,比如,你的应用配置为使用SpringSecurity的Md5PasswordEncoder,这也有其他可能的问题。数据库可能用Base64进行了加密,比如当加密器使用16进制的字符串(默认)[5]。可以选择,你的数据库可能使用了大写, 当编码器输出的是小写。确定你编写了一个测试来检测从你的密码编码器的输出,使用一个知道的密码和盐值结合检测它是否与数据库值匹配,在更深入之前,尝试通过你的系统认证。要想获得更多信息,在默认的方法从结合盐值和密码,查看BasePasswordEncoder的Javadoc。如果你希望直接通过java生成密码,为你的用户数据库保存,然后你可以使用PasswordEncoder的encodePassword方法。[5]你可以配置编码器使用Base64替换16进制,通过设置encodeHashAsBase64为true。参考MessageDigestPasswordEncoder的Javadoc和它的超类获得更多信息。PartIII.web应用安全大多数SpringSecurity的用户使用框架是基于HTTP和ServletAPI的。在这一章和以后的章节中,我们会看一下如何使用SpringSecurity提供验证和权限控制特性,用于应用的web层。我们会介绍命名空间外观的背后,看看哪些类和接口实际上用于提供web层的安全。在一些情况下,必须使用以前的bean配置来提供对配置的完全孔子和,所以我们也会看到如何直接配置这些类,不使用命名空间。安全过滤器链SpringSecurity的web架构是完全基于标准的servlet过滤器的。它没有在内部使用servlet或任何其他基于servlet的框架(比如springmvc),所以它没有与任何特定的web技术强行关联。它只管处理HttpServletRequest和HttpServletResponse,不关心请求时来自浏览器,web服务客户端,HttpInvoker还是一个AJAX应用。SpringSecurity维护了一个过滤器链,每个过滤器拥有特定的功能,过滤器需要服务也会对应添加和删除。过滤器的次序是非常重要的,它们之间都有依赖关系。如果你已经使用了命名空间配置,过滤器会自动帮你配置,你不需要定义任何SpringBean,但是有时候你需要完全控制Spring过滤器链,因为你使用了命名空间没有提供的特性,或者你需要使用你自己自定义的类。7.1.DelegatingFilterProxy当使用servlet过滤器时,你很需要在你的web.xml中声明它们,它们可能被servlet容器忽略。在SpringSecurity,过滤器类也是定义在xml中的springbean,因此可以获得Spring的依赖注入机制和生命周期接口。spring的DelegatingFilterProxy提供了在web.xml和applicationcontext之间的联系。当使用DelegatingFilterProxy,你会看到像web.xml文件中的这样内容:myFilterorg.springframework.web.filter.DelegatingFilterProxymyFilter/*注意这个过滤器其实是一个DelegatingFilterProxy,这个过滤器里没有实现过滤器的任何逻辑。DelegatingFilterProxy做的事情是代理Filter的方法,从applicationcontext里获得bean。这让bean可以获得springwebapplicationcontext的生命周期支持,使配置较为轻便。bean必须实现javax.servlet.Filter接口,它必须和filter-name里定义的名称是一样的。查看DelegatingFilterProxy的javadoc获得更多信息。7.2.FilterChainProxy现在应该清楚了,你可以声明每个SpringSecurity过滤器bean,你在application context中需要的。把一个DelegatingFilterProxy入口添加到web.xml,确认它们的次序是正确的。这是一种繁琐的方式,会让web.xml显得十分杂乱,如果我们配置了太多过滤器的话。我们最好添加一个单独的入口,在web.xml中,然后在applicationcontext中处理实体,管理我们的web安全bean。这就是FilterChainProxy所做的事情。它使用DelegatingFilterProxy(就像上面例子中那样),但是对应的class是org.springframework.security.web.FilterChainProxy。过滤器链是在applicationcontext中声明的。这里有一个例子:你可能注意到FilterSecurityInterceptor声明的不同方式。命名空间元素filter-chain-map被用来设置安全过滤器链。它映射一个特定的URL模式,到过滤器链中,从bean名称来定义的filters元素。它同时支持正则表达式和ant路径,并且只使用第一个出现的匹配URI。在运行阶段FilterChainProxy会定位当前web请求匹配的第一个URI模式,由filters属性指定的过滤器bean列表将开始处理请求。过滤器会按照定义的顺序依次执行,所以你可以对处理特定URL的过滤器链进行完全的控制。你可能注意到了,我们在过滤器链里声明了两个SecurityContextPersistenceFilter(ASC是allowSessionCreation的简写,是SecurityContextPersistenceFilter的一个属性)。因为web服务从来不会在请求里带上jsessionid,为每个用户代理都创建一个HttpSession完全是一种浪费。如果你需要构建一个高等级最高可扩展性的系统,我们推荐你使用上面的配置方法。对于小一点儿的项目,使用一个HttpSessionContextIntegrationFilter(让它的allowSessionCreation默认为true)就足够了。在有关声明周期的问题上,如果这些方法被FilterChainProxy自己调用,FilterChainProxy会始终根据下一层的Filter代理init(FilterConfig)和destroy()方法。这时,FilterChainProxy会保证初始化和销毁操作只会在Filter上调用一次,而不管它在过滤器链中被声明了多少次)。你控制着所有的抉择,比如这些方法是否被调用或targetFilterLifecycle初始化参数DelegatingFilterProxy。默认情况下,这个参数是false,servlet容器生命周期调用不会传播到DelegatingFilterProxy。当我们了解如何使用命名控制配置构建web安全。我们使用一个DelegatingFilterProxy,它的名字是“springSecurityFilterChain”。你应该现在可以看到FilterChainProxy的名字,它是由命名空间创建的。7.2.1.绕过过滤器链 通过命名空间,你可以使用filters="none",来提供一个过滤器bean列表。这会朝向请求模式,使用安全过滤器链整体。注意任何匹配这个模式的路径不会有任何授权或校验的服务起作用,它们是可以自由访问的。7.3.过滤器顺序定义在web.xml里的过滤器的顺序是非常重要的。不论你实际使用的是哪个过滤器,的顺序应该像下面这样:ChannelProcessingFilter,因为它可能需要重定向到其他协议。ConcurrentSessionFilter,因为它不使用SecurityContextHolder功能,但是需要更新SessionRegistry来从主体中放映正在进行的请求。SecurityContextPersistenceFilter,这样SecurityContext可以在web请求的开始阶段通过SecurityContextHolder建立,然后SecurityContext的任何修改都会在web请求结束的时候(为下一个web请求做准备)复制到HttpSession中。验证执行机制-UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等等-这样SecurityContextHolder可以被修改,并包含一个合法的Authentication请求标志。SecurityContextHolderAwareRequestFilter,如果,你使用它,把一个SpringSecurity提醒HttpServletRequestWrapper安装到你的servlet容器里。RememberMeAuthenticationFilter,这样如果之前的验证执行机制没有更新SecurityContextHolder,这个请求提供了一个可以使用的remember-me服务的cookie,一个对应的已保存的Authentication对象会被创建出来。AnonymousAuthenticationFilter,这样如果之前的验证执行机制没有更新SecurityContextHolder,会创建一个匿名Authentication对象。ExceptionTranslationFilter,用来捕捉SpringSecurity异常,这样,可能返回一个HTTP错误响应,或者执行一个对应的AuthenticationEntryPoint。FilterSecurityInterceptor,保护webURI。7.4.使用其他过滤器——基于框架如果你在使用SiteMesh,确认SpringSecurity过滤器在SiteMesh过滤器之前调用。这可以保证SecurityContextHolder为每个SiteMesh渲染器及时创建。Chapter8.核心安全过滤器这儿有几个在web应用中一直会用到的SpringSecurity关键过滤器,所以我们回来看看它们,和支持的类和接口。我们不会覆盖所有功能,所以确认参考它们的javadoc,如果你想要获得完全的信息。8.1.FilterSecurityInterceptor我们已经简要了解了FilterSecurityInterceptor,在简要讨论访问控制的时候(见#tech-intro-access-control),我们也已经在命名空间中使用过它,元素的结合在内部对它进行了配置。现在我们会看如何精确的配置它,使用FilterChainProxy,通过结合ExceptionTranslationFilter过滤器。一个典型的配置例子如下: FilterSecurityInterceptor负责处理HTTP资源的安全。它需要一个AuthenticationManager和AccessDecisionManager的引用。它也需要不同HTTPURL请求的配置属性。引用回#tech-intro-config-attributes这里可以看到原始信息。FilterSecurityInterceptor可是通过两种方式定义配置属性。第一种,向上面演示的,使用命名空间元素。这和用来配置FilterChainProxy的一样,但是使用的是子元素,只使用pattern和access属性。逗号用来分隔不同的配置属性,对于每个HTTPURL。第二个选择是编写你自己的SecurityMetadataSource,但是这超越了我们的文档的范围。根据使用的方式,SecurityMetadataSource负责返回一个包含了所有配置属性的List,它分配给一个单独的安全HTTPURL。应该注意的是FilterSecurityInterceptor.setSecurityMetadataSource()方法其实需要一个FilterSecurityMetadataSource实例。这是一个标记接口,它是SecurityMetadataSource的子类。它只标记了SecurityMetadataSource需要FilterInvocation。对于相似感兴趣,我们会继续引用FilterInvocationDefinitionSource作为一个SecurityMetadataSource,作为区别大多数用户的微笑相关不同。SecurityMetadataSource通过命名空间获得配置属性,为一个特定的FilterInvocation,通过匹配请求URL,对于配置好的pattern属性。这个行为和它在命名空间中配置的一样。默认使用apacheantpath的方式来处理所有表达式,也支持正则表达式来支持更复杂的请求。这个path-type属性用来指定模式使用的类型。它不可能在同一个定义中使用多个表达式语法的复合结构。作为一个例子,上面的配置使用正则表达式替换了antpath,可以写成下面这样:模式总是根据他们定义的顺序进行执行。因此很重要的是,把更确定的模式定义到列表的上面。这会反映在你上面的例子中,更确定的/secure/super/模式放在,没那么确定的/secure/模式的上面。如果它们被反转了。/secure/会一直被匹配,/secure/super/就永远也不会执行。 8.2.ExceptionTranslationFilterExceptionTranslationFilter处在FilterSecurityInterceptor的上面。它不执行任何真正的安全控制,但是处理安全监听器抛出的一场,提供对应的HTTP响应。8.2.1.AuthenticationEntryPointAuthenticationEntryPoint会被调用,在用户请求一个安全HTTP资源,但是他们还没有被认证。一个对应的AuthenticationException或AccessDeniedException会被抛出,由一个安全监听器在下面的调用栈中,触发入口点的commence方法。这执行展示对应的响应给用户,这样认证可以开始。我们这里使用的是LoginUrlAuthenticationEntryPoint,它会把请求重定向到另一个不同的URL(一般是一个登陆页面)。实际的使用使用会依赖你希望使用到的认证机制。8.2.2.AccessDeniedHandler如果一个用户已经认证了,他再去访问一个被保护的资源时会怎么样呢?正常情况下,这不会发生,因为应用工作流应该限制哪些资源用户可以访问。比如一个HTML连接到管理员页面,可能对没有管理权限的用户隐藏。你不能对一个隐藏的链接点击,因为安全的原因,这总有可能用户直接输入了URL尝试越过限制。或者他们可能修改了RESTfulURL来改变一些参数的值。你的应用必须保护这些场景,或者它们会变的不安全。你将使用简单的web层安全在你的服务层接口上,来确认哪些是被允许的。如果一个AccessDeniedException被抛出了,一个用户已经被认证,然后这意味着操作已经被尝试了,而他们没有足够的权限,这种情况下,ExceptionTranslationFilter会调用第二个策略,AccessDeniedHandler。默认下,一个AccessDeniedHandlerImpl会被使用,它只发送一个403(拒绝访问)响应到客户端。可选的,你可以确切配置一个实例(像上面的例子)然后设置一个错误页URL它会把请求跳转到错误页。[6]。这可能是一个简单的“accessdenied”页面,比如一个JSP,或者它可能是一个更加复杂的处理器,比如一个MVC控制器。当然,我们可以实现自己的接口,使用你自己的实现。也可能提供一个自定义的AccessDeniedHandler然后使用命名空间配置你的应用,参考#nsa-access-denied-handler.8.3.SecurityContextPersistenceFilter我们介绍了所有重要的过滤器,在技术概述一章,所以你可能希望重新阅读以下这些章节。让我们首先看一下如何使用FilterChainProxy配置它们。一个基本的配置需要bean自己像我们之前看到的,这个过滤器有两个主要的任务,它负责保存在不同的HTTP请求之间SecurityContext,负责清理在请求完成时SecurityContextHolder。清理上下文中保存的ThreadLocal是很基本的,因为它可能是servlet容器线程池中的一个替换的thread,这样springsecurity对应一个特定用户可能出现冲突。这个线程可能被下一个阶段使用,执行操作的时候就会使用错误的证书。8.3.1.SecurityContextRepository对于SpringSecurity3.0,读取和保存安全上下文的任务被委托给一个单独的策略接口:publicinterfaceSecurityContextRepository{SecurityContextloadContext(HttpRequestResponseHolderrequestResponseHolder);voidsaveContext(SecurityContextcontext,HttpServletRequestrequest,HttpServletResponseresponse);}HttpRequestResponseHolder是一个简单的容器,为了进入的请求和相应对象,允许使用封装类替换它们。返回的内容会被发送给过滤器链。默认的实现是HttpSessionSecurityContextRepository,它会把安全上下文保存为一个HttpSession的属性。[7].这个实现中最重要的配置参数是allowSessionCreation属性,默认是true,因此允许类创建会话,如果它需要保存springcontext为一个认证用户(它不会创建一个除非认证已经执行,securitycontext的内容发生改变)。如果你不想要创建会话,你可以把这个属性设置为false:可选的是,你可以提供一个SecurityContextRepository接口的null实现,这就可以防止安全上下文被保存,即使一个session已经在请求期间被创建了。8.4.UsernamePasswordAuthenticationFilter我现在看到了三个主要的过滤器,它们会一直在springsecurity的web配置中起作用。它们也是由命名空间元素自动创建的三个,它们是无法修改的。唯一丢失的是一个真正的认证机制,它们会执行一个用户的认证。这个过滤器是最常用的认证过滤器,这个过滤器也通常需要自定义。[8].它也提供了使用元素的实现,来自命名空间。有三个阶段需要配置它。配置一个LoginUrlAuthenticationEntryPoint使用一个登陆页URL,就像我们上面那样,把它配置到ExceptionTranslationFilter中。实现一个登陆页面(使用JSP或MVC控制器)。配置一个UsernamePasswordAuthenticationFilter的实例,放在applicationcontext中。把过滤器bean添加到你的过滤器链代理中(确认你注意了顺序)。登陆表单简单包含了j_username和j_password输入框,提交由过滤器管理的URL, (默认为:/j_spring_security_check).基本的过滤器配置看起来像这样:8.4.1.认证成功和失败的应用流程过滤器调用了配置的AuthenticationManager处理每个认证请求。认证成功或失败的目的地是由AuthenticationSuccessHandler和AuthenticationFailureHandler策略接口各自控制的。过滤器的属性允许我们设置这些,这样你可以随心所欲的自定义他们的行为。[9].我们提供了一些标准实现,比如SimpleUrlAuthenticationSuccessHandler,SavedRequestAwareAuthenticationSuccessHandler,SimpleUrlAuthenticationFailureHandler和ExceptionMappingAuthenticationFailureHandler。参考这些类的javadoc,了解他们是如何工作的。如果认证成功,结果Authentication对象会被放到SecurityContextHolder中。配置的AuthenticationSuccessHandler会被调用,进行重定向会把用户跳转到合适的目的地。默认情况,会使用SavedRequestAwareAuthenticationSuccessHandler。这意味着,用户会被重定向到原始的目标,他们在登录之前请求的页面。NoteExceptionTranslationFilter缓存了一个用户的原始请求。当用户认证时,请求处理器从这个缓存的请求中获得原始的URL,并重定向到它。原始请求然后重新构造,作为一个可选项使用。如果认证失败,配置好的AuthenticationFailureHandler会被调用。[6]我们使用forward,这样SecurityContextHolder会包含主体的信息,这可能对现实用户信息很有帮助。在老版本的SpringSecurity中,我们让servlet容器处理一个403错误信息,这可能丢失了有用的上下文信息。[7]在springsecurity2.0以及以前,这个过滤器叫做HttpSessionContextIntegrationFilter,它会执行保存上下文的所有工作。如果你对这个类很熟悉,然后大多数的配置属性现在都可以在HttpSessionSecurityContextRepository中找到。[8]因为一些历史原因,在SpringSecurity3.0之前,这个过滤器被称为AuthenticationProcessingFilter,入口点被称为AuthenticationProcessingFilterEntryPoint。因为这个框架现在支持了很多不同的认证表单,它们都需要在3.0中给与更确切的名字。[9]在版本3.0之前,这一点的应用流程被当做一个状态,通过这个类的一系列属性和策略插件进行处理。这个决定让3.0重构了代码,让两个策略完全负责。Basic(基本)和Digest(摘要)验证Basic(基本)和Digest(摘要)验证都是web应用中很受欢迎的可选机制。Basic验证一般用来处理无状态的客户端,它们在每次请求都附带它们的证书。很常见的用法是把它和基于表单的验证一起使用,这里的应用会同时使用基于浏览器的用户接口和web服务。然而,basic验证使用原文传送密码,所以应该只通过加密的传输途径发送,比如HTTPS。9.1.BasicAuthenticationFilterBasicAuthenticationFilter负责处理通过HTTP头部发送来的basic验证证书。它可以 用来像对待普通用户代理一样(比如IE和Navigator)认证由Spring远程协议的调用(比如Hessian和Burlap)。HTTP基本认证的执行标准定义在RFC1945,11章,BasicAuthenticationFilter符合这个RFC。基本认证是一个极具吸引力的认证方法,因为它在用户代理发布很广泛,实现也特别简单(只需要对username:password进行Base64编码,再放到HTTP头部里)。9.1.1.配置要实现HTTP基本认证,要先在过滤器链里定义BasicAuthenticationFilter。还要在applicationcontext里定义BasicAuthenticationFilter和协作的类:配置好的AuthenticationManager会处理每个认证请求。如果认证失败,配置好的AuthenticationEntryPoint会用来重试认证过程。通常你会使用BasicAuthenticationEntryPoint,它会返回一个401响应,使用对应的头部重试HTTP基本验证。如果验证成功,就把得到的Authentication对象放到SecurityContextHolder里。如果认证事件成功,或者因为HTTP头部没有包含支持的认证请求所以没有进行认证,过滤器链会像通常一样继续下去。唯一打断过滤器的情况是在认证失败并调用AuthenticationEntryPoint的时候,向上面段落里讨论的那样。9.2.DigestAuthenticationFilterSpringSecurity提供了一个DigestAuthenticationFilter,它可以处理HTTP头部中的摘要认证证书。摘要认证在尝试着解决许多基本认证的缺陷,特别是保证不会通过纯文本发送证书。许多用户支持摘要式认证,包括FireFox和IE。HTTP摘要式认证的执行标准定义在RFC2617,它是对RFC2069这个早期摘要式认证标准的更新。SpringSecurityDigestAuthenticationFilter会保证"auth"的安全质量(qop),它订明在RFC2617中,并与RFC2069提供了兼容。如果你需要使用没有加密的HTTP(比如没有TLS/HTTP),还希望认证达到最大的安全性的时候,摘要式认证便具有很高吸引力。事实上,摘要式认证是WebDAV协议的强制性要求,写在RFC2518的17.1章,所以我们应该期望看到更多的代替基本认证。摘要式认证,是表单认证,基本认证和摘要式认证中最安全的选择,不过更安全也意味着更复杂的用户代理实现。摘要式认证的中心是一个“nonce”。这是由服务器生成的一个值。SpringSecurity的nonce采用下面的格式:base64(expirationTime+":"+md5Hex(expirationTime+":"+key))expirationTime:Thedateandtimewhenthenonceexpires,expressedinmillisecondskey:Aprivatekeytopreventmodificationofthenoncetoken这个DigestAuthenticationEntryPoint有一个属性,通过指定一个key来生成nonce标 志,通过nonceValiditySeconds属性来决定过期时间(默认300,等于5分钟)。只要nonce是有效的,摘要就会通过串联字符串计算出来,包括用户名,密码,nonce,请求的URI,一个客户端生成nonce(仅仅是一个随机值,用户代理每个请求生成一个),realm名称等等,然后执行一次MD5散列。服务器和用户代理都要执行这个摘要计算,如果他们包含的值不同(比如密码),就会生成不同的散列码。在SpringSecurity的实现中,如果服务器生成的nonce已经过期(但是摘要还是有效),DigestAuthenticationEntryPoint会发送一个"stale=true"头信息。这告诉用户代理,这里不再需要打扰用户(像是密码和用户其他都是正确的),只是简单尝试使用一个新nonce。DigestAuthenticationEntryPoint的nonceValiditySeconds参数,会作为一个适当的值依附在你的程序上。对安全要求很高的用户应该注意,一个被拦截的认证头部可以用来假冒主体,直到nonce达到expirationTime。在选择合适的配置的时候,这是一个必须考虑到的关键性条件,但是在对安全性要求很高的程序里,第一次请求都会首先运行在TLS/HTTPS之上。因为摘要式认证需要更复杂的实现,这里常常有用户代理的问题。比如,IE不能在同一个会话的请求进程里阻止"透明"标志。因此SpringSecurity把所有状态信息都概括到"nonce"标记里。在我们的测试中,SpringSecurity在FireFox和IE里都可以工作,正确的处理nonce超时等等。9.2.1.Configuration现在我们重新看一下理论,让我们看看如何使用它。为了实现HTTP摘要认证,必须在过滤器链里定义DigestAuthenticationFilter。applicationcontext还需要定义DigestAuthenticationFilter和它需要的合作伙伴:需要配置一个UserDetailsService,因为DigestAuthenticationFilter必须直接访问用户的纯文本密码。如果你在DAO中使用编码过的密码,摘要式认证就没法工作。DAO合作者,与UserCache一起,通常使用DaoAuthenticationProvider直接共享。这个AuthenticationEntryPoint属性必须是DigestAuthenticationEntryPoint,这样DigestAuthenticationFilter可以在进行摘要计算时获得正确的realmName和key。像BasicAuthenticationFilter一样,如果认证成功,会把Authentication请求标记放到SecurityContextHolder中。如果认证事件成功,或者认证不需要执行,因为HTTP头部没有包含摘要认证请求,过滤器链会正常继续。过滤器链中断的唯一情况是,如果认证失败,就会像上面讨论的那样调用AuthenticationEntryPoint。摘要式认证的RFC要求附加功能范围,来更好的提升安全性。比如,nonce可以在每次请求的时候变换。但是,SpringSecurity的设计思路是最小复杂性的实现(毫无疑问, 用户代理会出现不兼容),也避免保存服务器端的状态。如果你想研究这些功能的更多细节,我们推荐你看一下RFC2617。像我们知道的那样,SpringSecurity实现类遵守了RFC的最低标准。Remember-Me认证10.1.概述记住我(remember-me)或持久登录(persistent-login)认证,指的是网站可以在不同会话之间记忆验证的身份。通常情况是发送一个cookie给浏览器,在以后的session里检测cookie,进行自动登录。SpringSecurity为remember-me实现提供了必要的调用钩子,并提供了两个remember-me的具体实现。其中一个使用散列来保护基于cookie标记的安全性,另一个使用了数据库或其他持久化存储机制来保存生成的标记。注意,两个实现方式,都需要UserDetailsService。如果你使用了认证提供器,没有使用UserDetailsService(比如LDAP供应器),那它就没法工作,除非你在applicationcontext里设置了一个UserDetailsService。10.2.简单基于散列标记的方法这种方法使用散列来完成remember-me策略。本质上,在成功进行认证的之后,把一个cookie发送给浏览器,使用的cookie组成结构如下:base64(username+":"+expirationTime+":"+md5Hex(username+":"+expirationTime+":"password+":"+key))username:AsidentifiabletotheUserDetailsServicepassword:ThatmatchestheoneintheretrievedUserDetailsexpirationTime:Thedateandtimewhentheremember-metokenexpires,expressedinmillisecondskey:Aprivatekeytopreventmodificationoftheremember-metoken这个remember-me标记只适用于指定范围,提供用户名,密码和关键字都不会改变。值得注意,这里有一个潜在的安全问题,来自任何一个用户代理的remember-me标记,直到标记过期都是可用的。这个问题和摘要式认证相同。如果一个用户发现标记已经设置了,他们可以轻易修改他们的密码,并且立即注销所有的remember-me标记。如果需要更好的安全性,你应该使用下一章描述的方法。或者不应该使用remember-me服务。如果你还记得在命名空间配置中讨论的主题,你只要添加元素就可以使用remember-me认证:...这个UserDetailsService会自动选上。如果你在applicationcontext中配置了多个,你需要使用user-service-ref属性指定应该使用哪一个,这里的值要放上你的UserDetailsServicebean的名字。10.3.持久化标记方法这个方法是基于这篇文章http://jaspan.com/improved_persistent_login_cookie_best_practice进行了一些小修改[10]。要用在命名空间配置里使用这个方法,你应该提供一个datasource引用:... 数据应该包含一个persistent_logins表,可以使用下面的SQl创建(或等价物):createtablepersistent_logins(usernamevarchar(64)notnull,seriesvarchar(64)primarykey,tokenvarchar(64)notnull,last_usedtimestampnotnull)10.4.Remember-Me接口和实现Remember-me认证不能和基本认证一起使用,因为基本认证往往不使用HttpSession。Remember-me使用在UsernamePasswordAuthenticationFilter中,通过在它的超类AbstractAuthenticationProcessingFilter里实现的一个调用钩子。这个钩子会在合适的时候调用一个具体的RememberMeServices。这个接口看起来像这样:AuthenticationautoLogin(HttpServletRequestrequest,HttpServletResponseresponse);voidloginFail(HttpServletRequestrequest,HttpServletResponseresponse);voidloginSuccess(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationsuccessfulAuthentication);请参考JavaDocs获得有关这些方法的完整讨论,不过注意在这里,AbstractAuthenticationProcessingFilter只调用loginFail()和loginSuccess()方法。当SecurityContextHolder没有包含Authentication的时候,RememberMeProcessingFilter才去调用autoLogin()。因此,这个接口通过使用完整的认证相关事件的提醒提供了下面remember-me实现,然后在可能包含一个cookie希望被记得的申请web请求中调用这个实现。这个设计允许任何数目的remember-me实现策略。我们在下面看看上面介绍过的两个SpringSecurity提供的实现。10.4.1.TokenBasedRememberMeServices这个实现支持在Section10.2,“简单基于散列标记的方法”里描述的简单方法。TokenBasedRememberMeServices被RememberMeAuthenticationProvider执行的时候生成一个RememberMeAuthenticationToken。认证提供器和TokenBasedRememberMeServices之间共享一个key。另外TokenBasedRememberMeServices需要一个UserDetailsService,用它来获得用户名和密码,进行比较,然后生成RememberMeAuthenticationToken来包含正确的GrantedAuthority[]。如果用户请求注销,让cookie失效,就应该使用系统提供的一系列注销命令。TokenBasedRememberMeServices也实现SpringSecurity的LogoutHandler接口,这样可以使用LogoutFilter自动清除cookie。这些bean要求在applicationcontext里启用remember-me服务,像下面一样: 不要忘记把你的RememberMeServices实现添加到UsernamePasswordAuthenticationFilter.setRememberMeServices()属性中,包括把RememberMeAuthenticationProvider添加到你的UsernamePasswordAuthenticationFilter.setProviders()队列中,把RememberMeProcessingFilter添加到你的FilterChainProxy中(要放到AuthenticationProcessingFilter后面)。10.4.2.PersistentTokenBasedRememberMeServices这个类可以像TokenBasedRememberMeServices一样使用,但是它还需要配置一个PersistentTokenRepository来保存标记。这里有两个标准实现。InMemoryTokenRepositoryImpl最好是只用来测试。JdbcTokenRepositoryImpl把标记保存到数据库里。数据库表结构在Section10.3,“持久化标记方法”.[10]基本上,为了防止暴露有效登录名,用户名没有包含在cookie里。在这个文章的评论里有一个相关的讨论。会话管理HTTP会话相关的功能是由SessionManagementFilter和SessionAuthenticationStrategy接口联合处理的,过滤器会代理它们。典型的应用包括会话伪造攻击预防,检测会话超时,限制已登录用户可以同时打开多少会话。11.1.SessionManagementFilterSessionManagementFilter会检测SecurityContextRepository的内容,比较当前SecurityContextHolder,决定用户在当前请求中是否已经登录,通常被一个非内部认证机制,比如预验证或remember-me[11]如果资源库中包含一个安全上下文,过滤器什么也不会做。如果没有包含,thread-local中SecurityContext包含一个(非匿名)Authentication对象,过滤器就会假设他们已经在过滤器栈中的前一个过滤器中被认证过了。它会调用配置好的SessionAuthenticationStrategy。如果用户当前没有认证,过滤器会检测是否有无效的sessionID被请求了(比如,因为超时)并会重定向到配置好的invalidSessionUrl,如果设置了。最简单的配置方法是通过命名空间,如前面描述的。11.2.SessionAuthenticationStrategySessionAuthenticationStrategy被SessionManagementFilter和AbstractAuthenticationProcessingFilter都是用了,所以如果你使用了一个自定义的formlogin类,比如,你需要把它注入到两者中。比如,在典型配种,结合命名空间和自定义bean看起来像这样: 11.3.同步会话SpringSecurity可以防止一个主体同时被一个相同的应用授权多于一个特定的次数。许多ISV可以利用这一点来加强协议,网络管理员就很喜欢这点功能,因为它可以防止人们共享登陆账号。你可以,比如,停止在两个不同的会话中的登陆web应用的用户“Batman”。你可以选择让上一次登录过期,或者当他们尝试重复登录时报告一个错误,防止第二次登录。注意,如果你使用第二种方式,一个用户如果没有使用注销(而是仅仅关闭了他们的浏览器,比如)就不能再次登陆了,直到他们之前的会话失效之前。这个功能在命名空间中已经支持了,所以请参考前面介绍命名空间的章节,来了解简便的配置方式。有时你需要做些一次自定义的工作。实现使用了特定版本的SessionAuthenticationStrategy,称作ConcurrentSessionControlStrategy。Note之前的验证检测是通过ProviderManager实现的,可能是被注入了一个ConcurrentSessionController,它可以检测用户是否视图超过最大会话限制的允许数量。然而,这种方式要求预先创建一个HTTP会话,这是不合理的。在SpringSecurity3中,用户首先被AuthenticationManager验证一旦认证成功,就会创建一个会话,并进行检测,是否允许另一个会话打开。如果要使用concurerntsession支持,你需要向web.xml添加如下内容:org.springframework.security.web.session.HttpSessionEventPublisher另外,你需要把org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter添加到你的FilterChainProxy中。ConcurrentSessionFilter需要两个属性,sessionRegistry,通常是一个SessionRegistryImpl的实例。和expiredUrl,这指向一个页面,当会话过期的时候就会显示它。一个配置在命名空间中会创建FilterChainProxy和其他bean可能看起来就像这样: ...在web.xml添加的监听器,会触发一个ApplicationEvent发布到SpringApplicationContext中,每当一个HttpSession生成或销毁时。这是非常重要,它允许SessionRegistryImpl在会话结束时被提醒。没有它的话,用户就永远不能再登陆到系统中了,一旦他们超过了他们允许的绘画最大量,即使他们注销了另外的会话,或会话超时了。[11]在那些会执行重定向的认证机制中(比如form-based),无法使用SessionManagementFilter检测,因为过滤器不会在验证请求过程中被调用。会话管理功能必须在这些情况下单独处理。匿名认证12.1.概述通常考虑的一个好的安全事件是采取“deny-by-default”(默认拒绝所有请求的方式)这种情况下,你可以明确指定允许哪些,然后拒绝其他所有操作。定义未认证用户可以做些什么,也是一种简单情况,特别是对于web应用。多数网站要求用户必须通过一些途径进行认证,不仅仅是一些URL(比如,首页和登陆页面)。在这种情况下,更简单的定义访问配置属性,为这些特殊的URL,而不是把每个资源都当做被保护的资源。不同的是,有时它最好被成为ROLE_SOMETHING,默认是这样要求的,对于这种规定,只有几个例外,比如,登陆,注销,应用的首页。你也可以从过滤器链中完全忽略他们,这样就可以通过安全控制的检测,但是,对于其他原因这些不好的,特别是,这些页面的行为与已认证用户不同。这就是为什么我们使用匿名认证的原因,注意这里没有一个真正的理论来区分“匿名认证”的用户和未认证用户。springsecurity匿名认证只是给你一个更方便的方式来配置你的 权限控制属性,调用servletAPI,比如getCallerPrincipal比如,也会返回null,即使这里已经有一个匿名认证对象在SecurityContextHolder里的。这里有一些其他匿名认证发挥作用的请抗,比如当一个审计拦截器查询SecurityContextHolder来验证哪些主体用来处理给定的操作。类可以更好的工作,如果它们知道SecurityContextHolder里总是包含一个Authentication对象,永远不会是null。12.2.配置当使用springsecurity3.0的HTTP配置时,就自动提供了对匿名认证的支持。可以使用元素进行自定义(或禁用)。你不需要在这里配置bean,除非使用了以前的bean配置方式。SpringSecurity提供三个类来一起提供匿名认证功能。AnonymousAuthenticationToken实现了Authentication,保存着GrantedAuthority[],用来处理匿名主体。有一个对应的需要链入ProviderManager的AnonymousAuthenticationProvider,可以从中获得AnonymousAuthenticationTokens。最后是AnonymousAuthenticationFilter,需要串链到普通认证机制后面,如果还没有存在的Authentication的话,它会自动向SecurityContextHolder添加一个AnonymousAuthenticationToken。过滤器和认证提供器的配置如下:这个key会在过滤器和认证提供器之间共享,这样创建的标记可以在以后用到。[12]userAttribute表达式的格式是usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]。这和InMemoryDaoImpl中userMap属性的语法一样。如上面所讲的,匿名认证的好处是,可以对所有的URL模式都进行安全配置。比如: 12.3.AuthenticationTrustResolver简略对匿名认证的讨论,就是AuthenticationTrustResolver接口,它对应着AuthenticationTrustResolverImpl实现。这个接口提供了一个isAnonymous(Authentication)方法,允许感兴趣的类评估认证的特殊状态类型。在处理AccessDeniedException异常的时候,ExceptionTranslationFilter使用这个接口。如果抛出了一个AccessDeniedException异常,而且认证是匿名类型,那么不会抛出403(禁止)响应,这个过滤器会展开AuthenticationEntryPoint,这样主体可以正确验证。这是一个必要的区别,否则主体会一直被认为“需要“认证””,没有机会通过表单,摘要,或其他普通的认证机制登录。你会经常看到ROLE_ANONYMOUS属性,在上面的拦截器配置中,被替换成IS_AUTHENTICATED_ANONYMOUSLY,这在定义权限控制时是完全相同的。这时一个使用AuthenticatedVoter的例子,可以参考验证章节。它使用一个AuthenticationTrustResolver来处理这个特殊的配置属性,并给匿名用户授权。AuthenticatedVoter的方式更强大,因为它允许你区别匿名,rememberMe和完全认证用户。如果你不需要这些功能,你可以直接使用ROLE_ANONYMOUS,这会被SpringSecurity的标准RoleVoter处理。[12]key参数应该没有提供任何真实的安全。它仅仅用来做一个标志。如果你共享了一个ProviderManager包含了一个AnonymousAuthenticationProvider,在一个场景中。可能对于一个认证客户端,创建Authentication对象(比如通过RMI调用),然后一个恶意的可能提交一个AnonymousAuthenticationToken,它会创建自己(选择用户名和权限队列)。如果key可以被猜出来,或可以被找到,这个token就可能被匿名提供者获得。这不是一个通常使用中的问题,但是如果你使用RMI,你最好使用一个自定义的ProviderManager,这可以避免匿名供应器共享你使用的HTTP验证机制。PartIV.授权SpringSecurity的高级授权能力,为它的受欢迎成都提供了一个更可信服的原因。无论你选择如何认证-是否使用SpringSecurity提供的机制和供应器,或整合容器或非SpringSecurity认证权限-你可以找到授权服务,可以在你的程序用通过统一和简单的方式使用。在这部分,我们将探索不同的AbstractSecurityInterceptor实现,他们在第一部分都介绍过了。我们再继续研究如何使用授权,通过使用领域访问对象列表。验证架构13.1.验证在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。这就是赋予给主体的权限。GrantedAuthority对象通过AuthenticationManager保存到Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。GrantedAuthority是一个只有一个方法接口:StringgetAuthority(); 这个方法允许AccessDecisionManager获得一个精确的String来表示GrantedAuthority。通过返回的String,一个GrantedAuthority可以简单的用大多数AccessDecisionManager“读取”。如果GrantedAuthority不能表示为一个String,GrantedAuthority会被看作是“复杂的”,然后返回null。一个“复杂的”GrantedAuthority的例子会是保存了操作列表和授权信息并应用在不同的客户帐号数值的一个实现。如果要显示这种复杂的GrantedAuthority,把转换成String是非常复杂的,getAuthority()方法的结果应该返回null。这会展示给任何AccessDecisionManager,它需要特别支持GrantedAuthority的实现,来了解它的内容。SpringSecurity包含一个具体的GrantedAuthority实现,GrantedAuthorityImpl。这允许任何用户指定的String转换成GrantedAuthority。所有AuthenticationProvider包含安全结构,它使用GrantedAuthorityImpl组装Authentication对象。13.2.处理预调用像我们在技术概述一章看到的那样,SpringSecurity提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。13.2.1.AccessDecisionManager这个AccessDecisionManager被AbstractSecurityInterceptor调用,它用来作最终访问控制的决定。这个AccessDecisionManager接口包含三个方法:voiddecide(Authenticationauthentication,ObjectsecureObject,Listconfig)throwsAccessDeniedException;booleansupports(ConfigAttributeattribute);booleansupports(Classclazz);从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。比如,让我们假设安全对象是一个MethodInvocation。很容易为任何Customer参数查询MethodInvocation,,然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。如果访问被拒绝,实现将抛出一个AccessDeniedException异常。这个supports(ConfigAttribute)方法在启动的时候被AbstractSecurityInterceptor调用,来决定AccessDecisionManager是否可以执行传递ConfigAttribute。supports(Class)方法被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。13.2.2.基于投票的AccessDecisionManager实现虽然用户可以实现它自己的AccessDecisionManager来控制所有授权的方面,SpringSecurity包括很多基于投票的AccessDecisionManager实现。Figure13.1,“投票决议管理器”显示有关的类。Figure13.1.投票决议管理器使用这种方法,一系列的AccessDecisionVoter实现为授权做决定。这个AccessDecisionManager会决定是否基于它的投票评估抛出AccessDeniedException异常。AccessDecisionVoter接口有三个方法:intvote(Authenticationauthentication,Objectobject,Listconfig);booleansupports(ConfigAttributeattribute); booleansupports(Classclazz);具体实现返回一个int,使用可能的反映的AccessDecisionVoter静态属性ACCESS_ABSTAIN,ACCESS_DENIED和ACCESS_GRANTED。如果一个投票实现没有选择授权决定,会返回ACCESS_ABSTAIN。如果它进行过抉择,它必须返回ACCESS_DENIED或ACCESS_GRANTED。这儿有三个由SpringSecurity提供的具体AccessDecisionManager,可以进行投票。ConsensusBased实现会授权,或拒绝访问,基于没有放弃的那些投票的共识。那些属性在平等投票时间上被提供来控制行为,或如果所有投票都是弃权了。AffirmativeBased实现会授予访问权限,如果收到一个或多个ACCESS_GRANTED投票(比如,一个反对投票会被忽略,如果这里至少有一个赞成票)。像ConsensusBased实现一样,这里有一个参数控制如果所有投票都弃权的行为。UnanimousBased提供器希望一致的ACCESS_GRANTED,来允许访问,忽略弃权。如果这里有任何一个ACCESS_DENIED投票,它会拒绝访问。像其他实现一样,有一个参数控制如果所有投票都弃权的行为。有可能实现一个自定义的AccessDecisionManager进行不同的投票统计。比如,投票一个特定的AccessDecisionVoter可能获得更多的权重,这样一个拒绝票对特定的投票者可能有否决权的效果。13.2.2.1.RoleVoterSpringSecurity中最常用到的AccessDecisionVoter实现是简单的RoleVoter,它把简单的角色名称作为配置属性,如果用户分配了某个角色就被允许访问。如果有任何一个配置属性是以ROLE_开头的,就可以进行投票。如果GrantedAuthority返回的String内容(通过getAuthority()方法),与一个或多个以ROLE_开头的ConfigAttributes完全相同的话,就表示允许访问。如果没有匹配任何一个以ROLE_开头的ConfigAttribute,RoleVoter就会拒绝访问。如果没有以ROLE_开头的ConfigAttribute,投票者就会弃权。RoleVoter在匹配的时候是大小写敏感的,这也包括对ROLE_这个前缀。13.2.2.2.AuthenticatedVoter另一个表决器我们不直接看到的是AuthenticatedVoter,它可以被用在不同的场景下,比如匿名,完全授权和remember-me认证用户。很多网站允许在rememberMe认证情况下访问有限的资源,但是需要用户验证自己的身份通过登录来获得完全访问的权限。什么时候我们使用IS_AUTHENTICATED_ANONYMOUSLY来授权匿名访问呢,这个属性被AuthenticatedVoter处理,参考这个类的javadoc获得更多信息。13.2.2.3.CustomVoters也可能实现一个自定义的AccessDecisionVoter。SpringSecurity的单元测试提供了很多例子,包括ContactSecurityVoter和DenyVoter。如果CONTACT_OWNED_BY_CURRENT_USER的ConfigAttribute没有找到,ContactSecurityVoter在投票决定的时候就会弃权。如果投票,它通过MethodInvocation来确认Contact对象的主体,这是方法调用的主体。如果Contact主体匹配Authentication对象中表现的主体,它就会投赞成票。它可能对Contact主体有一个简单的比较,使用一些Authentication表现的GrantedAuthority。所有这些对应不多几行代码,演示授权模型的灵活性。13.3.处理后决定虽然AccessDecisionManager被AbstractSecurityInterceptor在执行安全方法调用之前调用,一些程序需要一个方法来修改安全对象调用返回的对象。虽然你可以简单实现自己的AOP涉及实现,SpringSecurity提供有许多具体实现方面调用,集成它的ACL 功能。Figure13.2,“后决定实现”展示SpringSecurity的AfterInvocationManager和它的具体实现。Figure13.2.后决定实现就像SpringSecurity的其他很多部分一样,AfterInvocationManager有一个单独的具体实现,AfterInvocationProviderManager有AfterInvocationProvider列表。每个AfterInvocationProvider被允许修改返回对象,或抛出AccessDeniedException异常。确实多个提供器可以修改对象,作为之前提供器的结果传递给队列的下一个。让我们现在考虑我们的AfterInvocationProvider的ACL提醒实现。请注意,如果你使用AfterInvocationManager,你将依然需要配置属性,让MethodSecurityInterceptor的AccessDecisionManager允许一个操作。如果你使用典型的SpringSecurity包含AccessDecisionManager实现,没有在特定的安全方法调用上配置属性定义,会导致AccessDecisionVoter弃权。这里,如果AccessDecisionManager的"allowIfAllAbstainDecisions"属性是false,会抛出一个AccessDeniedException异常。你可以避免这个潜在的问题,使用(i)把"allowIfAllAbstainDecisions"设置为true(虽然通常不建议这么做),或者(ii)直接确保这里至少有一个配置属性,这样AccessDecisionVoter会投赞成票。后者(推荐)方法通常使用ROLE_USER或ROLE_AUTHENTICATED配置属性。安全对象实现14.1.AOP联盟(MethodInvocation)安全拦截器在SpringSecurity2.0之前,安全MethodInvocation需要进行很多厚重的配置。现在为方法安全推荐的方式,是使用命名空间配置.使用这个方式,会自动为你配置好方法安全基础bean,,你不需要了解那些实现类。我们只需要对这些类提供一个在这里提到的快速概述。方法安全强制使用MethodSecurityInterceptor,它会保障MethodInvocation。依靠配置方法,一个拦截器可能作用于一个单独的bean或者在多个bean之间共享。拦截器使用MethodSecurityMetadataSource实例获得配置属性,应用特定的方法调用。MapBasedMethodSecurityMetadataSource通过方法名(也可以是通配符)保存配置属性关键字,当使用或元素把属性定义在applicationcontext里,将在内部使用。其他实现会用来处理基于注解的配置。14.1.1.精确的MethodSecurityIterceptor配置你可以使用一个SpringAOP代理机制,直接在你的applicationcontext里配置一个MethodSecurityIterceptor:com.mycompany.BankManager.delete*=ROLE_SUPERVISORcom.mycompany.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR 14.2.AspectJ(JoinPoint)安全拦截器AspectJ安全拦截器相对于上面讨论的AOP联盟安全拦截器,就非常简单了。事实上,我们这节只讨论不同的部分。AspectJ拦截器的名字是AspectJSecurityInterceptor。与AOP联盟安全拦截器不同,在Spring的applicationcontext中的安全拦截器通过代理织入,AspectJSecurityInterceptor是通过AspectJ编译器织入。在一个系统里使用两种类型的安全拦截器也是常见的,使用AspectJSecurityInterceptor处理领域对象实例的安全,AOP联盟MethodSecurityInterceptor用来处理服务层安全。让我们首先考虑如何把AspectJSecurityInterceptor配置到spring的applicationcontext里:com.mycompany.BankManager.delete*=ROLE_SUPERVISORcom.mycompany.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR像你看到的,除了类名的部分,AspectJSecurityInterceptor其实与AOP联盟安全拦截器一样。实际上,两个拦截器共享同样的securityMetadataSource,securityMetadataSource运行的时候使用java.lang.reflect.Method而不是AOP库特定的类。当然,你的访问表决,已经获得了AOP库具体的引用(比如MethodInvocation或JoinPoint),也可以考虑在使用防伪决议时进行更精确的判断(比如利用方法参数)。下一步,你需要定义一个AspectJ切面。比如:packageorg.springframework.security.samples.aspectj;importorg.springframework.security.intercept.aspectj.AspectJSecurityInterceptor;importorg.springframework.security.intercept.aspectj.AspectJCallback;importorg.springframework.beans.factory.InitializingBean;publicaspectDomainObjectInstanceSecurityAspectimplementsInitializingBean{privateAspectJSecurityInterceptorsecurityInterceptor;pointcutdomainObjectInstanceExecution():target(PersistableEntity)&&execution(public**(..))&&!within(DomainObjectInstanceSecurityAspect);Objectaround():domainObjectInstanceExecution(){if(this.securityInterceptor==null){returnproceed();}AspectJCallbackcallback=newAspectJCallback(){publicObjectproceedWithObject(){ returnproceed();}};returnthis.securityInterceptor.invoke(thisJoinPoint,callback);}publicAspectJSecurityInterceptorgetSecurityInterceptor(){returnsecurityInterceptor;}publicvoidsetSecurityInterceptor(AspectJSecurityInterceptorsecurityInterceptor){this.securityInterceptor=securityInterceptor;}publicvoidafterPropertiesSet()throwsException{if(this.securityInterceptor==null)thrownewIllegalArgumentException("securityInterceptorrequired");}}在上面例子里,安全拦截器会作用在每一个PersistableEntity实例上,这是没提到过的一个抽象类(你可以使用任何其他的类或你喜欢的切点)。对于那些情况AspectJCallback是必须的,因为proceed();语句只有在around()内部才有意义。AspectJSecurityInterceptor在想继续执行目标对象时,调用这个匿名AspectJCallback类。你会需要配置Spring读取切面,织入到AspectJSecurityInterceptor中。下面的声明会处理这个:就是这个了!现在你可以在你的系统里任何地方创建bean了,无论用你想到的什么方式(比如newPerson();),他们都会被安全拦截器处理。Chapter15.基于表达式的权限控制SpringSecurity3.0介绍了使用SpringEL表达式的能力,作为一种验证机制添加简单的配置属性的使用和访问决策投票,就像以前一样。基于表达式的安全控制是建立在相同架构下的,但是允许使用复杂的布尔逻辑包含在单独的表达式中。15.1.概述SpringSecurity使用SpringEl来支持表达式,你应该看一下如何工作的如果你对深入了解这个话题感兴趣的话。表达式是执行在一个“根对象”上的,作为一部分执行上下文。SpringSecurity使用特定的类对应web和方法安全,作为根对象,为了提供内建的表达式,访问值,比如当前的认证主体。15.1.1.常用内建表达式表达式根对象的基本类是SecurityExpressionRoot。这个提供了一些常用的表达式,都可以在web和method权限控制中使用。Table15.1.常用内建表达式 表达式说明hasRole([role])返回true如果当前主体拥有特定角色。hasAnyRole([role1,role2])返回true如果当前主体拥有任何一个提供的角色(使用逗号分隔的字符串队列)principal允许直接访问主体对象,表示当前用户authentication允许直接访问当前Authentication对象从SecurityContext中获得permitAll一直返回truedenyAll一直返回falseisAnonymous()如果用户是一个匿名登录的用户就会返回trueisRememberMe()如果用户是通过remember-me登录的用户就会返回trueisAuthenticated()如果用户不是匿名用户就会返回trueisFullyAuthenticated()如果用户不是通过匿名也不是通过remember-me登录的用户时,就会返回true。15.2.Web安全表达式为了保护单独的URL,你需要首先把的use-expressions属性设置为true。SpringSecurity会把的access当做包含了SpringEL表达式。表达式应该返回boolean,定义访问是否应该被允许,比如:...这里我们已经定义了应用的“admin”范围(通过URL模式定义的)应该只对拥有“admin”权限的用户有效,并且用户要在本地子网的IP地址下。我们已经看到了内建的hasRole表达式,在上一章节。表达式hasIpAddress是一个附加的内建表达式,是由web安全提供的。它是由WebSecurityExpressionRoot定义的,它的实例作为表达式根对象当执行web权限表达式时。这个对象也直接暴露了HttpServletRequest对象使用request这个名字,所以你可以直接调用request在一个表达式里。如果使用了表达式,一个WebExpressionVoter会被添加到AccessDecisionManager中,这是被命名空间创建的。所以如果你没有使用命名空间还希望使用表达式,你必须把这些添加到你的配置中。15.3.方法安全表达式方法安全有一点儿复杂,比单独允许和拒绝规则来说。SpringSecurity3.0介绍了一些新注解,为了对复杂的表达式进行支持。15.3.1.@Pre和@Post注解这里有四个注解,支持表达式属性允许进行前置和后置调用验证检测,也支持过滤提交的集合参数或返回值。它们是@PreAuthorize,@PreFilter,@PostAuthorize和 @PostFilter。它们可以通过global-method-security命名空间元素启用:15.3.1.1.访问控制使用@PreAuthorize和@PostAuthorize使用最广泛的注解是@PreAuthorize,它可以决定一个方法是否可以被调用。比如(来自“Contacts”实例应用)@PreAuthorize("hasRole('ROLE_USER')")publicvoidcreate(Contactcontact);这意味着只允许拥有"ROLE_USER"角色的用户访问。很明显,相同的事情可以简单实用传统的配置方法,简单的配置属性来要求角色。但是如果是这样呢:@PreAuthorize("hasPermission(#contact,'admin')")publicvoiddeletePermission(Contactcontact,Sidrecipient,Permissionpermission);这里我们其实使用了方法参数作为表达式的一部分来决定是否当前的用户拥有“admin”表达式对给定的contract。内建的hasPermission()表达式链接到SpringSecurityACL模块,通过applicatoncontext。你可以访问任何方法参数,通过名称就像表达式的变量,在编译的时候使用debug信息。任何SpringEL功能都可以在表达式里使用,所以你可以访问参数的属性。比如,如果你希望特定的方法只允许访问一个用户名和contract匹配,你可以写成@PreAuthorize("#contact.name==principal.name)")publicvoiddoSomething(Contactcontact);这里我们使用另一个内建表达式,它是当前SpringSecurity从securitycontext中获得的Authentication的principal。你也可以直接访问Authentication对象自己,使用表达式名authentication。不太常见的是,你可能希望之星一个访问控制检测,在方法调用之后。这个可以使用@PostAuthorize注解。为了访问一个方法的返回值,使用表达式中的内建名称returnObject。15.3.1.2.过滤使用@PreFilter和@PostFilter你可能已经知道了,SpringSecurity支持对集合和数据的过滤,现在也可以使用表达式实现了。通常是用在一个方法的返回值中。比如:@PreAuthorize("hasRole('ROLE_USER')")@PostFilter("hasPermission(filterObject,'read')orhasPermission(filterObject,'admin')")publicListgetAll();当使用@PostFilter注解时,SpringSecurity遍历返回的集合删除任何表达式返回false的元素。名字filterObject引用集合中的当前对象。你也可以在方法调用之前进行过滤,使用@PreFilter虽然很少需要这样做。这个语法是一样的,但是如果这里有更多参数,都是集合类型然后你必须使用注解的filterTarget属性选择其中一个。注意过滤显然不是一个让你读取数据查询的解决方法。如果你过滤大型集合,删除很多实体,然后这就会是非常没有效率的。acegi到springsecurity的转换方式作者Chris.Baker,发表于2008/04/22-9:44am.http://java.dzone.com/tips/pathway-acegi-spring-security-以前它叫做spring的acegi安全框架,现在重新标识为springsecurity2.0,它实现了简易配置的承诺,提高了开发者的生产力。它已经是java平台上应用最广的安全框架了,在sourceforge上拥有250,000的下载量,SpringSecurity2.0又提供了一系列的新功能。 本文主要介绍了如果把之前建立在acegi基础上的spring应用转换到springsecurity2.0上。16.1.SpringSecurity是什么SpringSecurity是目前用于替换acegi的框架,它提供了一系列新的功能。大为简化了配置继承OpenID,标准单点登录支持windowsNTLM,在windows合作网络上实现单点登录支持JSR250("EJB3")的安全注解支持AspectJ切点表达式语言全面支持RESTWeb请求授权长期要求的支持组,层级角色和用户管理API提升了功能,使用后台数据库的remember-me实现通过springwebflow2.0对web状态和流转授权进行新的支持通过SpringWebServices1.5加强对WSS(原来的WS-Security)的支持整个更多的??16.2.目标目前,我工作在一个spring的web应用上,使用acegi控制对资源的访问权限。用户信息保存在数据库中,我们配置acegi使用了基于JDBC的UserDetails服务。同样的,我们所有的web资源都保存在数据库里,acegi配置成使用自定义的AbstractFilterInvocationDefinitionSource,对每个请求检测授权细节。随着SpringSecurity2.0的发布,我想看看是不是可以替换acegi,但还要保持当前的功能,使用数据库作为我们验证和授权的数据源,而不是xml配置文件(大多数演示程序里使用的都是xml)。这里是我采取的步骤??16.3.步骤第一步(也是最重要的)是下载新的SpringSecurity2.0框架,并确保jar文件放到正确的位置(/WEB-INF/lib/)。SpringSecurity2.0下载包里包含22个jar文件。我不需要把它们全用上(尤其是那些sources包)。在这次练习中我仅仅包含了以下几个:spring-security-acl-2.0.0.jarspring-security-core-2.0.0.jarspring-security-core-tiger-2.0.0.jarspring-security-taglibs-2.0.0.jar在web.xml文件里配置一个DelegatingFilterProxyspringSecurityFilterChainorg.springframework.web.filter.DelegatingFilterProxyspringSecurityFilterChain/*SpringSecurity2.0的配置比acegi简单太多了,所以我没有在以前acegi配置文件的基础上进行修改,我发现从一个空白文件开始更简单。如果你想修改你以前的配置文件,我确定你删除的行数比添加的行数还要多。配置文件的第一部分是指定安全资源过滤器的细节,这让安全资源可以通过数据库读取,而不是在配置文件里保存信息。这里是一个你将在大多数例子中看到的代码。 使用这些内容进行替换:这段配置的主要部分secureResourceFilter,这是一个实现了FilterInvocationDefinitionSource的类,它在SpringSecurity需要对请求页面检测权限的时候调用。这里是MySecureResouceFilter的代码:packageorg.security.SecureFilter;importjava.util.Collection;importjava.util.List;importorg.springframework.security.ConfigAttributeDefinition;importorg.springframework.security.ConfigAttributeEditor;importorg.springframework.security.intercept.web.FilterInvocation;importorg.springframework.security.intercept.web.FilterInvocationDefinitionSource;publicclassMySecureResourceFilterimplementsFilterInvocationDefinitionSource{publicConfigAttributeDefinitiongetAttributes(Objectfilter)throwsIllegalArgumentException{FilterInvocationfilterInvocation=(FilterInvocation)filter;Stringurl=filterInvocation.getRequestUrl();//createaresourceobjectthatrepresentsthisUrlobjectResourceresource=newResource(url);if(resource==null)returnnull;else{ConfigAttributeEditorconfigAttrEditor=newConfigAttributeEditor();//gettheRolesthatcanaccessthisUrlListroles=resource.getRoles();StringBufferrolesList=newStringBuffer();for(Rolerole:roles){rolesList.append(role.getName());rolesList.append(",");}//don'twanttoendwitha","soremovethelast","if(rolesList.length()>0)rolesList.replace(rolesList.length()-1,rolesList.length()+1,"");configAttrEditor.setAsText(rolesList.toString());return(ConfigAttributeDefinition)configAttrEditor.getValue();}}publicCollectiongetConfigAttributeDefinitions(){returnnull;}publicbooleansupports(Class arg0){returntrue;}}getAttributes()方法返回权限的名称(我称之为角色),它们控制当前url的访问权限。好了,现在我们需要安装信息数据库,下一步是让SpringSecurity从数据库中读取用户信息。这个SpringSecurity2.0的例子告诉你如何从下面这样的配置文件里获得用户和权限的列表:你可以把这些例子的配置替换掉,这样你可以像这样从数据库中直接读取用户信息:这里有一种非常快速容易的方法来配置安全数据库,意思是你需要使用默认的数据库表结构。默认情况下,需要下面的几个表:user,authorities,groups,group_members和group_authorities。我的情况下,我的安全数据库表无法这样工作,它和要求的不同,所以我需要修改:通过添加users-by-username-query和authorities-by-username-query属性,你可以使用你自己的SQL覆盖默认的SQL语句。就像在acegi中一样,你必须确保你的SQL语句返回的列与SpringSecurity所期待的一样。这里有另一个group-authorities-by-username-query属性,我在这里没有用到,所以也没有出现在这里例子中,不过它的用法与其他两个SQl语句的方法完全一致。的这些功能大概是在上个月才加入的,在SpringSecurity之前版本中是无法使用的。幸运的是它已经被加入到SpringSecurity中了,这让我们的工作更加简单。你可以通过这里和这里获取信息。dataSourcebean中指示的是链接数据库的信息,它没有包含在我的配置文件中,因为它并不只用在安全中。这里是一个dataSource的例子,如果谁不熟悉可以参考一下:这就是SpringSecurity的所有配置文件。我最后一项任务是修改以前的登陆页面。在acegi中你可以创建自己的登陆就像在第一步时提到到,下载SpringSecurity是最重要的步骤。从那里开始就可以一帆风顺了。PartV.高级话题在这部分,我们会介绍一些这些框架的更高级特性,它们需要前面章节的一些知识,这些功能并不常用到。领域对象安全(ACLs)16.1.概述复杂程序常常需要定义访问权限,不是简单的web请求或方法调用级别。而是,安全决议需要包括谁(认证),哪里(MethodInvocation)和什么(一些领域对象)。换而言之,验证决议也需要考虑真实的领域对象实例,方法调用的主体。想像我们为宠物店设计一个程序。在你的基于Spring程序里有两个主要的用户组:宠物商店的工作人员和宠物商店的顾客。工作人员可以访问所有数据,而你的顾客只能看到他自己的数据。让它更有趣一点儿,你的客户可以允许其他用户看他自己的数据,比如他们“学龄前小狗”教练,或他们本地“小马俱乐部”的负责人。以SpringSecurity为基础,我们可以使用很多方法:编写你的业务方法来提升安全。你可以使用一个集合,包含Customer领域对象实例,来决定哪个用户可以访问。通过SecurityContextHolder.getContext().getAuthentication(),你可以得到Authentication对象。编写一个AccessDecisionVoter提升安全性,通过保存在Authentication对象里的GrantedAuthority[]。这意味着你的AuthenticationManager需要使用自定义GrantedAuthority[]组装这个Authentication,处理每个主体访问的Customer领域对象实例。编写一个AccessDecisionVoter提升安全性,直接打开目标Customer领域对象。这意味着你的投票者需要访问一个DAO,允许它重审Customer对象。它会访问用户提交的Customer对象的集合,然后执行合适的决议。每个方法都是完全可用的。然而,你的第一种认证会涉及你的业务代码。它的主要问题是单元测试困难,也很难在其他地方重用Customer的授权逻辑。从Authentication获得GrantedAuthority[]也还好,但是不适合大规模数量的Customer。如果用户可以访问五万个Customer(不是在这个例子里,但是想像一下,如果它是一个大型的小马俱乐部),这么大的内存消耗,和时间消耗,建造Authentication是不可取的。最后一个方法,直接从外部代码打开Customer,可能是三个中最好的了。它分离了概念,没有滥用内存或CPU周期,但它还是没什么效率,在AccessDecisionVoter和最终业务方法里,它自己会执行一个DAO响应,来重申Customer对象。每个方法调用,都要评估两次,非常不可取。另外,每个方法列出了你需要,从头写自己访问控制列表(ACL)持久化和业务逻辑。幸运的是,这里有另一个选择,我们在下面讨论。16.2.关键概念SpringSecurity的ACL服务放在spring-security-acl-xxx.jar中。你需要把这个JAR添加到你的classpath下,来使用SpringSecurity的领域对象实例安全能力。SpringSecurity的领域对象实例安全能力其实是一个访问控制列表(ACL)的概念。在你的系统中每个领域对象实例都有它自己的ACL,然后这个ACL数据信息,谁可以,谁不可以和领域对象工作。在这种思想下,SpringSecurity在你的系统提供三个主要的ACL 相关能力:一个有效的方法,为所有你的领域对象(修改那些ACL)检索ACL条目。一个方法,在方法调用之前确认给定的主体有权限同你的对象工作。一个方法,在方法调用之后确认给定的主体有权限同你的对象工作(或它返回的什么东西)。像第一点所示,SpringSecurity的ACL模块的一个主要能力,是提供高性能的检索ACL。这个ACL资源能力特别重要,因为在你的系统中每个领域对象实例,可能有多个访问控制条目,每个ACL可能继承其他ACL,像一个树形结构(这是SpringSecurity支持的,非常常用)。SpringSecurity的ACL能力仔细定义来支持高性能检索ACL,可插拔缓存,最小死锁数据库更新,不依赖ORM(我们直接使用的JDBC),适当封装,数据库透明更新。给定的数据库是ACL模块操作的中心,让我们来看看默认实现使用的四个主要表。下面介绍的这些表,为了SpringSecurityACL的部署,使用的表在最后列出:ACL_SID让我们定义系统中唯一主体或授权(“SID”意思是“安全标识”)。它包含的列有ID,一个文本类型的SID,一个标志,用来表示是否使用文本显示引用的主体名或一个GrantedAuthority。因此,对每个唯一的主体或GrantedAuthority都有单独一行。在使用获得授权的环境下,一个SID通常叫做"recipient"授予者。ACL_CLASS让我们在系统中确定唯一的领域对象类。包含的列有ID和java类名。因此,对每个我们希望保存ACL权限的类都有单独一行。ACL_OBJECT_IDENTITY为系统中每个唯一的领域对象实例保存信息。列包括ID,指向ACL_CLASS的外键,唯一标识,所以我们知道为哪个ACL_CLASS实例提供信息,parent,一个外键指向ACL_SID表,展示领域对象实例的拥有者,我们是否允许ACL条目从任何父亲ACL继承。我们对每个领域对象实例有一个单独的行,来保存ACL权限。最后,ACL_ENTRY保存分配给每个授予者单独的权限。列包括一个ACL_OBJECT_IDENTITY的外键,recipient(比如一个ACL_SID外键),我们是否通过审核,和一个整数位掩码,表示真实的权限被授权或被拒绝。我们对于每个授予者都有单独一行,与领域对象工作获得一个权限。像上一段提到的,ACL系统使用整数位掩码。不要担心,你不需要知道使用ACL系统位转换的好处,但我们有充足的32位可以转换。每个位表示一个权限,默认授权是可读(位0),写(位1),创建(位2),删除(位3)和管理(位4)。如果你希望使用其他权限,很容易实现自己的Permission实例,其他的ACL框架部分不了解你的扩展,依然可以运行。了解你的系统中领域对象的数量很重要,完全用不害怕我们选择使用整数位掩码的事实。虽然我们有32位可用来作权限,你可能有几亿领域对象实例(意味着在ACL_OBJECT_IDENTITY表中有几亿行,ACL_ENTRY也很可能是这样)。我们说出这点,因为我们有时发现人们犯错误,决定他们为每个潜在的领域对象提供一位,情况并非如此。现在我们提供了ACL系统可以做的基本概述,它看起来像一个表结构,现在让我们探讨关键接口。关键接口是:Acl:每个领域对象有一个,并只有一个Acl对象,它的内部保存着AccessControlEntry,记住这是Acl的所有者。一个Acl不直接引用领域对象,但是作为替代的是使用一个ObjectIdentity。这个Acl保存在ACL_OBJECT_IDENTITY表里。AccessControlEntry:一个Acl里有多个AccessControlEntry,在框架里常常略写成ACE。每个ACE引用特别的Permission,Sid和Acl。一个ACE可以授权或不授权,包含审核设置。ACE保存在ACL_ENTRY表里。Permission:一个permission表示特殊不变的位掩码,为位掩码和输出信息提供方便的功能。上面的基本权限(位0到4)保存在BasePermission类里。Sid:这个ACL模块需要引用主体和GrantedAuthority[]。间接的等级由Sid接口提供, 简写成“安全标识”。通常类包含PrincipalSid(表示主体在Authentication里)和GrantedAuthoritySid。安全标识信息保存在ACL_SID表里。ObjectIdentity:每个领域对象放在ACl模型的内部,使用ObjectIdentity。默认实现叫做ObjectIdentityImpl。AclService:重审Acl对应的ObjectIdentity。包含的实现(JdbcAclService),重审操作代理LookupStrategy。这个LookupStrategy为检索ACL信息提供高优化策略,使用批量检索(BasicLookupStrategy)然后支持自定义实现,和杠杆物化视图,继承查询和类似的表现中心,非ANSI的SQL能力。MutableAclService:允许修改了的Acl放到持久化中。如果你不愿意,可以不使用这个接口。请注意,我们的AclService和对应的数据库类都使用ANSISQL。这应该可以在所有的主流数据库上工作。在写作的时候,系统成功在HypersonicSQL,PostgreSQL,MicrosoftSQLServer和Oracle上测试通过。SpringSecurity的两个实例演示了ACL模块。第一个是Contacts实例,另一个是文档管理系统(DMS)实例。我们建议大家看一看这些例子。16.3.开始为了开始使用SpringSecurity的ACL功能,你会需要在一些地方保存你的ACL信息。有必要使用Spring的DataSource实例。DataSource注入到JdbcMutableAclService和BasicLookupStrategy实例中。后一个提供了高性能ACL检索能力,前一个提供变异能力。参考例子之一,使用SpringSecurity,为一个例子配置。你也需要使用四个ACL指定的表建立数据库,这写在最后一章(参考ACL实例,查看对应的SQL语句)。一旦你创建了需要的结构,和JdbcMutableAclService的实例,你下一个需求是确认你的领域模型支持SpringSecurityACL包的互操作。希望的ObjectIdentityImpl会证明足够,它提供可以使用的大量方法。大部分人会使用领域对象,包含publicSerializablegetId()方法。如果返回类型是long或与long兼容(比如int),你会发现你不需要为ObjectIdentity进行更多考虑。ACL模块的许多部分对应long标识符。如果你没有使用long(或int,byte等等),你需要重新实现很多类。我们不倾向在SpringSecurityACL模块中支持非long标识符,因为所有数据库序列都支持,最常用的数据类型标识,也可以容纳所有常用场景的足够长度。下面的代码片段,显示如何创建一个Acl,或修改一个存在的Acl://Preparetheinformationwe'dlikeinouraccesscontrolentry(ACE)ObjectIdentityoi=newObjectIdentityImpl(Foo.class,newLong(44));Sidsid=newPrincipalSid("Samantha");Permissionp=BasePermission.ADMINISTRATION;//CreateorupdatetherelevantACLMutableAclacl=null;try{acl=(MutableAcl)aclService.readAclById(oi);}catch(NotFoundExceptionnfe){acl=aclService.createAcl(oi);}//Nowgrantsomepermissionsviaanaccesscontrolentry(ACE)acl.insertAce(acl.getEntries().length,p,sid,true);aclService.updateAcl(acl); 在上面的例子里,我们检索ACl,分配给"Foo"领域对象,使用数字44作标识。我们添加一个ACE,这样名叫"Samantha"的主体可以“管理”这个对象。代码片段是自解释的,除了insertAce方法。insertAce方法的第一个参数是Acl里新条目被插入的决定位置。在上面的的例子里,我们只把新ACE放到以存在的ACE的尾部。最后一个参数是一个布尔值,显示是否ACE授权或拒绝。大多数时间,是授权(true),如果它是拒绝(false),权限就会被冻结。SpringSecurity没有提供任何特定整合,自动创建,更新,或删除ACL,作为你的DAO的一部分或资源操作。作为替代的,你会需要像上面一样为你的单独领域对象写代码。值得考虑在你的服务层使用AOP,来自动继承ACL信息,使用你的服务层操作。我们发现以前这是一个非常有效的方式。一旦,你使用上面的技术,在数据库里保存一些ACL信息,下一步是使用ACL信息,作为授权决议逻辑的一部分。这里你有一大堆选择。你可以写你自己的AccessDecisionVoter或AfterInvocationProvider,期待在方法调用之前或之后触发。这些类使用AclService来检索对应的ACL,然后调用Acl.isGranted(Permission[]permission,Sid[]sids,booleanadministrativeMode),决定权限是授予还是拒绝。可选的,你可能使用我们的AclEntryVoter,AclEntryAfterInvocationProvider或AclEntryAfterInvocationCollectionFilteringProvider类。所有这些类提供一个基于声明的方法,在运行阶段来执行ACL信息,释放你从需要写任何代码。请参考例子程序,学习更多如何使用这些类。预认证场景有的情况下,你需要使用SpringSecurity进行认证,但是用户已经在访问系统之前,在一些外部系统中认证过了。我们把这种情况叫做“预认证”场景。例子包括X.509,Siteminder和应用所在的J2EE容器进行认证。在使用预认证的使用,SpringSecurity必须定义使用请求的用户从用户里获得权限细节信息要依靠外部认证机制。一个用户可能,在X.509的情况下由认证信息确定,或在Siteminder的情况下使用HTTP请求头。对于容器认证,需要调用获得HTTP请求的getUserPrincipal()方法来确认用户。一些情况下,外部机制可能为用户提供角色/权限信息,其他情况就需要通过单独信息源获得,比如UserDetailsService。17.1.预认证框架类因为大多数预认证机制都遵循相同的模式,所以SpringSecurity提供了一系列的类,它们作为内部框架实现预认证认证提供器。这样就避免了重复实现,让新实现很容易添加到结构中,不需要一切从脚手架开始写起。你不需要知道这些类,如果你想使用一些东西,比如X.509认证,因为它已经是命名空间配置里的一个选项了,可以很简单的使用,启动它。如果你需要使用精确的bean配置,或计划编写你自己的实现,这时了解这些提供的实现是如何工作就很有用了。你会在org.springframework.security.web.authentication.preauth包下找到这些类。我们这里只提供一个纲要,你应该从对应的Javadoc和源代码里获得更多信息。17.1.1.AbstractPreAuthenticatedProcessingFilter这个类会检测安全环境的当前内容,如果是空的,它会从HTTP请求里获得信息,提交给AuthenticationManager。子类重写了以下方法来获得信息:protectedabstractObjectgetPreAuthenticatedPrincipal(HttpServletRequestrequest); protectedabstractObjectgetPreAuthenticatedCredentials(HttpServletRequestrequest);在调用之后,过滤器会创建一个包含了返回数据的PreAuthenticatedAuthenticationToken,然后提交它进行认证。通过这里的“authentication”,我们其实只是可能进行读取用户的权限,不过下面就是标准的SpringSecurity认证结构了。17.1.2.AbstractPreAuthenticatedAuthenticationDetailsSource就像其他的SpringSecurity认证过滤器一样,预认证过滤器有一个authenticationDetailsSource属性,默认会创建一个WebAuthenticationDetails对象来保存额外的信息,比如在Authentication对象的details属性里的会话标识,原始IP地址。用户角色信息可以从预认证机制中获得,数据也保存在这个属性里。AbstractPreAuthenticatedAuthenticationDetailsSource的子类,使用实现了GrantedAuthoritiesContainer接口的扩展信息,因此可以使用认证提供其器来读取权限,明确定位用户。下面我们看一个具体的例子。17.1.2.1.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource如果过滤器配置了authenticationDetailsSource的实例,通过调用isUserInRole(Stringrole)方法为每个预先决定的“可映射角色”集合获得认证信息。这个类从MappableAttributesRetriever里获得这些信息。可能的实现方法,包含了在applicationcontext中进行硬编码,或者从web.xml的中读取角色信息。预认证例子程序使用了后一种方式。这儿有一个额外的步骤,使用一个Attributes2GrantedAuthoritiesMapper把角色(或属性)映射到SpringSecurity的GrantedAuthority。它默认只会为名称添加一个ROLE_前缀,但是你可以对这些行为进行完全控制。17.1.3.PreAuthenticatedAuthenticationProvider预认证提供器除了从用户中读取UserDetails以外,还要一些其他事情。它通过调用一个AuthenticationUserDetailsService来做这些事情。后者就是一个标准的UserDetailsService,但要需要的参数是一个Authentication对象,而不是用户名:publicinterfaceAuthenticationUserDetailsService{UserDetailsloadUserDetails(Authenticationtoken)throwsUsernameNotFoundException;}这个接口可能也含有其他用户,但是预认证允许访问权限,打包在Authentication对象里,像上一节所见的。这个PreAuthenticatedGrantedAuthoritiesUserDetailsService就是用来作这个的。或者,它可能调用标准UserDetailsService,使用UserDetailsByNameServiceWrapper这个实现。17.1.4.Http403ForbiddenEntryPoint这个AuthenticationEntryPoint在技术概述那章讨论过。通常它用来为未认证用户(当他们想访问被保护资源的时候)启动认证过程,但是在预认证情况下这不会发生。如果你不使用预认证结合其他认证机制的话,你只要配置ExceptionTranslationFilter的一个实例。如果用户的访问被拒绝了,它就会调用,AbstractPreAuthenticatedProcessingFilter结果返回的一个空的认证。调用的时候,它总会返回一个403禁用响应代码。17.2.具体实现X.509认证写在它自己的章里。这里,我们看一些支持其他预认证的场景。17.2.1.请求头认证(Siteminder) 一个外部认证系统可以通过在HTTP请求里设置特殊的头信息,给应用提供信息。一个众所周知的例子就是Siteminder,它在头部传递用户名,叫做SM_USER。这个机制被RequestHeaderPreAuthenticatedProcessingFilter支持,直接从头部得到用户名。默认使用SM_USER作为头部名。看一下Javadoc获得更多信息。Tip注意使用这种系统时,框架不需要作任何认证检测,极端重要的是,要把外部系统配置好,保护系统的所有访问。如果攻击者可以从原始请求中获得请求头,不通过检测,他们可能潜在修改任何他们想要的用户名。17.2.1.1.Siteminder示例配置使用这个过滤器的典型配置应该像这样:我们假设使用安全命名空间的配置方式,custom-filter,使用了authentication-manager和custom-authentication-provider三个元素(你可以从命名空间章节里了解它们的更多信息)。你应该走出传统的配置方式。我们也假设了你在配置里添加了一个UserDetailsService(名叫“userDetailsService”),来读取用户的角色信息。17.2.2.J2EE容器认证这个J2eePreAuthenticatedProcessingFilter类会从HttpServletRequest的userPrincipal属性里获得准确的用户名。这个过滤器的用法常常结合使用J2EE角色,像上面描述的Section17.1.2.1,“J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource”。 在根目录下有一个使用这个功能的实例应用,可以从svn里找到,如果你感兴趣的话,可以看一下applicationcontext文件。代码在samples/preauth目录下。LDAP认证18.1.综述LDAP通常被公司用作用户信息的中心资源库,同时也被当作一种认证服务。它也可以为应用用户储存角色信息。这里有很多如何对LDAP服务器进行配置的场景,所以SpringSecurity的LDAP提供器也是完全可配置的。它使用为验证和角色检测提供了单独的策略接口,并提供了默认的实现,这些都是可配置成处理绝大多数情况。你还是应该熟悉一下LDAP,在你在SpringSecurity使用它之前。下面的链接提供了很好的概念介绍,也是一个使用免费的LDAP服务器建立一个目录http://www.zytrax.com/books/ldap/的指南。我们也应该熟悉一下通过JNDIAPI使用java访问LDAP。我们没有在LDAP提供器里使用任何第三方LDAP库(Mozilla,JLDAP等等),但是还是用到了SpringLDAP,所以如果你希望自己进行自定义,对这个工程熟悉一下也是有好处的。18.2.在SpringSecurity里使用LDAPSpringSecurity的LDAP认证可以粗略分成以下几部分。从登录名中获得唯一的“辨别名称”或DN。这就意味着要对目录执行搜索,除非预先知道了用户名和DN之前的明确映射关系。验证这个用户,进行绑定用户,或调用远程“比较”操作,比对用户的密码和DN在目录入口中的密码属性。为这个用户读取权限队列。例外情况是,当LDAP目录只是用来检索用户信息和进行本地验证的时候,这也许不可能的,因为目录的属性,比如对用户密码属性,常常被设置成只读权限。让我们看看下面的一些配置场景。要是想得到所有可用的配置选项,请参考安全命名空间结构(使用你的XML编辑器应该就可以看到所有有效信息)。18.3.配置LDAP服务器你需要做的第一件事是配置服务器,它里面应该存放着认证信息。这可以使用安全命名空间里的元素实现。使用url属性指向一个外部LDAP服务器:18.3.1.使用嵌入测试服务器这个元素也可以用来创建一个嵌入服务器,这在测试和演示的时候特别有用。在这种情况,你不需要使用url属性:这里我们指定目录的根DIT应该是“dc=springframework,dc=org”,这是默认的。使用这种方式,命名空间解析器会建立一个嵌入Apache目录服务器,然后检索classpath下的LDIF文件,尝试从它里边把数据加载到服务器里。你可以通过ldif属性自定义这些行为,这样可以定义具体要加载哪个LDIF资源:这就让启动和运行LDAP变得更轻松了,因为使用一个外部服务器还是不大方便。它也避免链接到Apache目录服务器的复杂bean配置。如果使用普通Springbean配置方法会变的更加混乱。你必须把必要的Apache目录依赖的jar放到你的程序中。这些都可以 从LDAP示例程序中获得。18.3.2.使用绑定认证这是一个非常常见的LDAP认证场景。这个很简单的例子可以根据用户登录名提供的模式为用户获得DN,然后尝试和用户的登录密码进行绑定。如果所有用户都保存到一个目录的单独节点下就没有问题。如果你想配置一个LDAP搜索过滤器来定位用户,你可以使用如下配置:如果使用了上面的服务器定义,它会在DNou=people,dc=springframework,dc=org下执行搜索,使用user-search-filter里的值作为过滤条件。然后把用户登录名作为过滤名称的一个参数。如果没有提供user-search-base,搜索将从根开始。18.3.3.读取授权如果从LDAP目录的组里读取权限信息呢,这是通过下面的属性控制的。group-search-base。定义目录树部分,哪个组应该执行搜索。group-role-attribute。这个属性包含了组入口中定义的权限名称。默认是cngroup-search-filter。这个过滤器用来搜索组的关系。默认是uniqueMember={0},对应于groupOfUniqueMembersLDAP类。在这情况下,取代参数是用户的辨别名称。如果你想对登录名搜索,可以使用{1}这个参数。因此,如果我们使用下面进行配置并以用户“ben”的身份通过认证,在读取权限信息的子流程里,要在目录入口ou=groups,dc=springframework,dc=org下执行搜索,查找包含uniqueMember属性值为ou=groups,dc=springframework,dc=org的入口。默认,权限名都要以ROLE_作为前缀。你可以使用role-prefix属性修改它。如果你不想使用任何前缀,可以使用role-prefix="none"。要想得到更多读取权限的信息,可以查看DefaultLdapAuthoritiesPopulator类的Javadoc。18.4.实现类我们上面使用到的命名空间选项很容易使用,也比使用springbean更准确。也有可能你需要知道如何配置在你的applicationcontext里配置SpringSecurityLDAP目录。比如,你可能想自定义一些类的行为。如果你想使用命名空间配置,你可以跳过这节,直接进入下一段。最主要的LDAP提供器类是LdapAuthenticationProvider。它自己没做什么事情,而是代理了其他两个bean的工作,一个是LdapAuthenticator,一个是LdapAuthoritiesPopulator,用来处理用户认证和检索用户的GrantedAuthority属性集合。18.4.1.LdapAuthenticator实现验证者还负责检索所有需要的用户属性。这是因为对于属性的授权可能依赖于使用的验证类型比如,如果对某个用户进行绑定,它也许必须通过用户自己的授权才能进行读取。当前SpringSecurity提供两种验证策略:直接去LDAP服务器验证(“绑定”验证)。比较密码,将用户提供的密码与资源库中保存的进行比较。这可以通过检索密码属性的值并在本地检测,或者执行LDAP“比较” 操作,提供用来比较的密码是从服务器获得的,绝对不会检索真实密码的值。18.4.1.1.常用功能在认证一个用户之前(使用任何一个策略),辨别名称(DN)必须从系统提供的登录名中获得。这可以通过,简单的模式匹配(设置setUserDnPatterns数组属性)或者设置userSearch属性。为了实现DN模式匹配方法,一个标准的java模式格式被用到了,登录名将被参数{0}替代。这个模式应该和DN有关系,并绑定到配置好的SpringSecurityContextSource(看看链接到LDAP服务器那节,获得更多信息)。比如,如果你使用了LDAP服务的URL是ldap://monkeymachine.co.uk/dc=springframework,dc=org,并有一个模式uid={0},ou=greatapes,然后登录名"gorilla"会映射到DNuid=gorilla,ou=greatapes,dc=springframework,dc=org。每个配置好的DN模式将尝试进行定位,直到有一个匹配上。使用搜索获得信息,看看下面的安全对象那节。两种方式也可以结合在一起使用-模式会先被检测一下,然后如果没有找到匹配的DN,就会使用搜索。18.4.1.2.BindAuthenticator这个类BindAuthenticator在这个包里org.springframework.security.ldap.authentication实现了绑定认证策略。它只是尝试对用户进行绑定。18.4.1.3.PasswordComparisonAuthenticator这个类PasswordComparisonAuthenticator实现了密码比较认证策略。18.4.1.4.活动目录认证除了标准LDAP认证以外(绑定到一个DN),活动目录对于用户认证提供了自己的非标准语法。18.4.2.链接到LDAP服务器上面讨论的bean必须连接到服务器。它们都必须使用ContextSource,这个是SpringLDAP的一个扩展。除非你有特定的需求,你通常只需要配置一个DefaultSpringSecurityContextSourcebean,这个可以使用你的LDAP服务器的URL进行配置,可选项还有管理员用户的用户名和密码,这将默认用在绑定服务器的时候(而不是匿名绑定)。参考SpringLDAP的AbstractContextSource类的Javadoc获得更多信息。18.4.3.LDAP搜索对象通常,比简单DN匹配越来越复杂的策略需要在目录里定位一个用户入口。这可以使用LdapUserSearch的一个示例,它可以提供认证者实现,比如让他们定位一个用户。提供的实现是FilterBasedLdapUserSearch。18.4.3.1.FilterBasedLdapUserSearch这个bean使用一个LDAP过滤器,来匹配目录里的用户对象。这个过程在javadoc里进行过解释,在对应的搜索方法,JDKDirContextclass。就如那里解释的,搜索过滤条件可以通过方法指定。对于这个类,唯一合法的参数是{0},它会代替用户的登录名。18.4.4.LdapAuthoritiesPopulator在成功认证用户之后,LdapAuthenticationProvider会调用配置好的LdapAuthoritiesPopulatorbean,尝试读取用户的授权集合。这个DefaultLdapAuthoritiesPopulator是一个实现类,它将通过搜索目录读取授权,查找用户成员所在的组(典型的这会是目录中的groupOfNames或groupOfUniqueNames入 口)。查看这个类的Javadoc获得它如何工作的更多信息。如果你只想在验证时使用LDAP,而是从另外的地方读取认证信息(比如数据库)你可以提供你的自己的接口实现,然后使用注入作为替代。18.4.5.SpringBean配置典型的配置方法,使用到像我们这在里讨论的这些bean,就像这样:uid={0},ou=people
这里建立了一个提供器,访问LDAP服务,URL是ldap://monkeymachine:389/dc=springframework,dc=org。认证会被执行,尝试绑定这个DNuid=,ou=people,dc=springframework,dc=org。在成功认证之后,会通过查找下面的DNou=groups,dc=springframework,dc=org使用默认的过滤条件(member=),将角色分配给用户。角色名会通过每个匹配的“ou”属性获得。要配置用户的搜索对象,使用过滤条件(uid=)替代DN匹配(或附加到它上面),你需要配置下面的bean 并使用它,设置认证者的userSearch属性。这个认证者会调用搜索对象,在尝试绑定到用户之前获得正确的用户DN。18.4.6.LDAP属性和自定义UserDetails使用LdapAuthenticationProvider进行认证的结果,和使用普通SpringSecurity认证一样,都要使用标准UserDetailsService接口。它会创建一个UserDetails对象,并保存到返回的Authentication对象里。在使用UserDetailsService时,常见的需求是可以自定义这个实现,添加额外的属性。在使用LDAP的时候,这些基本都来自用户入口的属性。UserDetails对象的创建结果被提供者的UserDetailsContextMapper策略控制,它负责在用户对象和LDAP环境数据之间进行映射:publicinterfaceUserDetailsContextMapper{UserDetailsmapUserFromContext(DirContextOperationsctx,Stringusername,Collectionauthority);voidmapUserToContext(UserDetailsuser,DirContextAdapterctx);}只有第一个方法与认证有关。如果你提供这个接口的实现,你可以精确控制如何创建UserDetails对象。第一个参数是SpringLDAP的DirContextOperations实例,他给你访问加载的LDAP属性的通道。username参数是用来认证的名字,最后一个参数是从用户加载的授权列表。]环境数据加载的方式不同,视乎你采用的认证方法。使用BindAuthenticator,从绑定操作返回的环境会用来读取属性,否则数据会通过标准的环境,从配置好的ContextSource获得(当测试配置好定位用户,这会从搜索对象中获得数据)。Chapter19.JSP标签库SpringSecurity有它自己的Taglib,提供了JSP中访问权限信息和提供安全约束的功能。19.1.声明Taglib要想使用这些标签,你必须在你的JSP中声明安全taglib:<%@taglibprefix="sec"uri="http://www.springframework.org/security/tags"%>19.2.authorize标签这个标签用来决定它的内容是否会被执行。在SpringSecurity3.0中,它可以被用在两种方式中[13]。第一个方式是使用web-security表达式,指定标签中的access属性。表达式执行会被WebSecurityExpressionHandlder代理,这个类定义在applicationcontext中(你应该在命名空间配置中启用了web表达式,并确认这个服务可以使用)。所以,比如,你可能使用这段内容只能被拥有在他们的GrantedAuthority列表中含有"supervisor"权限的用户才能看到。一个常见的需求是指显示一个特定的链接,如果用户允许点击它。怎么让我们进一步决定是否一些事情可以允许呢?标签也可以用一种可选的模式操作,允许你定义一个特定的URL作为属性。如果用户允许调用这个URL,标签内容就会被执行,否则它会被略过。所以,你可能会使用一些像这些内容之会被有权限发送请求到"/admin"URL的用户才可以看到。为了使用这个标签,必须在你的applicationcontext中拥有一个 WebInvocationPrivilegeEvaluator实例。如果你使用了命名空间,会自动注册一个。这是一个DefaultWebInvocationPrivilegeEvaluator的实例,它会创建一个默认web请求对提供的URL,调用安全拦截器来查看请求是成功还是失败的。这允许你代理到访问控制设置,你使用intercept-url声明在命名空间中的配置,保存信息(比如必须的角色)在你的JSP中。这种方式也可以结合method属性,提供HTTPmethod,为了更详细的匹配。19.3.authentication标签这个标签允许访问当前的Authentication对象,保存在安全上下文中。它直接渲染一个对象的属性在JSP中。所以,比如,如果Authentication的principal属性是SpringSecurity的UserDetails对象的一个实例,就要使用来渲染当前用户的名称。当然,它不必使用JSP标签来实现这些功能,一些人更愿意在视图中保持逻辑越少越好。你可以在你的MVC控制器中访问Authentication对象(通过调用SecurityContextHolder.getContext().getAuthentication())然后直接在模型中添加数据,来渲染视图。19.4.accesscontrollist标签这个标签纸在使用SpringSecurityACL模块时才可以使用。它检测一个用逗号分隔的特定领域对象的需要权限列表。如果当前用户拥有这些权限的任何一个,标签内容就会被执行。否则,就会被略过。一个例子可能像这些将被显示,如果用户拥有指定对象的权限显示为"1"或"2"。权限会被传递到PermissionFactory定义在applicationcontext中,把它们转换为ACL的Permission实例,所以他们可以使用工厂支持的任何格式-不是必须使用整数,它们可以是字符串,像READ或者WRITE。如果没有找到PermissionFactory,一个DefaultPermissionFactory实例会被使用。applicationcontext中的AclService会被用来加载对应的对象的Acl实例。Acl会被调用,使用需要的权限来检测,如果它们中的任何一个被授权了。[13]SpringSecurity2.0遗留的方式也是支持的,但是不推荐使用。Java认证和授权服务(JAAS)供应器20.1.概述SpringSecurity提供一个包,可以代理Java认证和授权服务(JAAS)的认证请求。这个包的细节在下面讨论。JAAS的核心是登录配置文件。想要了解更多JAAS登录配置文件的信息,可以查询Sun公司的JAAS参考文档。我们希望你对JAAS有一个基本了解,也了解它的登录配置语法,这才能更好的理解这章的内容。20.2.配置这个JaasAuthenticationProvider通过JAAS认证用户的主体和证书。让我们假设我们有一个JAAS登录配置文件,/WEB-INF/login.conf,里边的内容如下:JAASTest{sample.SampleLoginModulerequired;};就像所有的SpringSecuritybean一样,这个JaasAuthenticationProvider要配置在 applicationcontext里。下面的定义是与上面的JAAS登录配置文件对应的:
这个CallbackHandler和AuthorityGranter会在下面进行讨论。20.2.1.JAASCallbackHandler大多数JAAS的登录模块需要设置一系列的回调方法。这些回调方法通常用来获得用户的用户名和密码。在SpringSecurity发布的时候,SpringSecurity负责用户交互(通过认证机制)。因此,现在认证请求使用JAAS代理,SpringSecurity的认证机制将组装一个Authentication对象,它包含了所有JAASLoginModule需要的信息。因此,SpringSecurity的JAAS包提供两个默认的回调处理器,JaasNameCallbackHandler和JaasPasswordCallbackHandler。他们两个都实现了JaasAuthenticationCallbackHandler。大多数情况下,这些回调函数可以直接使用,不用了解它们的内部机制。为了需要完全控制回调行为,内部JaasAuthenticationProvider使用一个InternalCallbackHandler封装这个JaasAuthenticationCallbackHandler。这个InternalCallbackHandler才是实际实现了JAAS通常的CallbackHandler接口。任何时候JAASLoginModule被使用的时候,它传递一个applicationcontext里配置的InternalCallbackHandler列表。如果这个LoginModule需要回调InternalCallbackHandler,回调会传递封装好的JaasAuthenticationCallbackHandler。20.2.2.JAASAuthorityGranterJAAS工作在主体上。任何“角色”在JAAS里都是作为主体表现的。另一方面SpringSecurity使用Authentication对象。每个Authentication对象包含单独的主体和多个GrantedAuthority[]。为了方便映射不同的概念,SpringSecurity的JAAS包包含了AuthorityGranter接口。一个AuthorityGranter负责检查JAAS主体,返回一个String的集合,用来表示分配给这个主体的权限。对于每一个返回的权限字符串,JaasAuthenticationProvider会创建 一个JaasGrantedAuthority(它实现了SpringSecurity的GrantedAuthority接口),包含了AuthorityGranter返回的字符串和AuthorityGranter传递的JAAS主体。JaasAuthenticationProvider获得JAAS主体,通过首先成功认证用户的证书,使用JAAS的LoginModule,然后调用LoginContext.getSubject().getPrincipals(),使用返回的每个主体,传递到每个AuthorityGranter里,最后定义在JaasAuthenticationProvider.setAuthorityGranters(List)属性里。SpringSecurity没有包含任何产品型的AuthorityGranter,因为每个JAAS主体都有特殊实现的意义。但是,这里的单元测试里有一个TestAuthorityGranter,演示了一个简单的AuthorityGranter实现。Chapter21.CAS认证21.1.概述JA-SIG开发了一个企业级的单点登录系统,叫做CAS。与其他项目不同,JA-SIG的中心认证服务是开源的,广泛使用的,简单理解的,不依赖平台的,而且支持代理能力。SpringSecurity完全支持CAS,提供一个简单的整合方式,把使用SpringSecurity的单应用发布,转换成使用企业级CAS服务器的多应用发布安全你可以从http://www.ja-sig.org/cas/找到CAS的更多信息。你还需要访问这个网站下载CAS服务器文件。21.2.CAS是如何工作的虽然CAS网站包含了CAS的架构文档,我们这里还是说一下使用SpringSecurity环境的一般性概述,。SpringSecurity3.0支持CAS3。在写文档的时候,CAS服务器的版本是3.3。你要在公司内部安装CAS服务器。CAS服务器就是一个WAR文件,所以安装服务器没有什么难的。在WAR文件里,你需要自定义登录和其他单点登录展示给用户的页面。发布CAS3.3的时候,你也需要指定一个CAS的deployerConfigContext.xml里包含的AuthenticationHandler。AuthenticationHandler有一个简单的方法,返回布尔值,判断给出的证书集合是否有效。你的AuthenticationHandler实现会需要链接到后台认证资源类型里,像是LDAP服务器或数据库。CAS自己也包含非常多AuthenticationHandler帮助实现这些。在你下载发布服务器war文件的时候,它会把用户名和密码匹配的用户成功验证,这对测试很有用。除了CAS服务器,其他关键角色当然是你企业发布的其他安全web应用。这些web应用被叫做"services"。这儿有两种服务:标准服务和代理服务。代理服务可以代表用户,从其他服务中请求资源。后面会进行更详细的介绍。21.3.配置CAS客户端CAS的web应用端通过SpringSecurity使用起来很简单。我们假设你已经知道SpringSecurity的基本用法,所以下面都没有涉及这些。我们会假设使用基于命名空间配置的方法,并且添加了CAS需要的bean。你需要添加一个ServicePropertiesbean,到你的applicationcontext里。这表现你的CAS服务: 这里的service必须是一个由CasAuthenticationFilter监控的URL。这个sendRenew默认是false,但如果你的程序特别敏感就应该设置成true。这个参数作用是,告诉CAS登录服务,一个单点登录没有到达。否则,用户需要重新输入他们的用户名和密码,来获得访问服务的权限。下面配置的bean就是展开CAS认证的过程(假设你使用了命名空间配置):...应该使用entry-point-ref选择驱动认证的CasAuthenticationEntryPoint类。CasAuthenticationFilter的属性与UsernamePasswordAuthenticationFilter非常相似(在基于表单登录时用到)。为了CAS的操作,ExceptionTranslationFilter必须有它的AuthenticationEntryPoint,这里设置成CasAuthenticationEntryPointbean。CasAuthenticationEntryPoint必须指向ServicePropertiesbean(上面讨论过了),它为企业CAS登录服务器提供URL。这是用户浏览器将被重定向的位置。下一步,你需要添加一个CasAuthenticationProvider和它的合作伙伴:... 一旦通过了CAS的认证,CasAuthenticationProvider使用一个UserDetailsService实例,来加载用户的认证信息。这里我们展示一个简单的基于内存的设置。如果你翻回头看一下"HowCASWorks"那节,这些beans都是从名字就可以看懂的。X.509认证22.1.概述X.509证书认证最常见的使用方法是使用SSL验证服务器的身份,通常情况是在浏览器使用SSL。浏览器使用一个它维护的可信任的证书权限列表,自动检测服务器发出的证书(比如数字签名)。你也可以使用SSL进行“mutualauthentication”相互认证;服务器会从客户端请求一个合法的证书,作为SSL握手协议的一部分。服务器将验证客户端,通过检测它被签在一个可接受的权限里的认证。如果已经提供了一个有效的证书,就可以从程序的servletAPI里获得。SpringSecurityX.509模块使用过滤器确认证书。它将证书映射为应用程序的用户,并使用标准的SpringSecurity基础设施读取用户的已授予权限集合。你应该很熟悉使用证书,在使用SpringSecurity之前为你的servlet容器启动客户端认证。大多数工作都是创建和安装合适的证书和密匙。比如,如果你使用tomcat,可以阅读这里的教程http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html。在你把它用在SpringSecurity里之前,先知道它是怎么工作,是很重要的。22.2.把X.509认证添加到你的web系统中启用X.509客户端认证非常直观。只需要把元素添加到你的http安全命名空间配置里。......这个元素有两个可选属性:subject-principal-regex。这是一个正则表达式,用来从证书主体名称里获得用户名。默认值已经写在上面了。这个用户名会传递给UserDetailsService来获得用户的认证信息。user-service-ref。这是X.509需要用到的一个UserDetailsService的bean的id。如果你的applicationcontext里只定义了一个bean,就不需要使用它。subject-principal-regex应该包含一个单独的组。比如默认的表达式"CN=(.*?),"匹配普通的名字字段。所以,如果证书主题名是"CN=JimiHendrix,OU=...",就会得到一个名叫"JimiHendrix"的用户。这个匹配是大小写不敏感的。所以"emailAddress=(.?),"也会匹配"EMAILADDRESS=jimi@hendrix.org,CN=...",得到一个"jimi@hendrix.org"用户名。如果客户端给出一个证书,并成功获得了一个合法用户名,然后在安全环境里应该有一个有效的Authentication对象。如果没有找到证书,或没有找到对应的用户,安全环境会保持为空。这说明你可以很简单的和其他选项一起使用X.509认证,比如基于表单登录。22.3.为tomcat配置SSL在SpringSecurity项目的samples/certificate目录下,有几个已经生成好的证书。如果你不想自己去生成,就可以使用们启用SSL做测试,。server.jks文件包含了服务器证书,私匙和签发证书颁发机构证书。这里还有一些客户端证书文件,提供给例子程序的用户。你可以把他们安装到你的浏览器,启动SSL客户端认证。要运行支持SSL的tomcat,把server.jks文件放到tomcat的conf目录下,然后把 下面的连接器添加到server.xml文件中clientAuth也可以设置成want,如果你希望客户端没有提供证书的时候SSL链接也能成功。客户端不提供证书的话,就不能访问SpringSecurity的任何安全对象,除非你使用了非X.509认证机制,比如表单认证。替换验证身份23.1.概述AbstractSecurityInterceptor可以在安全对象回调期间,暂时替换SecurityContext和SecurityContextHolder里的Authentication对象。只有在原始Authentication对象被AuthenticationManager和AccessDecisionManager成功处理之后,才有可能发生这种情况。如果有需要,RunAsManager会显示替换的Authentication对象,这应该通过SecurityInterceptorCallback调用。通过在安全对象回调过程中临时替换Authentication对象,安全调用可以调用其他需要不同认证授权证书的对象。这也可以为特定的GrantedAuthority对象执行内部安全检验。因为SpringSecurity提供不少帮助类,能够基于SecurityContextHolder的内容自动配置远程协议,这些运行身份替换在远程web服务调用的时候特别有用。23.2.配置一个SpringSecurity提供的RunAsManager接口::AuthenticationbuildRunAs(Authenticationauthentication,Objectobject,Listconfig);booleansupports(ConfigAttributeattribute);booleansupports(Classclazz);第一个方法返回Authentication对象,在方法的调用期间替换以前的Authentication对象。如果方法返回null,意味着不需要进行替换。第二个方法用在AbstractSecurityInterceptor中,作为它启动时校验配置属性的一部分。supports(Class)方法会被安全拦截器的实现调用,确保配置的RunAsManager支持安全拦截器即将执行的安全对象类型。SpringSecurity提供了一个RunAsManager的具体实现。如果任何一个ConfigAttribute是以RUN_AS_开头的,RunAsManagerImpl类返回一个替换的RunAsUserToken。如果找到了任何这样的ConfigAttribute,替换的RunAsUserToken会通过一个新的GrantedAuthorityImpl,为每一个RUN_AS_ConfigAttribute包含同样的主体,证书,赋予的权限,就像原来的Authentication对象一样。每个新GrantedAuthorityImpl会以ROLE_开头,对应RUN_ASConfigAttribute。比如,一个替代RunAsUserToken,对于RUN_AS_SERVER的结果是包含一个ROLE_RUN_AS_SERVER赋予的权限。替代的RunAsUserToken就像其他Authentication对象一样。它可能需要通过代理合适的AuthenticationProvider被AuthenticationManager验证。这个RunAsImplAuthenticationProvider执行这样的认证,它直接获得任何一个有效的 RunAsUserToken。为了保证恶意代码不会创建一个RunAsUserToken,由RunAsImplAuthenticationProvider保障获得一个key的散列值被保存在所有生成的标记里。RunAsManagerImpl和RunAsImplAuthenticationProvider在bean上下文里,创建使用同样的key:通过使用相同的key,每个RunAsUserToken可以被它验证,并使用对应的RunAsManagerImpl创建。出于安全原因,这个RunAsUserToken创建后就不能改变。安全数据库表结构可以为框架采用不同的数据库结构,这个附录为所有功能提供了一种参考形式。你只要为需要的功能部分提供对应的表结构。这些DDL语句都是对应于HSQLDB数据库的。你可以把它们当作一个指南,参照它,在你使用的数据库中定义表结构。A.1.User表UserDetailsService的标准JDBC实现(JdbcDaoImpl),需要从这些表里读取用户的密码,帐号信息(可用或禁用)和权限(角色)列表。createtableusers(usernamevarchar_ignorecase(50)notnullprimarykey,passwordvarchar_ignorecase(50)notnull,enabledbooleannotnull);createtableauthorities(usernamevarchar_ignorecase(50)notnull,authorityvarchar_ignorecase(50)notnull,constraintfk_authorities_usersforeignkey(username)referencesusers(username));createuniqueindexix_auth_usernameonauthorities(username,authority);A.1.1.组权限SpringSecurity2.0在JdbcDaoImpl中支持了权限分组。如果启用了权限分组功能,对应的数据库结果如下所示:createtablegroups(idbigintgeneratedbydefaultasidentity(startwith0)primarykey,group_namevarchar_ignorecase(50)notnull);createtablegroup_authorities(group_idbigintnotnull,authorityvarchar(50)notnull,constraintfk_group_authorities_groupforeignkey(group_id)referencesgroups(id));createtablegroup_members(idbigintgeneratedbydefaultasidentity(startwith0)primarykey, usernamevarchar(50)notnull,group_idbigintnotnull,constraintfk_group_members_groupforeignkey(group_id)referencesgroups(id));A.2.持久登陆(Remember-Me)表这个表用来保存安全性更高的持久登陆remember-me实现所需要的数据。如果你直接或通过命名空间使用了JdbcTokenRepositoryImpl,你就会需要这些表结构。createtablepersistent_logins(usernamevarchar(64)notnull,seriesvarchar(64)primarykey,tokenvarchar(64)notnull,last_usedtimestampnotnull);A.3.ACL表这里有四个表被SpringSecurity用来实现ACL。acl_sid保存被ACL系统分配的安全标示符。它们可能是唯一的实体或可能分配给多个实体的权限。acl_class定义ACL可以处理的实体类型。class列保存了对象的Java类名。acl_object_identity保存值得那个领域对象昂的对象标示定义。acl_entry保存ACL权限,分配给一个特定的对象标示和安全标示。假设数据库会自动生成主键作为每个标示。JdbcMutableAclService必须可以获得这些,当创建了一个新的acl_sid或acl_class表中的数据。它有两个属性可以定义需要的SQL来获得这些数据,classIdentityQuery和sidIdentityQuery。这两个属性的默认值是callidentity()。A.3.1.HypersonicSQL默认的表结构可以工作在内嵌的HSQLDB中,可以在框架内用作单元测试。createtableacl_sid(idbigintgeneratedbydefaultasidentity(startwith100)notnullprimarykey,principalbooleannotnull,sidvarchar_ignorecase(100)notnull,constraintunique_uk_1unique(sid,principal));createtableacl_class(idbigintgeneratedbydefaultasidentity(startwith100)notnullprimarykey,classvarchar_ignorecase(100)notnull,constraintunique_uk_2unique(class));createtableacl_object_identity(idbigintgeneratedbydefaultasidentity(startwith100)notnullprimarykey,object_id_classbigintnotnull,object_id_identitybigintnotnull,parent_objectbigint,owner_sidbigintnotnull,entries_inheritingbooleannotnull,constraintunique_uk_3unique(object_id_class,object_id_identity),constraintforeign_fk_1foreignkey(parent_object)referencesacl_object_identity(id),constraintforeign_fk_2foreignkey(object_id_class)referencesacl_class(id),constraintforeign_fk_3foreignkey(owner_sid)referencesacl_sid(id)); createtableacl_entry(idbigintgeneratedbydefaultasidentity(startwith100)notnullprimarykey,acl_object_identitybigintnotnull,ace_orderintnotnull,sidbigintnotnull,maskintegernotnull,grantingbooleannotnull,audit_successbooleannotnull,audit_failurebooleannotnull,constraintunique_uk_4unique(acl_object_identity,ace_order),constraintforeign_fk_4foreignkey(acl_object_identity)referencesacl_object_identity(id),constraintforeign_fk_5foreignkey(sid)referencesacl_sid(id));A.3.1.1.PostgreSQLcreatetableacl_sid(idbigserialnotnullprimarykey,principalbooleannotnull,sidvarchar(100)notnull,constraintunique_uk_1unique(sid,principal));createtableacl_class(idbigserialnotnullprimarykey,classvarchar(100)notnull,constraintunique_uk_2unique(class));createtableacl_object_identity(idbigserialprimarykey,object_id_classbigintnotnull,object_id_identitybigintnotnull,parent_objectbigint,owner_sidbigint,entries_inheritingbooleannotnull,constraintunique_uk_3unique(object_id_class,object_id_identity),constraintforeign_fk_1foreignkey(parent_object)referencesacl_object_identity(id),constraintforeign_fk_2foreignkey(object_id_class)referencesacl_class(id),constraintforeign_fk_3foreignkey(owner_sid)referencesacl_sid(id));createtableacl_entry(idbigserialprimarykey,acl_object_identitybigintnotnull,ace_orderintnotnull,sidbigintnotnull,maskintegernotnull,grantingbooleannotnull,audit_successbooleannotnull,audit_failurebooleannotnull,constraintunique_uk_4unique(acl_object_identity,ace_order),constraintforeign_fk_4foreignkey(acl_object_identity)referencesacl_object_identity(id),constraintforeign_fk_5foreignkey(sid)referencesacl_sid(id));你需要把classIdentityQuery和sidIdentityQuery两个JdbcMutableAclService的 属性设置成下面的值:selectcurrval(pg_get_serial_sequence('acl_class','id'))selectcurrval(pg_get_serial_sequence('acl_sid','id'))安全命名空间这个附录提供了对安全命名空间中可用元素的参考信息,也介绍了它们创建的bean的信息(对单独类的知识和它们是如何在一起工作的-你可以在工程的Javadoc和这个文档的其他部分找到更多信息)。如果你以前没有使用过命名空间,请阅读命名空间配置部分的介绍章节,这部分的信息是作为那些章节的一个补充资料的。我们推荐你在编辑基于schema的配置时使用一个高质量的XML编辑器,这会为你提供上下文环境相关的信息,哪些元素和属性是可用的,注释会解释它们的用途。命名空间是使用RELAXNG兼容格式编写的,随后转换为XSDSchema。如果你对这种格式很熟悉,很可能希望直接查看schema文件。B.1.Web应用安全-元素元素为你的应用程序的web层封装了安全性配置。它创建了一个名为"springSecurityFilterChain"的FilterChainProxybean,这个bean维护了一系列的建立了web安全配置的安全过滤器。[14]。一些核心的过滤器总是要被创建的,其他的将根据子元素的配置添加到过滤器队列中。标准过滤器的位置都是固定的(参考命名空间配置中的过滤器顺序表格),这避免了之前版本中的一个常见问题,那时候用户必须自己在FilterChainProxybean中配置过滤器链。当然如果你需要对配置进行完全控制,依然可以这样做。所有需要引用AuthenticationManager的过滤器,都会自动注入命名空间配置创建的内部实例(查看介绍章节获得AuthenticationManager的更多信息)。命名空间块会创建一个HttpSessionContextIntegrationFilter,一个ExceptionTranslationFilter和一个FilterSecurityInterceptor。它们是固定的,不能使用其他可选方式替换。B.1.1.属性元素的属性控制核心过滤器的一些属性。B.1.1.1.servlet-api-provision支持一些版本的HttpServletRequest提供的安全方法,必须isUserInRole()和getPrincipal(),通过向堆栈中添加一个SecurityContextHolderAwareRequestFilterbean来实现。默认是"true"。B.1.1.2.path-type控制拦截URL的时候,使用ant路径(默认)或是使用正则表达式。实际中,它向FilterChainProxy中设置了特定的UrlMatcher。B.1.1.3.lowercase-comparisons是否在对URL进行匹配前,先将URL转换成小写。如果没有定义,默认是"true"。B.1.1.4.realm为基础认证设置realm名称(如果启用)。对应BasicAuthenticationEntryPoint中的realmName属性。B.1.1.5.entry-point-ref正常情况下AuthenticationEntryPoint将根据配置的认证机制进行设置。这个属性让这个行为使用自定义的AuthenticationEntryPointbean进行覆盖,它会启动认证流程。B.1.1.6.access-decision-manager-ref可选的属性,指定AccessDecisionManager实现的ID,这应该被认证的HTTP请求使用。默认情况下一个AffirmativeBased实现会被RoleVoter和AuthenticatedVoter使用。 B.1.1.7.access-denied-page这个属性已经被access-denied-handler子元素取代了。B.1.1.8.once-per-request对应FilterSecurityInterceptor的observeOncePerRequest属性。默认是"true"。B.1.1.9.create-session控制创建一个HTTP会话的紧急程度。如果不设置,默认是"ifRequired"。其他选项是"always"和"never"。这个属性的设置影响HttpSessionContextIntegrationFilter的allowSessionCreation和forceEagerSessionCreation属性。除非把属性设置为"never"allowSessionCreation会一直为"true"。除非把属性设置为"always"forceEagerSessionCreation会一直为"false"。所以默认的配置允许会话的创建,但不会强制。如果启用同步会话控制,当forceEagerSessionCreation被设置为"true",不管这里设置的什么都会抛出异常。使用"never"会在HttpSessionContextIntegrationFilter初始化的过程中导致异常。B.1.2.这个元素允许你为默认的AccessDeniedHandler设置errorPage属性,它会被ExceptionTranslationFilter用到,(使用error-page属性,或通过ref属性提供你自己的实现。参考ExceptionTranslateFilter获得更多信息。)B.1.3.元素这个元素用来定义URL模式集合,应用对什么感兴趣并配置它们应该如何处理。它用来构建被FilterSecurityInterceptor使用的FilterInvocationDefinitionSource,也可以从过滤器链中排除特定的模式(通过使用filters="none"属性)。它也负责配置ChannelAuthenticationFilter,如果特定的URL需要通过HTTPS访问,比如。当匹配时指定的模式对应了进入的请求,匹配过程就会完成,按照声明的元素顺序。所以最希望被匹配的模式应该放在上面,最常用的模式应该放在最后。B.1.3.1.pattern这个模式定义了URL路径。内容依赖于http元素中的path-type属性,它的默认值是ant路径语法。B.1.3.2.methodHTTPMethod会被用来结合模式来匹配进入的请求。如果忽略,所有的Method都会匹配。如果一个相同的模式指定了,使用method和没有使用method两种方式,指定了method的匹配将被优先使用。B.1.3.3.access列出会被存储在FilterInvocationDefinitionSource中的访问属性,为定义的模式/Method结合的形式。这应该是由分号分隔的安全配置属性队列(比如角色名称)。B.1.3.4.requires-channel可以是http或https,这是根据一个特定的URL模式是否应该通过HTTP或HTTPS访问。如果没有偏好还可以选择any。如果这个属性已经出现在任何一个上,一个ChannelAuthenticationFilter会添加到过滤器堆栈里,它的附加依赖也会添加到applicationcontext中。查看信道安全获得使用传统bean的例子配置。如果添加了一个配置,它会被SecureChannelProcessor和InsecureChannelProcessor使用来决定在重定向到HTTP/HTTPS的时候使用什么端口。B.1.3.5.filters可以只使用“none”作为属性值。它会导致任何匹配的请求完全被SpringSecurity忽略。所有中的其他配置,影响在请求上,在这个过程中都无法访问安全上下文。在这 个请求过程中访问被保护的方法都会失败。B.1.4.元素默认情况下,PortMapperImpl的实例会添加到配置中,在重定向到安全和不安全的URL时使用到。这个元素可以选择用来覆盖类定义的默认映射。每个子元素都定义了一对HTTP:HTTPS端口。默认的映射是80:443和8080:8443。一个覆盖这些的例子可以在命名空间介绍中看到。B.1.5.元素用来把一个UsernamePasswordAuthenticationFilter添加到过滤器堆栈中,把一个LoginUrlAuthenticationEntryPoint添加到applicationcontext中来提供需要的认证。这将永远凌驾于其他命名空间创建的切入点。如果没有提供属性,一个登录页面会自动创建在"/spring-security-login"这个URL下[15]。这个行为可以使用下面的属性自定义。B.1.5.1.login-page这个URL应该用来生成登录页面。对应LoginUrlAuthenticationEntryPoint的loginFormUrl属性。默认是"/spring-security-login"。B.1.5.2.login-processing-url对应UsernamePasswordAuthenticationFilter的filterProcessesUrl属性。默认是"/j_spring_security_check"B.1.5.3.default-target-url对应UsernamePasswordAuthenticationFilter的defaultTargetUrl属性。如果没有设置,默认值是"/"(应用的根路径)。一个用户会在登录之后到达这个URL,在他们没有在登录之前尝试访问一个安全资源,否则他们就会被转向到原来请求的URL。B.1.5.4.always-use-default-target如果设置成"true",用户会一直转发到default-target-url指定的位置,无论他们在登录页面之前访问的什么位置。对应UsernamePasswordAuthenticationFilter的alwaysUseDefaultTargetUrl属性。B.1.5.5.authentication-failure-url对应UsernamePasswordAuthenticationFilter的authenticationFailureUrl属性。定义了在登录失败时浏览器会重定向的URL。默认是"/spring_security_login?login_error",它会自动被登陆页面生成器处理,并使用一个错误信息重新渲染登录页面。B.1.5.6.authentication-success-handler-ref这可以用来替换default-target-url和always-use-default-target,你可以完全控制成功认证之后的导航流向。这个值应该是applicationcontext中的AuthenticationSuccessHandlerbean的名称。B.1.5.7.authentication-failure-handler-ref可以用来替换authentication-failure-url,你可以完全控制认证失败之后的导航流向。这个值应该是applicationcontext中的AuthenticationFailureHandlerbean的名称。B.1.6.元素向配置中添加一个BasicAuthenticationFilter和BasicAuthenticationEntryPoint。后一个只有在基于表单登录没有启用的时候才会被用作配置入口。B.1.7.元素向堆栈中添加RememberMeAuthenticationFilter。这会在配置了一个TokenBasedRememberMeServices,或一个PersistentTokenBasedRememberMeServices,或一个用户自定义的实现了 RememberMeServices的配置设置后启用。B.1.7.1.data-source-ref如果设置了这个,PersistentTokenBasedRememberMeServices会被使用到,并配置上一个JdbcTokenRepositoryImpl实例。B.1.7.2.token-repository-ref配置一个PersistentTokenBasedRememberMeServices但是允许使用一个自定义的PersistentTokenRepositorybean。B.1.7.3.services-ref允许对将要用在过滤器里的RememberMeServices的实现提供完全控制。这个值将是applicationcontext里的一个实现了这个接口的bean的id。B.1.7.4.token-repository-ref配置一个PersistentTokenBasedRememberMeServices但是允许使用一个自定义的PersistentTokenRepositorybean。B.1.7.5.key属性对应AbstractRememberMeServices的"key"属性。应该设置一个唯一的值来确定remember-me的cookies只对唯一的应用有效。[16]。B.1.7.6.token-validity-seconds对应AbstractRememberMeServices的tokenValiditySeconds属性。指定remember-mecookie生效的秒数周期。默认它会在14日内生效。B.1.7.7.user-service-refremember-me服务实现要求可以访问UserDetailsService,所以在applicationcontext中必须有一个定义。如果只定义了一个,它会被选中,并被命名空间配置自动使用。如果这里有多个实例,你可以使用这个树形指定一个bean的id。B.1.8.元素会话管理相关的功能由额外的过滤器栈中的SessionManagementFilter实现。B.1.8.1.session-fixation-protection分析一个已存在的绘画是否应该被销毁,当一个用户认证通过,并启动了一个新会话。如果设置为"none",则不会出现任何改变。"newSession"会创建一个新的空会话。"migrateSession"会创建一个新会话,并把之前会话中的属性都复制到新会话中。默认是"migrateSession"。如果启用了会话伪造防御,SessionManagementFilter会使用一个匹配的DefaultSessionAuthenticationStrategy。参考这个类的javadoc获得更多细节。B.1.9.元素添加对同步会话控制的支持,允许限制一个用户可以拥有的活动会话的数量。会创建一个ConcurrentSessionFilter,连同一个ConcurrentSessionControllerStrategy和SessionManagementFilter的实例。如果已经声明了form-login元素,策略对象也会注入到创建的验证过滤器中。一个SessionRegistry的实例(SessionRegistryImpl的实例,除非用户希望使用自定义bean)会被创建,交给策略使用。B.1.9.1.max-sessions属性对应ConcurrentSessionControllerImpl的maximumSessions属性。B.1.9.2.expired-url属性如果一个用户尝试使用一个已经过期"expired"的会话,同步会话控制器会重定性到的URL。因为用户超过了允许的会话数量,但是又在其他地方登录了系统。除非设置exception-if-maximum-exceeded,其他时候都应该设置这个属性。如果没有设置值, 一个过期信息会直接写到响应中。B.1.9.3.error-if-maximum-exceeded属性如果设置成"true",一个SessionAuthenticationException会被抛出,当一个用户尝试超过最大会话允许数量。默认行为是让原始会话过期。B.1.9.4.session-registry-alias和session-registry-ref属性用户可以提供他们自己的SessionRegistry实现,使用session-registry-ref属性。其他同步会话控制bean就可以使用它。它也可以用来使用内部会话注册的引用,用在你自己的bean或一个管理接口里。你可以使用session-registry-alias属性暴露内部bean,给它一个名字你可以在你的配置的任意地方都使用它。B.1.10.元素添加一个AnonymousAuthenticationFilter和AnonymousAuthenticationProvider到堆栈里。如果你使用IS_AUTHENTICATED_ANONYMOUSLY属性,就是必要的。B.1.11.元素添加X.509认证的支持。一个X509AuthenticationFilter会被添加到堆栈中,会创建一个PreAuthenticatedProcessingFilterEntryPoint。后一个只有的其他认证机制都没有使用的情况下才会用到(它唯一的功能是返回一个HTTP403错误代号)。一个PreAuthenticatedAuthenticationProvider也会被创建,并代理用户权限读取到一个UserDetailsService里。B.1.11.1.subject-principal-regex属性定义一个正则表达式,会从证书中取出用户名(与UserDetailsService一起使用)。B.1.11.2.user-service-ref属性允许一个特定的UserDetailsService,与X.509一起使用,当多个实例被配置的时候。如果没有设置,会尝试自动定位一个合适的实例并使用它。B.1.12.元素与类似,拥有相同的属性。login-processing-url的默认值是"/j_spring_openid_security_check"。一个OpenIDAuthenticationFilter和OpenIDAuthenticationProvider会被注册上。后者需要一个UserDetailsService的引用。它也可以使用id指定,使用user-service-ref属性,或者在applicationcontext中自动定位。B.1.13.元素添加一个LogoutFilter到过滤器堆栈中。它和SecurityContextLogoutHandler一起配置。B.1.13.1.logout-url属性这个URL会触发注销操作(比如,会被过滤器处理)。默认是"/j_spring_security_logout"。B.1.13.2.logout-success-url属性用户在注销后转向的URL。默认是"/"。B.1.13.3.invalidate-session属性对应SecurityContextLogoutHandler的invalidateHttpSession属性。默认是"true",这样注销的时候会销毁会话。B.1.14.元素这个元素用来将一个过滤器添加到过滤器链中。它不会创建额外的bean,但是它用来选择选择一个javax.servlet.Filter类型的bean,这个bean已经定义在application context中,把它添加到SpringSecurity维护的过滤器链的特定位置。全部信息可以在命名空间章节找到。B.2.认证服务在SpringSecurity3.0之前,一个AuthenticationManager会自动注册,现在你必须使用元素注册一个bean。这个bean是SpringSecurity的ProviderManager类的一个实例,它需要配置一个或多个AuthenticationProvider的实例。这里可以使用命名空间支持的语法元素,也可以使用标准的bean定义,使用custom-authentication-provider元素来添加列表。B.2.1.元素每个SpringSecurity应用,只要使用了命名空间,就必须在什么地方包含对应的元素。它负责注册AuthenticationManager,为应用各提供验证服务。它也允许你定义一个别名,为内部实例,在你的配置中来使用。这些都写在命名空间介绍中。所有元素,创建了AuthenticationProvider实例,应该是这个元素的子元素。B.2.1.1.元素这个元素基本是配置DaoAuthenticationProvider的简化形式。DaoAuthenticationProvider读取用户信息,从UserDetailsService中,比较用户名/密码,来进行用户登录。UserDetailsService实例可以被定义,无论是命名空间中的(jdbc-user-service或使用user-service-ref属性来引用一个bean,定义在applicationcontext中)。你可以在命名空间介绍中找到。。B.2.1.2.使用来引用一个AuthenticationProviderBean如果你已经创建了自己的AuthenticationProvider实现,(或希望配置SpringSecurity提供的一个实现,因为什么原因使用传统配置方式,你可以使用下面的语法,来把它添加到内部ProviderManager列表中:)B.3.方法安全B.3.1.元素这个元素是为SpringSecurity中的bean提供安全方法支持的最基本元素。方法可以通过使用注解来保护(在接口或类级别进行定义)或者作为子元素的切点集合,使用AspectJ语法。方法安全使用与web安全相同的AccessDecisionManager配置,但是可以使用SectionB.1.1.6,“access-decision-manager-ref”中的解释进行覆盖,使用相同的属性。B.3.1.1.secured-annotations和jsr250-annotations属性把这些设置为"true"会分别启用对SpringSecurity自己的@Secured注解和JSR-250注解的支持,默认情况下它们两个都是禁用的。JSR-250注解的应用还需要向AccessDecisionManager添加一个Jsr250Voter,这样你需要确定你需要做这个,如果你使用一个自定义的实现,然后想要使用这些注解。B.3.1.2.安全方法使用除了在单独的方法或类的基础上使用@Secured定义安全属性,你可以定义交叉安全实体,覆盖你服务层中所有的方法和接口,使用元素。它有两个属性:expression-切点表达式 access-提供的安全属性你可以在命名空间介绍中找到一个例子。B.3.1.3.元素这个元素可以用来装饰一个AfterInvocationProvider来使用安全拦截器,通过命名空间。你可以定义0,或者多个类,在global-method-security元素中,每个都使用一个ref属性引用到一个AfterInvocationProvider实例,在你的applicationcontext中B.3.2.LDAP命名空间选项LDAP已经在它自己的章节中讨论过一些细节了。我们将在这里进行一些扩展,解释命名空间中的选项如何对应Spring的bean。LDAP实现使用SpringLDAP扩展,所以最好熟悉一下工程的API。B.3.2.1.使用元素定义LDAP服务器这个元素使用其他LDAPbean来建立一个SpringLDAPContextSource,定义LDAP服务器的位置和其他信息(比如用户名和密码,如果它不允许匿名访问)来连接它。它也可以用来创建一个测试用的嵌入式服务器。所有选项的语法细节信息都在LDAP章节中。实际上的ContextSource实现是DefaultSpringSecurityContextSource,它扩展了SpringLDAP的LdapContextSource类。manager-dn和manager-password属性分别对应后者的userDn和password属性。如果你只在你的applicationcontext中定义了一个服务器,其他LDAP命名空间定义的bean会自动使用它。否则,你可以为这个元素定义一个"id"属性,然后在其他命名空间bean中使用server-ref属性引用它。这其实是ContextSource实例的bean的id,如你你想要在其他传统Springbean中使用它。B.3.2.2.元素这个元素是为了创建LdapAuthenticationProvider实例的简写。默认情况下它会和BindAuthenticator实例和一个DefaultAuthoritiesPopulator一起配置。B.3.2.2.1.user-dn-pattern属性如果你的用户在目录中的一个固定的位置(比如,你可以不需要进行目录查询,就从用户名直接找到一个DN),你可以使用这个属性来直接映射DN。它直接对应AbstractLdapAuthenticator的userDnPatterns属性。B.3.2.2.2.user-search-base和user-search-filter属性如果你需要执行查询,来定义目录中的用户,然后你可以设置这些属性来控制查询。BindAuthenticator会被配置在FilterBasedLdapUserSearch中,属性值直接对应bean构造方法的前两个参数。如果这些属性没有设置,也没有提供可选的user-dn-pattern,就会使用默认的查询值user-search-filter="(uid={0})"和user-search-base=""。B.3.2.2.3.group-search-filter,group-search-base,group-role-attribute和role-prefix属性group-search-base的值对应DefaultAuthoritiesPopulator的构造方法参数groupSearchBase,默认是"ou=groups"。默认过滤器值是"(uniqueMember={0})",这假设入口是"groupOfUniqueNames"类型。group-role-attribute对应groupRoleAttribute属性,默认是"cn"。role-prefix对应于rolePrefix,默认为"ROLE_"。B.3.2.2.4.元素它是作为的子元素,切换认证策略从BindAuthenticator到 PasswordComparisonAuthenticator。这可以选择性提供hash属性或者使用子元素来对密码进行编码,在提交到目录进行比较之前。B.3.2.3.元素这个元素配置一个LDAPUserDetailsService。这个类使用LdapUserDetailsService,这是FilterBasedLdapUserSearch和DefaultAuthoritiesPopulator的结合。它支持的属性与的用法相同。[14]查看介绍章节来了解如何从你的web.xml文件中建立映射[15]这个特性真是只为了提供便利,没有支持生产环境的意图(应该选择一个视图技术,可以用来渲染一个自定义登录页面)。这个类DefaultLoginPageGeneratingFilter负责生成登录页面,会提供登录表单为正常表单登录和/或OpenID如果需要的话。[16]这不会对PersistentTokenBasedRememberMeServices的使用产生影响,这个标记是保存在服务器一端的__