序言
Acegi Security为基于J2EE的企业应用软件提供全面的安全解决方案。正如你在本手册中看到的那样,我们尝试为您提供有用的,高可配置的安全系统。
安 全是一个永无止境的目标,获取一个全面的,系统级的实现方式是至关重要的。在安全界,我们鼓励你采用“分层安全”,这样每个层都确保自身尽可能的安全,另 外的层提供另外的安全。每个层自身越“紧密”,你的系统就越鲁棒和安全。在底层,你要处理传输入安全和系统认证,减少“中间人攻击”(man-in- the-middle attacks)。接下来你要使用防火墙,结合VPN或者IP安全来确保只有认证过的系统能够尝试连接。在企业环境中,你可能部署DMZ (demilitarized zone,隔离区)来把面向公众的服务器和后端的数据和应用服务器分隔开。在以非授权用户运行进程和文件系统安全最大化上,你的操作系统也将扮演一个关键 的角色。接下来你要防止针对系统的拒绝服务和暴力攻击。入侵检测系统在检测和应对攻击方面尤其有用,这些系统可以实时屏蔽恶意TCP/IP地址。在更高层 上,你的Java虚拟机需要进行配置,将授予不同Java类型的权限最小化,然后,你的应用程序要对添加针对自身特定问题域的安全配置。Acegi Security使后者-应用程序安全变得容易。
当然,你要正确对待上述提到的每个安全层,以及包含于每个层的管理因素。这样的管理因素具体包括:安全公告监测,补丁,人工诊断,审计,变更管理,工程管理系统,数据备份,灾难恢复,性能评测,负载监测,集中日志,应急反应程序等等。
Acegi Security专注于在企业应用安全层为您提供帮助,你将会发现和各式各样的需求和商业问题领域一样多。银行系统的需求和电子商务应用的需求不同。电子 商务应用和售卖军用自动工具的公司的需求不同。这些客户化的需求使得应用安全显得有趣,富有挑战性而且物有所值。
本手册为Acegi Security 1.0.0的发布而大规模重新组织。请先阅读Part I 架构概览。手册的其余部分按照传统的手册方式编排,需要一定的基础才能阅读。
我们希望您会觉得手册有用,并且欢迎您提供反馈意见和建议。
最后,欢迎加入Acegi Security社区。
acegi参考手册(v1.0.4)[译]-第一章 简介
Part I. 架构概览
象其他的软件一样,Acegi Security也有在整个框架中都会使用的特定核心接口,类,和概念抽象。在手册的这一部分,在检视这些规划和执行Acegi Security集成所必须的核心要素之前,我们先介绍Acegi Security。
第一章. 简介
1.1. Acegi Security是什么?
Acegi Security为基于J2EE的企业软件应用提供全面的安全服务。特别是使用领先的J2EE解决方案-Srping框架开发的项目。如果您不是使用 Spring开发企业应用,我们温馨提醒您仔细研究一下。熟悉Spring,尤其是依赖注射原理,会极大的帮助你快速掌握Acegi Security。
人们使用Acegi Security有很多种原因,不过通常吸引他们到这个项目的原因是他们在J2EE的 Servlet Specification 或者 EJB Specification中找不到迫切需要的典型企业应用场景。提到这些规范,特别要提出的是他们不是在WAR或者EAR级别可移植的。这样,如果你切 换服务器环境,一般来说你要在目标环境中花费很多工夫来重新配置你的应用安全。使用Acegi Security解决了这些问题,并且为你提供了很多其他有用的,完全可定制的安全特性。
如你所知,安全包含两个主要操作。第一个被称为“认 证”,是为用户建立一个它所声明的principal。Principal通常代表用户,设备,或者其他能在你的应用中执行操作的其他系统。“授权”指判 定一个principal能否在你的系统中执行某个操作。在到达授权判断之前,principal的的身份认证已经由认证过程执行过了。这些概念是通用 的,不是Acegi Security特有的。
在认证层面,Acegi Security广泛支持各种认证模块。这些认证模块绝大多数是第三方提供,或者相关的标准组织开发的,例如Internet Engineering Task Force。作为补充,Acegi Security自己也提供了一些认证功能。Acegi Security当前支持如下的认证技术。
&S226; HTTP BASIC authentication headers (an IEFT RFC-based standard)
&S226; HTTP Digest authentication headers (an IEFT RFC-based standard)
&S226; HTTP X.509 client certificate exchange (an IEFT RFC-based standard)
&S226; LDAP (a very common approach to cross-platform authentication needs, especially in large environments)
&S226; Form-based authentication (for simple user interface needs)
&S226; Computer Associates Siteminder
&S226; JA-SIG Central Authentication Service (otherwise known as CAS, which is a popular open source single sign on system)
&S226; Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (a Spring remoting protocol)
&S226; Automatic "remember-me" authentication (so you can tick a box to avoid re-authentication for a predetermined period of time)
&S226; Anonymous authentication (allowing every call to automatically assume a particular security identity)
&S226; Run-as authentication (which is useful if one call should proceed with a different security identity)
&S226; Java Authentication and Authorization Service (JAAS)
&S226; Container integration with JBoss, Jetty, Resin and Tomcat (so you can still use Container Manager Authentication if desired)
&S226; 你自己的认证系统 (如下所示)
很 多独立软件供应商(ISVs)选择Acegi Security是因为它具有丰富的认证模块。这样无论他们的终端客户需要什么,他们都可以快速集成到他们的系统中,不用花很多工夫或者让终端客户改变环 境。如果Acegi Security System for Spring的7个认证模块还没有满足你的需求的话,Acegi Security是一个开放的系统,很容易写你自己的认证机制。许多Acegi Security的企业用户需要和“遗留”系统集成,这些遗留系统不遵循任何安全标准,Acegi Security能够和这样的系统“合作愉快”。
有 时候基本的认证是不够的。有时候你需要根据principal和应用交互的方式来应用不同的安全措施。例如,你可能为了防止密码被窃取,或者防止终端用户 受到“中间人”攻击,需要保证到达的是请求通过HTTPS的。或者,你要确保是一个真正的人而不是某种机器人或者自动进程在发送请求。这对于保护密码恢复 不受暴力破解攻击,或者防止他人很容易的复制你应用的关键内容。为了帮助你实现这些目标,Acegi Security完全支持自动“通道安全”("channel security"),以及集成Jcaptcha来检测是否是真正人类用户。
Acegi Security不仅提供了认证功能,而且提供了完备的授权功能。在授权方面主要有三个领域,授权web请求,授权方法调用,授权存取单个领域对象实例。 为了帮助你理解这些区别,对照考虑一下Servlet 规范中的web模式安全的授权功能,EJB容器管理安全以及文件系统安全。Acegi Security提供了所有这些重要领域的完备功能,我们将在本手册的后面介绍。
1.2. 历史
Acegi Security始于2003年晚期,当时在Spring Developers邮件列表中有人提问是否有人考虑提供一个基于Spring的安全实现。当时,Srping的社区是相对比较小的(尤其是和今天相 比!),实际上Spring本身也是2003年早期才作为一个SourceForge项目出现的。对此问题的回应是它确实是一个值得研究的领域,虽然限于 时间无法进行深入。
有鉴于此,这个简单的安全实现虽然构建了但是并没有发布。几周以后,Spring社区的其他成员询问了安全框架,代码就被提供给了他们。
随后又有人请求,到了2004年一月,大约有20人左右在使用这些代码。另外一些人加入到这些先行的用户中来,并建议建立一个SourceForge项目,这个项目在2004年3月建立起来。
在 早期,该项目自身并布具备任何认证模块。认证过程依赖容器管理安全(Container Managed Security)而Acegi Security注重授权。在一开始这样是合适的,但是随着越来越多的用户要求额外的容器支持,基于容器的认证的限制就显示出来了。另外一个相关的问题是 添加新的JAR文件到容器的classpath,通常会让最终用户感到困惑并且配置错误。
随后,Acegi Security加入了认证服务。大约一年后,Acegi Security成为了一个Spring Framework官方子项目。在2年半多的在多个软件项目中的活跃使用以及数以百计的改进和社区贡献,1.0.0最终版在2006年5月发布。
今天,Acegi Security成为一个强大而活跃的社区。在支持论坛上有数以千计的帖子。14位开发人员专职开发,一个活跃的社区也定期共享补丁并支持他们的同侪。
1.3. 发行版本号
理 解Acegi Security的版本号是非常好处的,它可以帮助你判定升级的到新的版本是否需要花费很大精力。我们的正式发行版本使用Apache Portable Runtime Project版本指引,可以在下述网站查看http://apr.apache.org/versioning.html。为了您查看方便,我们引用该 页的说明部分如下:
“版本号由三个部分的整数组成:主版本号(MAJOR)、副版本号(MINOR)、补丁版本号(PATCH)。主要的含义 是主版本号(MAJOR)是不兼容的,API大规模升级。副版本号(MINOR)在源文件和可执行版和老版本保持兼容,补丁版本号(PATCH)则意味着 向前和向后的完全兼容”。
acegi参考手册(v1.0.4)[译]-第二章 技术概览[上]
第二章. 技术概览
2.1. 运行时环境
Acegi Security可以在JRE1.3中运行。这个发行版本中支持也Java 5.0,尽管对应的Java类型被分开打包到一个后缀是“tiger”的包中。因为Acegi Security致力于以一种自包含的方式运行,因此不需要在JRE中放置任何特殊的配置文件。特别无需配置Java Authentication and Authorization Service (JAAS)策略文件或者将Acegi Security放置到通用的classpath路径中。
同样的,如果你使用EJB容器或者Servlet容器,同样无需放置任何特别的配置文件或者将Acegi Security包含在服务器的类加载器(classloader)中。
上述的设计提供了最大的部署灵活性,你可以直接把目标工件(JAR, WAR 或者 EAR))直接从一个系统copy到另一个系统,它马上就可以运行起来。
2.2. 共享组件
让我们来看看Acegi Security中最重要的一些共享组件。所谓共享组件是指在框架中处于核心地位,系统脱离了它们之后就不能运行。这些Java类型代表了系统中其他部分的构建单元,因此理解它们是非常重要的,即使你不需要直接和它们打交道。
最 基础的对象是SecurityContextHolder。在这里存储了当前应用的安全上下文(security context),包括正在使用应用程序的principal的详细信息。SecurityContextHolder默认使用ThreadLocal来 存储这些详细信息,这意味着即便安全上下文(security context)没有被作为一个参数显式传入,它仍然是可用的。如果在当前principal的请求处理后清理线程,那么用这种方式使用 ThreadLocal是非常安全的。当然, Acegi Security自动为你处理这些,所以你无需担心。
有些应用程序由于使 用线程的方式而并不是完全适用ThreadLocal。例如,Swing客户端可能需要一个Java Virtual Machine中的所有线程都使用同样的安全上下文(security context)。在这种情况下你要使用SecurityContextHolder.MODE_GLOBAL模式。另外一些应用程序可能需要安全线程产 生的线程拥有同样的安全标识符(security identity)。这可以通过SecurityContextHolder.MODE_INHERITABLETHREADLOCAL来实现。你可以通 过两种方法来修改默认的SecurityContextHolder.MODE_THREADLOCAL。第一种是设置一个系统属性。或者,调用 SecurityContextHolder的一个静态方法。大部分的应用程序不需要修改默认值,不过如果你需要,那么请查看 SecurityContextHolder的JavaDocs获取更多信息。
我们在SecurityContextHolder中 存储当前和应用程序交互的principal的详细信息。Acegi Security使用一个Authentication对象来代表这个信息。尽管你通常不需要自行创建一个Authentication对象,用户还是经 常会查询Authentication对象。
你可以在你的应用程序中的任何地方使用下述的代码块:
java 代码
1. Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
2. if (obj instanceof UserDetails) {
3. String username = ((UserDetails)obj).getUsername();
4. } else {
5. String username = obj.toString();
6. }
上 述的代码展示了一些有趣的联系和关键的对象。首先,你会注意到在SecurityContextHolder和Authentication之间有一个媒 介对象。SecurityContextHolder.getContext() 方法实际上返回一个SecurityContext。Acegi Security使用若干个不同的SecurityContext实现,以备我们需要存储一些和principal无关的特殊信息。一个很好的例子就是我 们的Jcaptcha集成,它需要知道一个需求是否是由人发起的。这样的判断和principal是否通过认证完全没有关系,因此我们将它保存在 SecurityContext中。
从上述的代码片段可以看出的另一个问题是你可以从一个Authentication对象中获取一 个principal。Principal只是一个对象。通常可以把它cast为一个UserDetails对象。UserDetails在Acegi Security中是一个核心接口,它以一种扩展以及应用相关的方式来展现一个principal。可以把UserDetails看作是你的用户数据库和 Acegi Security在SecurityContextHolder所需要的东西之间的一个适配器(adapter)。作为你自己用户数据库的一个展现,你可 能经常要把它cast到你应用程序提供的原始对象,这样你就可以调用业务相关的方法(例如 getEmail(), getEmployeeNumber())。
现在你可能已经开始疑惑,那我什么时候提供UserDetails对象呢?我要如何提供呢?
我 想你告诉过我这个东西是声明式的,我不需要写任何Java代码-那怎么做到呢?对此的简短回答就是有一个叫做UserDetailsService的特殊 接口。这个接口只有一个方法,接受一个Sring类型的参数并返回一个UserDetails。Acegi Security提供的大多数认证提供器将部分认证过程委派给UserDetailsService。UserDetailsService用来构建保存 在SecurityContextHolder中的Authentication对象。好消息是我们提供若干个UserDetailsService的实 现,包括一个使用in-memory map和另一个使用JDBC的。
大多数用户还是倾向于写自己的实现,这样的实现经常就是简单的构建于已 有的Data Access Object (DAO)上,这些DAO展现了他们的雇员、客户、或者其他企业应用程序中的用户。要记得这样做的好处,不论你的UserDetailsService返 回什么,它总是可以从SecurityContextHolder中获取,象上面的代码显示的那样。
除了principal, Authentication提供的另一个重要方法就是getAuthorities()。这个方法返回一个GrantedAuthority对象数组。 GrantedAuthority,毫无疑问,就是授予principal的权限。这些权限通常是“角色”,例如ROLE_ADMINISTRATOR 或者ROLE_HR_SUPERVISOR。这些角色稍后配置到web授权,方法授权和领域对象授权。Acegi Security的其他部分能够处理这些权限,并且期待他们被提供。通常你会从UserDetailsService中返回 GrantedAuthority对象。
通常GrantedAuthority对象都是应用范围的权限。它们都不对应特定的领域对 象。因此,你应该不会有一个代表54号员工对象的GrantedAuthority,因为这样会有数以千计的authority,你马上就会用光所有内存 (或者,至少会让系统花太长时间来认证一个用户)。当然,Acegi Security会高效的处理这种普遍的需求,但是你不会使用领域对象安全功能来实现这个目的。
最后,但不是不重要,你有时候需要在 HTTP 请求之间存储SecurityContext。另外有些时候你在每次请求的时候都会重新认证principal,不过大部分时候你会存储 SecurityContext。HttpSessionContextIntegrationFilter在HTTP之间存储 SecurityContext。正如类名字显示的那样,它使用HttpSession来进行存储。基于安全原因,你永远都不要直接和 HttpSession交互。没有理由这么做,所以记得使用SecurityContextHolder来代替。
让我们回忆一下,Acegi Security的基本组成构件是:
&S226; SecurityContextHolder,提供对SecurityContext的所有访问方式。
&S226; SecurityContext, 存储Authentication以及可能的请求相关的安全信息。
&S226; HttpSessionContextIntegrationFilter, 在web请求之间把SecurityContext存储在HttpSession中。
&S226; Authentication, 以Acegi Security的方式表现principal。
&S226; GrantedAuthority, 表示赋予一个principal的应用范围的权限。
&S226; UserDetails, 为从你的应用程序DAO中获取必要的信息来构建一个Authentication 对象。
&S226; UserDetailsService,用传入的String类型的username(或者认证ID,或类似)来创建一个UserDetails。
现在你已经理解了这些重复使用的组件,让我们仔细看看认证过程吧。
2.3. 认证
正 如我们在手册开始部分所说的那样,Acegi Security适用于很多认证环境。虽然我们建议大家使用Acegi Security自身的认证功能而不是和容器管理认证(Container Managed Authentication)集成,但是我们仍然支持这种和你私有的认证系统集成的认证。让我们先从Acegi Security完全自行管理管理web安全的角度来探究一下认证,这也是最复杂和最通用的情形。
想象一个典型的web应用的认证过程:
1.你访问首页,点击一个链接。
2.一个请求被发送到服务器,服务器判断你是否请求一个被保护的资源。
3.因为你当前未被认证,服务器发回一个回应,表明你必须通过认证。这个回应可能是一个HTTP回应代码,或者重定向到一个特定的网页。
4.基于不同的认证机制,你的浏览器会重定向到一个网页好让你填写表单,或者浏览器会用某种方式获取你的身份认证(例如一个BASIC认证对话框,一个cookie,一个X509证书等等。)。
5.浏览器会发回给服务器一个回应。可能是一个包含了你填写的表单内容的HTTP POST,或者一个包含你认证详细信息的HTTP header。
6.接下来服务器会判断提供的认证信息是否有效。如果它们有效,你进入到下一步。如果它们无效,那么通常请求你的浏览器重试一次(你会回到上两步)。
7.你引发认证的那个请求会被重试。但愿你认证后有足够的权限访问那些被保护的资源。如果你有足够的访问权限,请求就会成功。否则,你将会受到一个意味“禁止”的HTTP403错误代码。
在Acegi Security中,对应上述的步骤,有对应的类。主要的参与者(按照被使用的顺序)是:ExceptionTranslationFilter, AuthenticationEntryPoint, 认证机制(authentication mechanism), 以及AuthenticationProvider。
ExceptionTranslationFilter是Acegi Security用来检测任何抛出的安全异常的过滤器(filter)。这种异常通常是由AbstractSecurityInterceptor抛出 的,它是授权服务的主要提供者。我们将会在下一部分讨论AbstractSecurityInterceptor,现在我们只需要知道它产生Java异 常,并且对于HTTP或者如何认证一个principal一无所知。反而是ExceptionTranslationFilter提供这样的服务,它负责 要么返回403错误代码(如果principal通过了认证,只是缺少足够的权限,象上述第7步那样),要么加载一个 AuthenticationEntryPoint (如果principal还没有被认证,那么我们要从第3步开始)。
AuthenticationEntryPoint 负责上述的第3步。如你所想,每个web应用都有一个默认的认证策略(象Acegi Security中几乎所有的东西一样,它也是可配置的,不过我们现在还是还是从简单开始)。每个主流的认证系统都有它自己的 AuthenticationEntryPoint实现,负责执行第3步中描述的动作。
当浏览器确定要发送你的认证信息(HTTP 表单或者HTTP header),服务器上需要有什么东西来“收集”这些认证信息。现在我们在上述的第6步。在Acegi Security中对从用户代理(通常是浏览器)收集认证信息有一个特定的名字,这个名字是“认证机制(authentication mechanism)”。当认证信息从客户代理收集过来以后,一个“认证请求(Authentication request)”对象被创建,并发送到AuthenticationProvider。
Acegi Security中认证的最后一环是一个AuthenticationProvider。非常简单,它的职责是取用一个认证请求 (Authentication request)对象,并且判断它是否有效。这个provider要么抛出一个异常,要么返回一个组装完毕的Authentication对象。还记得我 们的好朋友UserDetails 和 UserDetailsService吧?如果没有,回到前一部分重新回忆一下。大部分的AuthenticationProviders都会要求 UserDetailsService提供一个UserDetails对象。如前所述,大部分的应用程序会提供自己的 UserDetailsService,尽管有些会使用Acegi Security提供的JDBC或者 in-memory实现。作为成品的UserDetails 对象,特别是其中的GrantedAuthority[]s,在构建完备的Authentication对象时会被使用。
当认证机制 (authentication mechanism)取回组装完全的Authentication对象后,它将会相信请求是有效的,将Authentication放到 SecurityContextHolder中,并且将原始请求取回(上述第7步)。反之,AuthenticationProvider则拒绝请求,认 证机制(authentication mechanism)会请求用户重试(上述第2步)。
在讲述典型的认证流程的同时,有个好消 息是Acegi Security不关心你是如何把Authentication放到SecurityContextHolder内的。唯一关键的是在 AbstractSecurityInterceptor授权一个请求之前,在SecurityContextHolder中包含一个代表了 principal的Authentication。
你可以(很多用户确实)实现自己的过滤器(filter)或者MVC控制器 (controller)来提供和不是基于Acegi Security的认证系统交互。例如,你可能使用使用容器管理认证(Container Managed Authentication),从ThreadLocal 或者JNDI中获取当前用户信息,使得它有效。或者,你工作的公司有一个遗留的私有认证系统,而它是公司“标准”,你对它无能为力。在这种情况之下也是非 常容易让Acegi Security运作起来,提供认证能力。你所需要做的是写一个过滤器(或等价物)从某处读取第三方用户信息,构建一个Acegi Security式的Authentication对象,把它放到SecurityContextHolder中。这非常容易做,也是一种广泛支持的集成 方式。
acegi参考手册(v1.0.4)[译]-第二章 技术概览[下]
2.4. 安全对象
如果你熟悉AOP,你会知 道有很多种advice可用:before, after, throws 和 around。around advice非常有用,因为它能够选择是否选择是否执行一个方法调用,是否修改返回值,以及是否抛出异常。Acegi Security对方法调用和web请求都提供around advice。我们使用AOP联盟实现对方法调用的around advice,对于web请求的around advice则是使用标准的过滤器(Filter)。
对于那些不熟悉AOP的人来说, 关键是要理解Acegi Security能够帮助你保护方法调用以及web请求。大多数人对保护他们服务层的方法调用感兴趣。这是因为在当前的J2EE应用中,服务层包含了大多 数的业务逻辑(声明,作者不赞成这种设计,反而支持正确封装的领域模型以及DTO,assembly, facade 以及 transparent persistence patterns,而不是当前主流的贫血模型,我们将在这里讨论)。如果你需要保护service层的方法调用,使用标准的Spring AOP平台(或者被成为AOP 联盟(AOP Alliance))就足够了。如果你需要直接对领域模型进行保护,那么可以考虑使用AspectJ。
你 可以选择对使用AspectJ 或者AOP联盟(AOP Alliance)对方法进行授权,或者你可以选择使用过滤器(filter)来对web请求进行授权。你将0个,1个,2个或者3个这些方法一起使用。 主流的用法是执行一些web请求授权,以及在服务层使用AOP联盟(AOP Alliance)对一些方法调用授权。
Acegi Security使用“安全对象”(secure object)这个词来指任何能够将安全应用于其上的对象。每个Acegi Security支持的安全对象都有自己的类,它是AbstractSecurityInterceptor的子类。重要的一点是,如果一个 principal通过认证,当AbstractSecurityInterceptor执行的时候,SecurityContextHolder中要包 含一个有效的Authentication。
AbstractSecurityInterceptor提供一个固定的工作流程来处理 安全对象请求。这个工作流程包括查找和当前请求相关联的“配置属性(configuration attributes)”。配置属性(configuration attributes)可以被认为是对被AbstractSecurityInterceptor使用的类有特殊含义的字符串。他们通常针对 AbstractSecurityInterceptor使用XML进行配置。反正,AbstractSecurityInterceptor会询问 AccessDecisionManager “这是配置属性(configuration attributes),这是当前的认证对象(Authentication object),这是当前请求的详细信息-那么这个特定的principal可以执行这个特定的操作吗?”。
假如 AccessDecisionManager判定允许这个请求,那么AbstractSecurityInterceptor一般来说就继续执行请求。虽 然这样,用户在少数情况之下可能需要替换SecurityContext中的Authentication,可以通过 AccessDecisionManager调用一个RunAsManager来实现。在某些不常见的情形下这将非常有用,例如服务层的方法需要用另一种 标识(身份)来调用远程系统。这可能有所帮助,因为Acegi Security自动在不同的服务器之间传播安全标识(假设你正确配置了RMI或者HttpInvoker remoting protocol client)。
随着安全对象处理和返回-意味着方法调用完毕或者过滤器链(filter chain)处理完毕-AbstractSecurityInterceptor有最后的机会来处理调用。这时, AbstractSecurityInterceptor可能会修改返回的对象。我们可能要这样做,因为授权判断不能在安全对象调用途中执行。由于高度的 可插拔性,如果需要AfterInvocationManager将控制权交给AfterInvocationManager来实际修改对象。这个类甚至 可以彻底替换对象,或者抛出异常,或者根本不修改它。
因为是AbstractSecurityInterceptor中心模版类,看起来第一副插图该献给它。(译注:原手册里的图画的太丑陋了,我用jude重新画了一遍)
<!--[if gte vml 1]><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> <v:stroke joinstyle="miter" /> <v:formulas> <v:f eqn="if lineDrawn pixelLineWidth 0" /> <v:f eqn="sum @0 1 0" /> <v:f eqn="sum 0 0 @1" /> <v:f eqn="prod @2 1 2" /> <v:f eqn="prod @3 21600 pixelWidth" /> <v:f eqn="prod @3 21600 pixelHeight" /> <v:f eqn="sum @0 0 1" /> <v:f eqn="prod @6 1 2" /> <v:f eqn="prod @7 21600 pixelWidth" /> <v:f eqn="sum @8 21600 0" /> <v:f eqn="prod @7 21600 pixelHeight" /> <v:f eqn="sum @10 21600 0" /> </v:formulas> <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /> <o:lock v:ext="edit" aspectratio="t" /> </v:shapetype><v:shape id="_x0000_i1025" type="#_x0000_t75" style='width:423pt; height:270.75pt'> <v:imagedata src="file:///C:\WINDOWS\TEMP\msohtml1\01\clip_image001.jpg" o:title="ch2-1" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
图1 关键“安全对象”模型
只有那些希望实现全 新的对请求进行截取截取和授权方式的开发者才需要直接使用安全对象。例如,可能构建一个新的安全对象安全调用一个消息系统。任何需要安全并且能够提供一种 截取调用的方式(例如AOP around advice semantics)的东西都可以成为安全对象。虽然如此,大部分的Spring应用都会只是透明应用当前支持的三种安全对象类型(AOP Alliance MethodInvocation, AspectJ JoinPoint 和 web request FilterInterceptor)。
2.5. 结论
恭喜!你已经获取了Acegi Security足够的概括性的图景来开始着手你的项目。我们探究了共享组件,认证过程,以及对“安全对象”的通用授权概念。手册中的余下部分你可能用到也可能用不到,可以按照任意顺序阅读。
acegi参考手册(v1.0.4)[译]-第三章 协助系统
第三章. 协助系统
本章介绍一些Acegi Security使用的附加和协助系统。那些和安全无关,但是包含在Acegi Security项目中的部分,将会在本章中讨论
3.1. 本地化
Acegi Security支持对终端客户可能会看到的异常信息进行本地化。如果你的应用是为英文用户设计的,那么你什么都不用做,因为Acegi Security的所有消息默认都是英文的。如果你要支持其他区域用户,那么本节包含了你所需要了解的所有东西。
包括认证失败或者访问被拒绝(授权失败)的所有异常消息都可以被本地化。提供给开发者或者系统部署人员的异常或者日志信息(包括错误的属性、接口不符、构造器错误、debug级日志)没有被本地化,它们硬编码在Acegi Security的代码中。
在acegi -security-xx.jar(译注:xx代表版本号)的org.acegisecurity包中包含了一个 messages.properties文件。这个文件会被你的application context引用,因为Acegi Security实现了Spring的MessageSourceAware接口,它期待在application context启动的时候注入一个message resolver。通常你所需要做的是在你的application context中注册一个引用这个消息的bean,如下所示:
xml 代码
1. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
2. <property name="basename"><value>org/acegisecurity/messages<!--</span-->value><!--</span-->property>
3. <!--</span-->bean>
messages.properties是按照资源包标准命名的,它代表了Acegi Securtiy支持的默认语言。文件默认是英文的。如果你不注册一个消息源,Acegi Security仍然可以正常工作,它会用回硬编码的英文消息。
如 果你想定制messages.properties文件,或者支持其他语言,那么你应该copy这个文件,然后重命名,并在上述的bean定义中 注册。因为文件中的key并不多,因此本地化花不了多少工夫。如果你针对消息文件进行了本地化,那么请和社区分享,你可以添加一个JIRA任务,将你正确 命名的messages.properties本地化文件作为附件添加。
为了完善关于本地化的讨论需要知道Spring的 ThreadLocal org.springframework.context.i18n.LocaleContextHolder。你应该为每个用户设置代表他区域的 LocaleContextHolder。Acegi Security会尝试从这个ThreadLocal中获取的Locale来从消息源中获取消息。请参考Spring的文档以获取更多使用 LocaleContextHolder和能够帮你自动设置它的辅助类(例如
AcceptHeaderLocaleResolver, CookieLocaleResolver, FixedLocaleResolver, SessionLocaleResolver 等)的详细信息。
3.2. Filters
正如你在整个手册中看到的那样,Acegi Security使用很多filter。你可以使用FilterToBeanProxy或者FilterChainProxy来确定这些是怎样加入到你的web应用中的,下面我们来看看。
大部分filter使用FilterToBeanProxy来配置。例如下面web.xml中配置所示:
xml 代码
1. <filter>
2. <filter-name>Acegi HTTP Request Security Filter</filter-name>
3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
4. <init-param>
5. <param-name>targetClass</param-name>
6. <param-value>org.acegisecurity.ClassThatImplementsFilter</param-value>
7. </init-param>
8. </filter>
注 意在web.xml中的filter实际上是一个FilterToBeanProxy,而不是真正实现filter逻辑的filter。 FilterToBeanProxy所作的是代理Filter的方法到一个从Spring的application context 获取的bean。这使得这个bean可以享受Spring application context的生命周期支持以及配置灵活性。这个bean必须实现javax.servlet.Filter。
FilterToBeanProxy 只需要一个简单的初始化参数,targetClass或者targetBean。targetClass会定位 application context中指定的类的第一个对象,而FilterToBeanProxy按照bean的名字定位对象。象标准的Spring web应用一样,FilterToBeanProxy使用 WebApplicationContextUtils.getWebApplicationContext(ServletContext)来访问 application context,所以你应该在web.xml中配置一个ContextLoaderListener。
在IoC 容器而不是servlet容器中部署Filter会有一个生命周期的问题。特别是,哪个容器应该负责调用Filter的"startup" 和 "shutdown"方法?注意到Filter的初始化和析构顺序随servlet容器不同而不同,如果一个Filter依赖于由另一个更早初始化的 Filter的配置,这样就会出现问题。另一方面,Spring IoC具备更加完善的生命周期/IoC接口(例如InitializingBean, DisposableBean, BeanNameAware, ApplicationContextAware以及其他许多)以及一个容易理解的接口契约(interface contract),可预见的方法调用顺序,自动装配支持,以及可以避免实现Spring接口的选项(例如Spring XML中的destroy-method 属性)。因此,我们推荐尽可能使用Spring生命周期服务而不是servlet容器生命周期服务。FilterToBeanProxy默认不会将 init(FilterConfig) 和 destroy()方法委派到被代理的bean。如果你需要这些调用被委派,那么将lifecycle初始化参数设置为servlet- container-managed。
我们强烈推荐你使用FilterChainProxy而不是FilterToBeanProxy。虽然 FilterToBeanProxy是一个非 常有用的类FilterToBeanProxy,问题是当web.xml中filter变多时, 和 项就会太多而变得臃肿不堪。为了解决这个问题,Acegi Security提供一个FilterChainProxy类。它在FilterToBeanProxy中被装配(正如上面例子中所示),但目标类 (target class)是org.acegisecurity.util.FilterChainProxy。这样过滤器链(filter chain)可以在application context中按照如下代码配置:
xml 代码
1. <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
2. <property name="filterInvocationDefinitionSource">
3. <value>
4. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
5. PATTERN_TYPE_APACHE_ANT
6. /webServices/*=httpSessionContextIntegrationFilterWithASCFalse,basicProcessingFilter,exceptionTranslationFilter,
7. /*=httpSessionContextIntegrationFilterWithASCTrue,authenticationProcessingFilter,exceptionTranslationFilter,filterSecurityInterceptor
8. <!--</span-->value>
9. <!--</span-->property>
10. <!--</span-->bean>
你 可能注意到FilterSecurityInterceptor定义方式的相似之处。同时支持正则表达式和Ant Paths格式,越对应的URI越早出现。在运行时,FilterChainProxy会定位符合当前的web请求的第一个URI模式。每个对应的配置属 性代表了在application context中定义的一个bean的名字。接着fiter会按照它们被指定的顺序,按照FilterChain的标准行为模式被调用(如果一个 Filter决定停止处理,它可以不在chain中执行)。
如你所见,FilterChainProxy需要为不同的请求模式重复配置 filter的名字(在上面的例子中,, exceptionTranslationFilter 和 filterSecurityInterceptor 是重复的)。这样的设计是为了让FilterChainProxy能够为不同的URI配置不同的filter调用顺序,同时也提高了表达力(针对正则表达 式、Ant Paths、以及任何FilterInvocationDefinitionSource的特定实现)和清晰度,可以知道是哪个filter应该被调用。
你可能注意到了我们在filter chain定义了两个HttpSessionContextIntegrationFilter (ASC是allowSessionCreation的缩写,是HttpSessionContextIntegrationFilter的一个属性)。 因为web服务不会为将来的请求提供一个jsessionid,为这样的用户创建HttpSessions是浪费的。如果你有一个需要最大限度的伸缩性的 高容量的应用,我们建议你使用上述的方法。对于小的应用,使用单一的HttpSessionContextIntegrationFilter (默认的allowSessionCreation设为true)应该足够了。
说到生命周期问题,如果对FilterChainProxy自 身调用init(FilterConfig) 和 destroy()方法,它会把它代理到底层的filter。这样FilterChainProxy保证只初始化和析构每个filter一次,不论它在 FilterInvocationDefinitionSource中定义了多少次。你可以通过FilterToBeanProxy的lifecycle 初始化参数来控制这些方法是否被调用。如上面所讨论的那样,默认所有servlet容器生命周期调用是不被代理到FilterChainProxy的。
在web.xml中定义的filter的顺序是非常重要的。不管你实际用到哪个filter,的顺序应该是如下所示的:
1.ChannelProcessingFilter,因为可能要重定向到另一种协议。
2.ConcurrentSessionFilter 因为不使用任何SecurityContextHolder的功能,但是需要更新SessionRegistry来表示当前的发送请求的principal。
3. HttpSessionContextIntegrationFilter, 这样当一个web请求开始的时候就可以在SecurityContextHolder中设置一个SecurityContext,当web请求结束的时候 任何对SecurityContext的改动都会被copy到HttpSession(以备下一个web请求使用)。
4. Authentication processing mechanisms - AuthenticationProcessingFilter, CasProcessingFilter, BasicProcessingFilter, HttpRequestIntegrationFilter, JbossIntegrationFilter 等 - 修改SecurityContextHolder,使其中包含一个有效的认证请求令牌(token)。
5.SecurityContextHolderAwareRequestFilter, 如果你使用它来在你的servlet容器中安装一个Acegi Security aware HttpServletRequestWrapper。
6. RememberMeProcessingFilter, 如果早期的认证处理过程没有更新SecurityContextHolder,并且请求(request)提供了一个cookie启用remember- me服务,一个合适的被记住的Authentication对象会被放到SecurityContextHolder那里。
7.AnonymousProcessingFilter, 如果早期的认证处理过程没有更新SecurityContextHolder,, 一个匿名Authentication 对象会被放到SecurityContextHolder那里。
8.ExceptionTranslationFilter, 捕获所有的Acegi Security 异常,这样要么返回一个HTTP错误响应或者加载一个对应的AuthenticationEntryPoint。
9.FilterSecurityInterceptor, 保护 web URIs
所 有上述的filter使用FilterToBeanProxy或FilterChainProxy。建议在一个应用中使用一个单个的 FilterToBeanProxy代理到一个单个的FilterChainProxy。,在FilterChainProxy中定义所有的Acegi Security Filters。如果你使用SiteMesh,确保Acegi Security filters 在 SiteMe**ers调用前调用。这样使SecurityContextHolder在SiteMesh decorator使用前能够
acegi参考手册(v1.0.4)[译]-第四章 信道安全
第四章. 信道安全
4.1. 概述
Acegi Security不仅能满足你的认证和授权的请求,而且能够保证你的未认证的web请求也能拥有某些属性。这些属性可能包括使用特定的传输类型,在HttpSession设置特定的属性等等。Web请求的最普遍的需求是使用特定的传输协议,例如HTTPS。
在 传输安全中的一个重要议题就是会话劫持(session hijacking)。Web容器通过一个jsessionid来引用一个HttpSession,这个jsessionid通过cookie 或者URL重写转向(URL rewriting)发送到到客户端。如果jsessionid是通过HTTP发送的,那么就存在被劫持以及在认证过程之后冒充被认证用户的可能。这是因 为大部分的web容器为特定的用户维护同一个会话标识符,即便是用户从HTTP 切换到 HTTPS页面。
如果对于你的特定应用来说,会话劫 持(session hijacking)是一个很严重的风险,那么唯一的解决方法就是对每一个请求都使用HTTPS。这意味着jsessionid不会使用非安全信道传输。 你要保证你的web.xml中定义,把它指向一个HTTPS位置,同时应用程序不把用户指向一个HTTP位置。 Acegi Security提供一个解决方案帮助你实现后者。
4.2. 配置
启用Acegi Security的信道安全服务,需要在web.xml中增加如下行:
xml 代码
1. <filter>
2. <filter-name>Acegi Channel Processing Filter</filter-name>
3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
4. <init-param>
5. <param-name>targetClass</param-name>
6. <param-value>org.acegisecurity.securechannel.ChannelProcessingFilter</param-value>
7. </init-param>
8. </filter><filter-mapping>
9. <filter-name>Acegi Channel Processing Filter</filter-name>
10. <url-pattern>/*</url-pattern>
11. </filter-mapping>
和平时一样,你同样需要在application context中配置filter
java 代码
1. "channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter">
2. "channelDecisionManager">"channelDecisionManager"/>
3. "filterInvocationDefinitionSource">
4.
5. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
6. \A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
7. \A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL
8. \A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL
9. \A.*\Z=REQUIRES_INSECURE_CHANNEL
10.
11.
12.
13.
14. "channelDecisionManager" class="org.acegisecurity.securechannel.ChannelDecisionManagerImpl">
15. "channelProcessors">
16.
17. "secureChannelProcessor"/>
18. "insecureChannelProcessor"/>
19.
20.
21.
22.
23. "secureChannelProcessor" class="org.acegisecurity.securechannel.SecureChannelProcessor"/>
24.
25. "insecureChannelProcessor" class="org.acegisecurity.securechannel.InsecureChannelProcessor"/>
ChannelProcessingFilter和FilterSecurityInterceptor一样支持Apache Ant style paths。
ChannelProcessingFilter 的工作方式是过滤所有的web请求,并将判断将适合的配置属性应用于其上。然后它代理到 ChannelDecisionManager。默认的实现类ChannelDecisionManagerImpl应该能够满足大多数需求。它就代理到 配置好的ChannelProcessor实例列表。ChannelProcessor会检视请求,如果它不满意请求(例如请求是发送自不正确的传输协 议)它将会重定向,抛出异常或者采取其他任何恰当的措施。
Acegi Security 包括ChannelProcessor两个实体类实现:SecureChannelProcessor 保证配置了REQUIRES_SECURE_CHANNEL 属性的请求都是从HTTPS发送过来的。而InsecureChannelProcessor 保证配置了REQUIRES_INSECURE_CHANNEL 属性的请求都是从HTTP发送过来的。如果没有使用请求的协议,这两个实现都会转到ChannelEntryPoint,而两个 ChannelEntryPoint 实现所作的就是简单的把请求相应按照HTTP 和 HTTPS重定向。
要注意重定向是绝对(例如 http://www.company.com:8080/app/page ) 而不是相对的(例如 /app/page)。在测试中发现Internet Explorer 6 Service Pack 1 有一个bug,因此如果在重定向的时候也改变使用的端口,它就不能正确响应。对应这个bug,在很多Acegi Security bean中都会使用的PortResolverImpl也使用绝对URL。请参阅PortResolverImpl的JavaDoc以获取更多信息。
你 要注意使用为了在登录过程中保证用户名和密码的安全,要使用安全信道。如果你配合基于表单的登录使用 ChannelProcessingFilter,请记得一定要把你的登录页面设置为REQUIRES_SECURE_CHANNEL,并且 AuthenticationProcessingFilterEntryPoint.forceHttps属性设置为true。
4.3. 结论
一 旦配置好了,使用安全信道是非常简单的。只要请求页面,不用管使用什么协议(HTTP 或 HTTPS)或什么端口(80, 8080, 443, 8443等)。显然你只要确定初始请求(获取通过在web.xml 中的 或一个众所周知的主页URL),完成以后filter会执行你application context定义的重定向。
你也可以在ChannelDecisionManagerImpl中增加自己的ChannelProcessor实现。例如,你可能通过"输入图片中的内容"检测到一个个人类用户,然后在HttpSession中设置一个属性。
要判断一个安全检查应该是或者ChannelProcessor或是 AccessDecisionVoter 记得前者是设计用来处理认证或者未认证的请求,而后者是设计用来处理已认证的请求。因此后者可以访问已认证的principal被授予的权限。
另 外,ChannelProcessor检测到问题后一般是引发一个HTTP/HTTPS重定向这样他的请求可以被满足,而 AccessDecisionVoter将则会跑出一个AccessDeniedException异常(取决于支配的 AccessDecisionManager)。
acegi参考手册(v1.0.4)[译]-第五章 标签库
5.1. 概述
Acegi Security带有若干个可以降低JSP编写难度的JSP标签库。标签库的标志是authz,它提供了一些各不相同的服务。
5.2. 配置
所 有的标签库类都包含在acegi-security-xx.jar文件中,authz.tld在JAR的META-INF目录。这意味着如果在 JSP1.2以上的wb容器中,你只要把JAR文件放到WEB-INF/lib目录它就可用了。如果你使用JSP1.1容器,你需要在web.xml文件 中声明JSP标签库,并在WEB-INF/lib中包含authz.tld文件。将如下片段加入到web.xml中:
java 代码
1.
2. http://acegisecurity.org/authz
3. /WEB-INF/authz.tld
4.
第六章. 通用认证服务
6.1. Mechanisms, Providers 和 Entry Points
如果你使用Acegi Security提供的认证方法,那么通常你需要配置一个web filter,一个AuthenticationProvider
以及AuthenticationEntryPoint。在本节我们将要浏览一个示例应用,它需要支持基于form的认证(例如提供给用户登录的HTML页面)以及基础认证(例如web service或者类似的可以访问受保护资源)。
在web.xml中,这个应用需要一个单独的Acegi Security filter来使用FilterChainProxy。几乎所有的Acegi Security应用都有一个类似的项,看起来象下面这样:
xml 代码
1. <filter>
2. <filter-name>Acegi Filter Chain Proxy</filter-name>
3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
4. <init-param>
5. <param-name>targetClass</param-name>
6. <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
7. </init-param>
8. </filter>
9. <filter-mapping>
10. <filter-name>Acegi Filter Chain Proxy</filter-name>
11. <url-pattern>/*</url-pattern>
12. </filter-mapping>
上 述声明将使每个web请求都要经过Acegi Security的FilterChainProxy。正如在本手册的filter那节中所说,FilterChainProxy是一个通用类,它使得 web请求按照URL模式被发送到不同的filter。那些被委派的filter是由application context管理的,因此它们可以享受依赖注射的好处。我们来看看在你的application context中FilterChainProxy的定义会是什么样的:
xml 代码
1. <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
2. <property name="filterInvocationDefinitionSource">
3. <value>
4. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
5. PATTERN_TYPE_APACHE_ANT
6. /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,</value>
7. </property>
8. </bean>
在内部,Acegi Security会使用PropertyEditor来将上述XML片段中的字符串转化为一个 FilterInvocationDefinitionSource对象。在这个阶段需要注意的是,一系列的filter会按照定义的顺序运行,并且这些 filter实际就是application context中的bean的。所以,在我们的例子中,会在application context出现另外一些bean,它们会被命名为httpSessionContextIntegrationFilter, logoutFilter 等。Filter出现的顺序会在手册中filter那一节讨论,虽然上述的例子中它们是正确的。
在我们的 例子中,我们使用了AuthenticationProcessingFilter和BasicProcessingFilter。它们分别对应了基于 form的认证和BASIC HTTP header-based认证的“认证机制”(我们在手册的前面部分讨论了认证机制扮演的角色)。如果你既不使用form也不使用BASIC认证,就不需 要定义这些bean了。取而代之的是你要定义对应你所需要的认证环境的filter,例如DigestProcessingFilter 或者CasProcessingFilter。请对照手册中对应的章节来了解如何配置这些认证机制。
让我们回忆一下,在 HttpSessionContextIntegrationFilter中保存了每个HTTP session调用中的SecurityContext。这意味着认证机制只会在principal最初尝试认证的时候被使用一次。在余下的时间内,认证 机制只是静静的待在那里,将请求发往filter链中的下一个filter。这个基于实际的需求源于这样的一个事实,很少有认证实现在每一个,每一次的调 用的时候都会进行认证(BASIC认证是一个值得注意的例外),但是如果一个pricipal在最初的认证步骤之后帐号被取消了,或者被禁用了,或者被修 改了(例如GrantedAuthority[]中增加或者减少)会怎么样呢?让我们来看看现在这些情况是如何处理的。
前面已经介绍 了安全对象的主要认证provider AbstractSecurityInterceptor。这个类需要能够访问一个AuthenticationManager。它同时有个可选配置可以 设定一个认证对象每次安全对象调用的时候是否需要重新认证。如果Authentication.isAuthenticated()返回true,那么它 默认在SecurityContextHolder中的认证对象是已认证的。这样做对于提高性能是非常好的,但是对于即时的认证验证是不理想的。在这样的 情况下你可能需要将AbstractSecurityInterceptor.alwaysReauthenticate属性设置为true。
你 可能会问自己“这个AuthenticationManager是什么?”我们之前没有见过它,但是我们曾经讨论过 AuthenticationProvider的概念。非常简单,AuthenticationManager负责在 AuthenticationProvider链之间传递请求。它非常象我们之前讨论过的filter链,虽然有一些不同。Acegi Security只提供了一个AuthenticationManager实现,因此让我们看看对于我们这章的例子,它是如何配置的:
xml 代码
1. <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
2. <property name="providers">
3. <list>
4. <ref local="daoAuthenticationProvider"/>
5. <ref local="anonymousAuthenticationProvider"/>
6. <ref local="rememberMeAuthenticationProvider"/>
7. </list>
8. </property>
9. </bean>
在这个时候,可能值得提到的是你的认证机制(通常是filter)也被注入了一个AuthenticationManager的引用。所以和认证机制都会使用上述的ProviderManager来轮询一系列的AuthenticationProvider。
在 我们例子中有三个provider。它们按照上述的顺序调用(使用list而不是set来显示是按照顺序调用的),每个provider都能够尝试认证, 或者仅仅返回一个null来跳过认证。如果所有的实现都返回null,ProviderManager会抛出一个相应的异常。如果你想了解更多 chaining providers的信息,请参阅ProviderManager的JavaDoc。
authentication mechanism使用的那些provider有时候是可以互换的,而有时候它们又依赖于特定的authentication mechanism。例如,DaoAuthenticationProvider只需要一个基于字符串的用户名和密码。若干个认证机制会产生基于字符串的 用户名和密码的集合,包括(但不限于)BASIC 和 form 认证。同时,有些认证机制会产生一个只能和特定类型的AuthenticationProvider交互的认证请求对象。一个这种一对一映射的例子是JA -SIG CAS,它使用service ticket的概念,只能被Common Authentication Services CasAuthenticationProvider认证。一个更加深入的一对一映射的例子是LDAP认证机制,它只能由 LdapAuthenticationProvider处理。这种特定的对应关系在每个类的JavaDoc以及在本手册的特定认证方法章节中有详细说明。 你不用担心这些实现的细节,因为如果你忘记注册一个合适的provider,你在尝试认证时只会收到一个 ProviderNotFoundException异常。
当你在FilterChainProxy中正确配置了认证机制,并且确保 注册了对应的AuthenticationProvider,你的最后一步是配置一个AuthenticationEntryPoint。回忆一下早先我 们讨论过的ExceptionTranslationFilter的角色,当一个基于HTTP的请求收到一个HTTP头或者一个HTTP重定向以开始认证 时它被使用。继续我们早先的例子:
xml 代码
1. <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
2. <property name="authenticationEntryPoint"><ref
3. local="authenticationProcessingFilterEntryPoint"/></property>
4. <property name="accessDeniedHandler">
5. <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
6. <property name="errorPage" value="/accessDenied.jsp"/>
7. </bean>
8. </property>
9. </bean>
10. <bean id="authenticationProcessingFilterEntryPoint"
11. class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
12. <property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
13. <property name="forceHttps"><value>false</value></property>
14. </bean>
注 意到ExceptionTranslationFilter需要两个协作者。第一个AccessDeniedHandlerImpl,使用一个 RequestDispatcher导向显示特定的访问拒绝的错误页面。我们使用forwad所以SecurityContextHolder中仍然保留 principal的详细信息,这些对于显示给用户来说是有用的(在Acegi Security的老版本中,我们依赖rervlet容器来处理403错误信息,它缺乏这个有用的上下文信息)。 AccessDeniedHandlerImpl同时将会将HTTP头设置为403,它是访问拒绝的正式错误代码。至于 AuthentionEntryPoint,这里设置如果一个未受认证的principal尝试执行一个受保护的操作时,我们需要执行那些动作。因为在我 们的例子中要使用基于form的认证,因此我们设定AuthenticationProcessinFilterEntryPoint以及登录页面的 URL。你的应用系统通常只需要一个entry point,并且大多数的认证方法都定义了自己特有的AuthenticationEntryPoint。每个认证方式所对应的特定entry point的详细情况会在本手册特定的认证方法章节中介绍。
6.2. UserDetails 和 Associated Types
正如在第一部分中提到的,大多数认证provider要用到UserDetails 和UserDetailsService 接口。后面那个接口只包含一个方法:
java 代码
1. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException,
2. DataAccessException;
返 回值UserDetails是一个接口,它提供了若干个getter保证返回非null值,例如用户名,密码,授予的权限以及用户是启用还是禁用状态。大 部分认证provider都会使用一个,即使它在认证判断过程中实际并不使用用户名和密码。通常这些provider只会使用返回的 UserDetails中的GrantedAuthority[]信息,因为有些系统(例如LDAP 或 X509 或 CAS)已经承担了实际的身份验证的责任。
Acegi Security提供了一个UserDetails的实体类实现-User。Acegi Security用户需要确定什么时候实现UserDetailsService以及返回什么样的UserDetails实体类。通常,直接使用User 类或者继承User类就可以了,尽管有一些特殊情况(例如 object relational mappers),需要用户从头写他们自己的UserDetails实现。这种情况也时有发生,用户只要返回他们正常的代表系统用户的领域对象就可以了。 特别是UserDetails经常被用来存储额外的principal相关属性(例如他们的电话号码以及email地址),这样它们可以很容易被web视 图使用。
特定的UserDetailsService实现起来是很简单的,它应该很容易由用户来选择持久化策略来获取认证信息。说到这里,Acegi Security确实包含了一些有用的基础实现,下面让我们看一下。
6.2.1. In-Memory 认证
虽 然用户可以创建一个定制的UserDetailsService实现来从一个持久化引擎中获取信息,很多应用不需要这种复杂性。特别是如果你正在进行快速 原型开发或者刚开始集成Acegi Security,当你不需要花费时间来进行数据库配置或者写UserDetailsService的实现。这种情况之下,你有一个简单的选择,就是配置 InMemoryDaoImpl实现。
xml 代码
1. <bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
2. <property name="userMap">
3. <value>
4. marissa=koala,ROLE_TELLER,ROLE_SUPERVISOR
5. dianne=emu,ROLE_TELLER
6. scott=wombat,ROLE_TELLER
7. peter=opal,disabled,ROLE_TELLER
8. </value>
9. </property>
10. </bean>
在 上面的例子中,userMap属性包含了每个用户的用户名,密码,一个授权列表以及一个可选的启用/禁用关键词。使用逗号分隔。用户名必须在等号的左侧, 密码必须在等号右侧第一个出现。启用和禁用关键词(大小写敏感)可以出现在第二个或者之后任意位置。剩余的字符串被看作是授予的权限,这些权钱被创建为 GrantedAuthorityImpl对象(仅供参考-大多数的应用不需要自定义的GrantedAuthority实现,所以使用默认的实现就可以 了)。注意如果一个用户没有密码及或没有被授予权限,该用户不会在in-memory 认证库中创建。
InMemoryDaoImpl 也提供了一个setUserProperties(Properties)方法,可以允许你用另一个Spring的配置好的bean或者一个外部的 properties文件来实例化属性。你可能要使用Spring的PropertiesFactoryBean,它在加载外部属性文件的时候非常有用。 这个setter可能对于有大量用户的应用,或者开发期配置变更有所助益,但是不要指望使用整个数据库来处理认证细节。
6.2.2. JDBC 认证
也 包括了一个从JDBC数据源获取认证信息的UserDetailsService。使用Spring内部的JDBC,避免了仅仅为了存储用户信息而使用复 杂的对象关系Common Authentication Services 映射(ORM)。如果你确实使用ORM工具,你可能要写一个定制的UserDetailsService来重用你已经创建的映射文件。回到 JdbcDaoImpl,下面是一个配置的例子:
xml 代码
1. <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
2. <property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>
3. <property name="url"><value>jdbc:hsqldb:hsql://localhost:9001</value></property>
4. <property name="username"><value>sa</value></property>
5. <property name="password"><value></value></property>
6. </bean>
7. <bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
8. <property name="dataSource"><ref bean="dataSource"/></property>
9. </bean>
10.
你 可能要修改上述的DriverManagerDataSource来使用不同的关系数据库管理系统。你还可以使用从JNDI获取的全局数据源,如上的常规 Spring选项。不论是使用什么数据库以及如何获取数据源,必须使用一个按照dbinit.txt中写明的数据库模式。你可以从Acegi Security网站下载这个文件。
如果你的默认数据库模式不能满足需要,JdbcDaoImpl提供了两个属性允许定制SQL语 句。如果需要进一步定制,你可以继承JdbcDaoImpl。请参考JavaDocs获取详情,不过请注意这个类并不是为了复杂的自定义继承而写的。如果 你的需求比较复杂(例如数据库结构比较特殊或者需要返回一个特定的UserDetails实现),那么你最好写自己的 UserDetailsService实现。Acegi Security提供的基础实现只是为了典型场景,并没有提供无限的配置灵活性。
6.3. 并行Concurrent Session 处理
Acegi Security能够限定次数防止一个principal多次并行认证到同一个应用。许多ISV利用这一点来加强授权管理,网管也喜欢这个特性因为可以防止一个用户名被重复使用。例如,你可以限制“Batman”用户从两个不同的session登录系统。
使用并行session支持,你需要在web.xml中增加如下内容:
xml 代码
1. <listener>
2. <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
3. </listener>
而 且,你需要在中FilterChainProxy增加 org.acegisecurity.concurrent.ConcurrentSessionFilter to your FilterChainProxy。ConcurrentSessionFilter需要两个属性,sessionRegistry用来指向一个 SessionRegistryImpl实例,expiredUrl指向一个session实效时显示的页面。
当一个 HttpSession开始或者结束的时候web.xml HttpSessionEventPublisher发送一个ApplicationEvent到Spring ApplicationContext。这很关键,因为它确保session终止的时候SessionRegistryImpl会收到通知。
你还要装配ConcurrentSessionControllerImpl并在ProviderManager中引用:
xml 代码
1. <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
2. <property name="providers">
3. <!-- your providers go here -->
4. </property>
5. <property name="sessionController"><ref bean="concurrentSessionController"/></property>
6. </bean>
7. <bean id="concurrentSessionController"
8. class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
9. <property name="maximumSessions"><value>1</value></property>
10. <property name="sessionRegistry"><ref local="sessionRegistry"/></property>
11. </bean>
12. <bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl"/>
6.4. 认证标签库
AuthenticationTag只是用来把principal的Authentication.getPrincipal()对象的属性显示到web页面。
下面的JSP片段展示了如何使用AuthenticationTag:
java 代码
1. "username"/>
这个标签将会显示pricipal的名字。这里我们假设Authentication.getPrincipal()是一个UserDetails对象,这在使用典型的DaoAuthenticationProvider时候的一般状况。
[转载自 dxjsunday]
凡是有该标志的文章,都是该blog博主Caoer(草儿)原创,凡是索引、收藏
、转载请注明来处和原文作者。非常感谢。