Acegi框架介绍

    概述
    对于任何一个完整的应用系 统,完善的认证和受权机制是必不可少的。Acegi Security(如下简称Acegi)是一个能为基于Spring的企业应用提供强大而灵活安全访问控制解决方案的框架,Acegi已经成为 Spring官方的一个子项目,因此也称为Spring Security。它经过在Spring容器中配置一组Bean,充分利用Spring的IoC和AOP功能,提供声明式安全访问控制的功能。虽然,如今 Acegi也能够应用到非Spring的应用程序中,但在Spring中使用Acegi是最天然的方式。
Acegi能够实现业务对象方法级的安 全访问控制粒度,它提供了如下三方面的应用程序的安全:

? URL资源的访问控制
    如全部用户(包括其名用户)能够访问index.jsp登陆页面,而只有受权的用户能够访问/user/addUser.jsp页面。Acegi容许经过 正则表达式或Ant风格的路径表达式定义URL模式,让受权用户访问某一URL匹配模式下的对应URL资源。

? 业务 类方法的访问控制
    Spring容器中全部Bean的方法均可以被Acegi管理,如全部用户能够调用BbtForum#getRefinedTopicCount()方 法,而只有受权用户能够调用BbtForum#addTopic()方法。

? 领域对象的访问控制
    业务类方法表明一个具体的业务操做,好比更改、删除、审批等,业务类方法访问控制解决了用户是否有调用某种操做的权限,但并未对操做的客体(领域对象)进 行控制。对于咱们的论坛应用来讲,用户能够调用BbtForum#updateUser(User user)方法更改用户注册信息,但应该仅限于更改本身的用户信息,也即调用BbtForum#updateUser()所操做的User这个领域对象必 须是受限的。

    Acegi经过多个不一样用途的Servlet过滤器对URL资源进行保护,在请求受保护的URL资源前,Acegi的Servlet过滤器判断用户是否有 权访问目标资源,受权者被开放访问,而未未被受权者将被阻挡在大门以外。
    Acegi经过Spring AOP对容器中Bean的受控方法进行拦截,当用户的请求引起调用Bean的受控方法时,Acegi的方法拦截器开始工做,阻止未受权者的调用。 
    
    对领域对象的访问控制创建在对Bean方法保护的基础上,在最终开放目标Bean方法的执行前,Acegi将检查用户的ACL(Aeccess Control List:访问控制列表)是否包含正要进行操做的领域对象,只有领域对象被受权时,用户才可使用Bean方法对领域对象进行处理。此外,Acegi还可 以对Bean方法返回的结果进行过滤,将一些不在当前用户访问权限范围内的领域对象剔除掉——即传统的数据可视域范围的控制。通常来讲,使用Acegi控 制数据可视域未非理想的选择,相反经过传统的动态SQL的解决方案每每更加简单易行。

    从本质特性上来讲,Servlet过滤器就是最原始的原生态AOP,因此咱们能够说Acegi不但对业务类方法、领域对象访问控制采用了AOP技术方案, 对URL资源的访问控制也使用了AOP的技术方案。使用AOP技术方案的框架是使人振奋的,这意味着,开发者能够在应用程序业务功能开发完毕后,轻松地通 过Acegi给应用程序穿上安全保护的“铁布衫”。程序员

    Acegi体系结构
    乘飞机前须要经过安检,乘客必须提供身份证以验证其身份。在经过安检进入候机室后,国航、海航、南航等不一样航空公司的飞机陆续到达,但你只能登上机票上对 应航班的飞机。在登机后,只能坐在机票对应的座位上——你不能抢占他人的座位,你不能在座位上刻字留念、你不能要求空姐打开机窗……

    乘飞机的过程最能体现安全控制的流程,咱们能够从中找到身份认证、资源访问控制、领域对象安全控制的对应物:安检对应身份认证,登机对应资源访问控制而按 号就座则对应领域对象安全控制。
    Acegi经过两个组件对象完成以上安全问题的处理:AuthenticationManager(认证管理器)、 AccessDecisionManager(访问控制管理器),如图 1所示:web


图 1 Acegi体系结构正则表达式

    SecurityContextHolder是框架级的容器,它保存着和全部用户关联SecurityContext实 例,SecurityContext承载着用户(也称认证主体)的身份信息的权限信息, AuthenticationManager、AccessDecisionManager将据此进行安全访问控制。
   
    SecurityContext的认证主体安全信息在一个HTTP请求线程的多个调用之间是共享的(经过ThreadLocal),但它不能在多个请求之 间保持共享。为了解决这个问题,Acegi将认证主体安全信息缓存于HttpSession中,当用户请求一个受限的资源时,Acegi经过 HttpSessionContextIntegrationFilter将认证主体信息从HttpSession中加载到 SecurityContext实例中,认证主体关联的SecurityContext实例保存在Acegi容器级的 SecurityContextHolder里。当请求结束以后,HttpSessionContextIntegrationFilter执行相反的操 做,将SecurityContext中的认证主体安全信息从新转存到HttpSession中,而后从SecurityContextHolder中清 除对应的SecurityContext实例。经过HttpSession转存机制,用户的安全信息就能够在多个HTTP请求间共享,同时保证 SecurityContextHolder中仅保存当前有用的用户安全信息,其总体过程如图 2所示:数据库


图 2 SecurityContext在HttpSession和请求线程间的转交过程浏览器


    当用户请求一个受限的资源时,AuthenticationManager首先开始工做,它象一个安检入口,对用户身份进行核查,用户必须提供身份认证的 凭证(通常是用户名/密码)。在进行身份认证时,AuthenticationManager将身份认证的工做委托给多个 AuthenticationProvider。由于在具体的系统中,用户身份可能存储在不一样的用户信息安全系统中(如数据库、CA中心、LDAP服务 器),不一样用户信息安全系统须要不一样的AuthenticationProvider执行诸如用户信息查询、用户身份判断、用户受权信息获取等工做。只要 有一个AuthenticationProvider能够识别用户的身份,AuthenticationManager就经过用户身份认证,并将用户的授 权信息放入到SecurityContext中。

   当用户经过身份认证后,试图访问某个受限的程序资源时,AccessDecisionManager开始工做。 AccessDecisionManager采用民主决策机制判断用户是否有权访问目标程序资源,它包含了多个AccessDecisionVoter。 在访问决策时每一个AccessDecisionVoter都拥有投票权,AccessDecisionManager统计投票结果,并按照某种决策方式根 据这些投票结果决定最终是否向用户开放受限资源的访问。缓存

    重要组件类介绍
    每一个框架都有一些核心的概念,这些概念被固化为类和接口,成为框架的重要组件类。框架的管理类、操做类都在这些组件类的基础上进行操做。在进入Acegi 框架的具体学习前,有必要事先了解一下这些承载Acegi框架重要概念的组件类。
    首先,咱们要接触是UserDetails接口,它表明一个应用系统的用户,该接口定义了用户安全相关的信息,如用户名/密码,用户是否有效等信息,你可 以根据如下接口方法进行相关信息的获取:
    String getUsername():获取用户名; 
     String getPassword():获取密码; 
     boolean isAccountNonExpired():用户账号是否过时; 
     boolean isAccountNonLocked():用户账号是否锁定; 
     boolean isCredentialsNonExpired():用户的凭证是否过时; 
     boolean isEnabled():用户是否处于激活状态。
    当以上任何一个判断用户状态的方法都返回false时,用户凭证就被视为无效。
    UserDetails还定义了获取用户权限信息的方法:GrantedAuthority[] getAuthorities(),GrantedAuthority表明用户权限信息,它定义了一个获取权限描述信息(以字符串表示,如 PRIV_COMMON)的方法:String getAuthority()。安全


图 3 用户和权限服务器

    在未使用Acegi以前,咱们可能经过相似User、Customer等领域对象表示用户的概念,并在程序中编写相应的用户认证的逻辑。如今,你要作的一 个调整是让原先这些表明用户概念的领域类实现UserDetails接口,这样,Acegi就能够经过UserDetails接口访问到用户的信息了。 

    UserDetails可能从数据库、LDAP等用户信息资源中返回,这要求有一种机制来完成这项工做,UserDetailsService正是充当这 一角色的接口。UserDetailsService接口很简单,仅有一个方法:UserDetails loadUserByUsername(String username) ,这个方法经过用户名获取整个UserDetails对象。
Authentication 表明一个和应用程序交互的待认证用户,Acegi从相似于登陆页面、Cookie等处获取待认证的用户信息(通常是用户名密码)自动构造 Authentication实例。并发


图 4 Acegi的认证用户框架

    Authentication能够经过Object getPrincipal()获取一个表明用户的对象,这个对象通常能够转换为UserDetails,从中能够取得用户名/密码等信息。在 Authentication被AuthenticationManager认证以前,没有任何权限的信息。在经过认证以后,Acegi经过 UserDetails将用户对应的权限信息加载到Authentication中。Authentication拥有一个 GrantedAuthority[] getAuthorities()方法,经过该方法能够获得用户对应的权限信息。
    Authentication和UserDetails很容易被混淆,由于二者都有用户名/密码及权限的信息,接口方法也很相似。其实 Authentication是Acegi进行安全访问控制真正使用的用户安全信息的对象,它拥有两个状态:未认证和已认证。UserDetails是代 表一个从用户安全信息源(数据库、LDAP服务器、CA中心)返回的真正用户,Acegi须要将未认证的Authentication和表明真实用户的 UserDetails进行匹配比较,经过匹配比较(简单的状况下是用户名/密码是否一致)后,Acegi将UserDetails中的其它安全信息(如 权限、ACL等)拷贝到Authentication中。这样,Acegi安全控制组件在后续的安全访问控制中只和Authentication进行交 互。

    因为Acegi对程序资源进行访问安全控制时,必定要事先获取和请求用户对应的Authentication,Acegi框架必须为 Authentication提供一个“寓所”,以便在须要时直接从“寓所”把它请出来,做为各类安全管理器决策的依据。

    SecurityContextHolder就是Authentication容身的“寓所”,你能够经过 SecurityContextHolder.getContext().getAuthenication()代码获取Authentication。 细心观察一下这句代码,你会发如今SecurityContextHolder和Authentication之间存在一个getContext()中 介,这个方法返回SecurityContext对象。SecurityContext这个半路杀出来的程咬金有什么特殊的用途呢?咱们知道 Authentication是用户安全相关的信息,请求线程其它信息(如登陆验证码等)则放置在SecurityContext中,构成了一个完整的安 全信息上下文。SecurityContext接口提供了获取和设置Authentication的方法:
 Authentication getAuthentication()
 void setAuthentication(Authentication authentication)


图 5 认证用户信息存储器

    SecurityContextHolder是Acegi框架级的对象,它在内部经过ThreadLocal为请求线程提供线程绑定的 SecurityContext对象。这样,任何参与当前请求线程的Acegi安全管理组件、业务服务对象等均可以直接经过 SecurityContextHolder.getContext()获取线程绑定的SecurityContext,避免经过方法入参的方式获取用户 相关的SecurityContext。

    线程绑定模式对于大多数应用来讲是适合的,可是应用自己会建立其它的线程,那么只有主线程能够得到线程绑定SecurityContext,而主线程衍生 出的新线程则没法获得线程绑定的SecurityContext。Acegi考虑到了这些不一样应用状况,提供了三种绑定SecurityContext的 模式:
 SecurityContextHolder.MODE_THREADLOCAL:SecurityContext绑定到主线程,这是默认的模式;
 SecurityContextHolder.MODE_GLOBAL:SecurityContext绑定到JVM中,全部线程都使用同一个 SecurityContext;
 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL::SecurityContext绑定到主线程 及由主线程衍生的线程中。
    你能够经过SecurityContextHolder.setStrategyName(String strategyName)方法指定SecurityContext的绑定模式。

    用户认证过程
Acegi支持多种方式的用户认证:如典型的基于数据库的认证、基于LDAP的认 证、基于Yale中心认证等方式。不一样的认证环境拥有不一样的用户认证方式,如今咱们先抛开这些具体的细节,考察一下Acegi对受限资源进行访问控制的典 型过程:
    1.你点击一个连接访问一个网页;
    2.浏览器发送一个请求到服务器,服务器判断出你正在访问一个受保护的资源;
    3.若是此时你并未经过身份认证,服务器发回一个响应提示你进行认证——这个响应多是一个HTTP响应代码,抑或重定向到一个指定页面;
    4.根据系统使用认证机制的不一样,浏览器或者重定向到一个登陆页面中,或者由浏览器经过一些其它的方式获取你的身份信息(如经过BASIC认证对话框、一 个Cookie或一个X509证书);
    5.浏览器再次将用户身份信息发送到服务器上(多是一个用户登陆表单的HTTP POST信息、也多是包含认证信息的HTTP报文头);
    6.服务器判断用户认证信息是否有效,若是无效,通常状况下,浏览器会要求你继续尝试,这意味着返回第3步。若是有效,则到达下一步;
    7.服务器从新响应第2步所提交的原始请求,并判断该请求所访问的程序资源是否在你的权限范围内,若是你有权访问,请求将获得正确的执行并返回结果。否 则,你将收到一个HTTP 403错误,这意味着你被禁止访问。
    在Acegi框架里,你能够找到对应以上大多数步骤的类,其中ExceptionTranslationFilter、 AuthenticationEntryPoint、AuthenticationProvider以及Acegi的认证机制是其中的表明者。

    ExceptionTranslationFilter是一个Acegi的Servlet过滤器,它负责探测抛出的安全异常。当一个未认证用户访问服务器 时,Acegi将引起一个Java异常。Java异常自己对HTTP请求以及如何认证用户是一无所知 的,ExceptionTranslationFilter适时登场,对这个异常进行处理,启动用户认证的步骤(第3步)。若是已认证用户越权访问一个资 源,Acegi也将引起一个Java异常,ExceptionTranslationFilter则将这个异常转换为HTTP 403响应码(第7步)。可见,Acegi经过异常进行通信,
ExceptionTranslationFilter接收这些异常并做出相应的动 做。

    当ExceptionTranslationFilter经过Java异常发现用户还未认证时,它到底会将请求重定向哪一个页面以要求用户提供认证信息呢? 这经过咨询AuthenticationEntryPoint来达到目的——Acegi经过AuthenticationEntryPoint描述登陆页 面。

    当你的浏览器经过HTTP表单或HTTP报文头向服务器提供用户认证信息时,Acegi须要将这些信息收集到Authentication中,Acegi 用“认证机制”描述这一过程。此时,这个新生成Authentication只包含用户提供的认证信息,但并未经过认证。
AuthenticationProvider 负责对Authentication进行认证。AuthenticationProvider究竟如何完成这一过程呢?请回忆一下上节咱们所介绍的 UserDetails和UserDetailsService,大多数AuthenticationProvider经过 UserDetailsService获取和未认证的Authentication对应的UserDetails并进行匹配比较来完成这一任务。当用户认 证信息匹配时,Authentication被认为是有效的,AuthenticationProvider进一步将UserDetails中权限、 ACL等信息拷贝到Authentication。
当Acegi经过认证机制收集到用户认证信息并填充好Authentication 后,Authentication将被保存到SecurityContextHolder中并处理用户的原始请求(第7步)。

    你彻底能够抛开Acegi的安全机制,编写本身的Servlet过滤器,使用本身的方案构建Authentication对象并将其放置到 SecurityContextHolder中。也许你使用了CMA(Container
Managed Authentication:容器管理认证),CMA容许你从ThreadLocal或JNDI中获取用户认证信息,这时你只要获取这些信息并将其转换 为Authentication就能够了。

    安全对象访问控制
    Acegi称受保护的应用资源为“安全对象”,这包括URL资源和业务类方法。咱们知道在Spring AOP中有前置加强、后置加强、异常加强和环绕加强,其中环绕加强的功能最为强大——它不但能够在目标方法被访问前拦截调用,还能够在调用返回前改变返回 的结果,甚至抛出异常。Acegi使用环绕加强对安全对象进行保护。
    Acegi经过AbstractSecurityInterceptor为安全对象访问提供一致的工做模型,它按照如下流程进行工做:
    1. 从SecurityContext中取出已经认证过的Authentication(包括权限信息);
    2. 经过反射机制,根据目标安全对象和“配置属性”获得访问目标安全对象所需的权限;
    3. AccessDecisionManager根据Authentication的受权信息和目标安全对象所需权限作出是否有权访问的判断。若是无权访 问,Acegi将抛出AccessDeniedException异常,不然到下一步;
4. 访问安全对象并获取结果(返回值或HTTP响应);
5. AbstractSecurityInterceptor能够在结果返回前进行处理:更改结果或抛出异常。     Acegi称受保护的应用资源为“安全对象”,这包括URL资源和业务类方法。咱们知道在Spring AOP中有前置加强、后置加强、异常加强和环绕加强,其中环绕加强的功能最为强大——它不但能够在目标方法被访问前拦截调用,还能够在调用返回前改变返回 的结果,甚至抛出异常。Acegi使用环绕加强对安全对象进行保护。     Acegi经过AbstractSecurityInterceptor为安全对象访问提供一致的工做模型,它按照如下流程进行工做:     1. 从SecurityContext中取出已经认证过的Authentication(包括权限信息);     2. 经过反射机制,根据目标安全对象和“配置属性”获得访问目标安全对象所需的权限;     3. AccessDecisionManager根据Authentication的受权信息和目标安全对象所需权限作出是否有权访问的判断。若是无权访 问,Acegi将抛出AccessDeniedException异常,不然到下一步; 4. 访问安全对象并获取结果(返回值或HTTP响应); 5. AbstractSecurityInterceptor能够在结果返回前进行处理:更改结果或抛出异常。


图 6 AbstractSecurityInterceptor工做流程

    安全对象和通常对象的区别在于前者经过Acegi的“配置属性”进行了描述,如“/view.jsp=PRIV_COMMON”配置属性就将“ /view.jsp”这个URL资源标识为安全对象,它表示用户在访问/view.jsp时,必须拥有PRIV_COMMON这个权限。配置属性经过 XML配置文件,注解、数据库等方式提供。安全对象经过配置属性表示为一个权限,这样,Acegi就能够根据Authentication的权限信息获知 用户能够访问的哪些安全对象。
    根据安全对象的性质以及具体实现技术,AbstractSecurityInterceptor拥有如下三个实现类:
 FilterSecurityInterceptor:对URL资源的安全对象进行调用时,经过该拦截器实施环绕切面。该拦截器使用Servlet过滤器 实现AOP切面,它自己就是一个Servlet过滤器;
 MethodSecurityInterceptor:当调用业务类方法的安全对象时,可经过该拦截器类实施环绕切面;
 AspectJSecurityInterceptor:和MethodSecurityInterceptor相似,它是针对业务类方法的拦截器,只不 过它经过AspectJ实施AOP切面。

    Acegi版本升级的一些重大变化 
    Acegi项目开始于2003年,Acegi团队在发布新版本时很是谨慎,在本书写做之时,Acegi最新版本为1.0.3。在此以前Acegi已经发布 了10多个预览版本,因为Acegi框架优异的表现,许多大型应用早在Acegi 1.0正式版本发布以前(2006年5月),就已经采用Acegi框架做为其安全访问控制的解决方案。

    在Acegi社区里,来自世界各地众多优秀的安全领域专家对Acegi的改进和发展献计献策,Acegi团队普遍听取并吸取各类有益的建议,将它们融入到 Acegi的框架中,使Acegi成为构建在Spring基础上企业应用的首选安全控制框架。
Acegi 1.0.3版本相比于早期预览版本发生了很大的变化,对于须要进行Acegi版本的项目来讲,了解这一变化特别重要。下面,咱们列出Acegi的一些重大 的升级更新:
 包名的更新:在0.9.0及以前的版本中,Acegi采用net.sf.acegisecurity包名前缀,在1.0.0版本以后更改成 org.acegisecurity(Hibernate也走过相同的道路,好在Acegi在正式版本发布之时就完成了这种转变);

 ACL模块的调整:ACL模块发生了重大的调整,Acegi团队接收了社区大量关于ACL模块的反馈意见,从新设计了ACL模块的底层结构,在性能、封装 性、灵活性上获得了质的提高。事实上,Acegi使用org.acegisecurity.acls包代替了原来的 org.acegisecurity.acl包,后者将在后期的版本中删除,因为这种伤筋动骨的变化,将很难兼容原来ACL模块。不过,目前基于新框架的 ACL模块尚未进行充分的测试,Acegi承诺在1.1.0版本发布时提供最终的实现;

 删除了ContextHolder及其相关类:在Acegi 0.9版本中,ContextHolder及其相关类被完全从Acegi项目中删除。ContextHolder能够在多个HTTP请求中共享同一个 ThreadLocal,这和Spring提倡的ThreadLocal只应在同一线程中共享相悖。如今,Acegi使用 SecurityContextHolder替换ContextHolder,它的生命周期是一个HTTP 请求;

 使用FilterChainProxy同时代理多个过滤器:在早期的版本中,Acegi经过FilterToBeanProxy将web.xml中的 Servlet过滤器定义转移到Spring容器中。这比直接在web.xml中配置Servlet过滤器要方便一些,可是Acegi框架每每须要定义多 个Servlet过滤器,使web.xml配置文件变得冗长难看。在Acegi 0.8版本中提供FilterChainProxy,它能够同时代理多个Servlet过滤器并保证过滤器的顺序。所以在新版本 中,FilterChainProxy成为推荐的选择。

    小结
    Acegi是Spring项目下一个成熟的安全访问控制框架,它容许利用了Spring IoC的AOP的功能完成安全对象的访问控制。在Acegi框架中,SecurityContextHolder处于很是核心的位置,它是存放认证管理器 用户安全信息SecurityContext的“容器”,SecurityContext保存着用户安全访问控制所需的信息,直接被访问决策管理器使用。 HttpSessionContextIntegrationFilter经过在SecurityContextHolder和HttpSession中 摆渡SecurityContext,使多个请求线程能够共享同一个SecurityContext。

原文地址:https://www.oschina.net/question/12_8396
相关文章
相关标签/搜索