张昊

J-Hi(http://www.j-hi.net)

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  45 Posts :: 1 Stories :: 110 Comments :: 0 Trackbacks

首先描述最简单的身份验证后台的处理过程

1、 用户输入需要确认自己身份的信息,如账号与密码或许需要其它信息

2、 也许你希望提供多种有方式的验证形式,由系统选择最合适的那一种,如从数据库(DAO)或是通过身份验证中心的服务(ACS)统一管理等

3、 根据账号找到指定源中的用户、密码及所拥有的权限(如果没找到就抛出异常)等用户的身份信息,验证用户输入的信息与源中返回的信息是否一致(如密码)

4、 为了不至于每次都重复上面的过程,还要将验证后的信息缓存起来,如放到session

acegi结合分析

1、 用户提交后为了能进行上述的处理,acegi不是通过请求-影响模式,而是能过ServletFilter实现的。可以理解成这个身份验证的主入口,不同的处理机制采用不同的Filter,类名的规则为*ProcessingFilter,这些类全部都继承至javax.servlet.Filter接口。下面是几个常用的过滤器

模块

功能

BasicProcessingFilter

按照RFC1945处理基本的身份验证请求

CasProcessingFilter

处理耶鲁大学的中心身份验证服务器(CAS)许可

AnonymousProcessingFilter

对匿名身份的处理

AuthenticationProcessingFilter

处理类似于Servlet规范的j_security_checkHttp form post

Spring的配置过程中有些过滤器除了自身的信息外,还有可能需要切入点,以便切入到其它模块中的配置信息。如AuthenticationProcessingFilter验证不通过的处理页面或要采用的协议、CasProcessingFilter需要提交到CAS上的其他信息。它们的统一接口类为AuthenticationEntryPoint在此要注意的是,切入点与过滤器之间有着密切的联系与对应关系的。

2、 acegi为了实现多种方式的验证形式提供了AuthenticationManager接口,目的是对多种验证形式进行管理,并通过指定的验证形式返回用户的身份。所以上述的过滤器均会调用该接口的authenticate(Authentication authentication)方法以返回当前用户的身份信息。该接口主要的实现类是ProviderManager

AuthenticationProvider(身份验证供应器)接口有很多实现类,每个都致力于处理一个特定身份验证的具体实现,AuthenticationManager负责轮询AuthenticationProvider列表,并使用第一个能够处理给定的Authentication请求对象的AuthenticationProvider。身份验证供应器也就是上面说的验证形式。最常用的是DaoAuthenticationProvider,实现从数据库中获取用户的身份信息

3、 acegi中用户的身份信息存放到以Authentication接口的实现类的实例中。在介绍该接口之前先要了解一些关键术语

1) principal(主体)指可以执行操作的用户、服务或代理(agent),即身份验证的发启者

2) credential(凭证)指主体提供密码之类用于身份验证(authentication)的信息戳

3) authentication(身份验证)确定调用者身份的过程

4) authorization(授权)指决定哪个主体准许执行给定操作的过程

Authentication接口包含主体标示、主体的凭证以及主体所拥有的一组权限。

Authentication接口继承java.security.Principal接口,因此Authentication天生就要满足主体的类型。该接口实际上是上述几个关键词对象化的一个容器,它存储了身份验证的全部信息。由此会发现它与AuthenticationProvider(身份验证供应器)有着天然的联系,如对于数据库的供应器它只要记录用户的帐号和密码,但对于CAS它还可能还要记录服务器响应的列表,或是与认证相关的其它信息。因此该接口对应身份验证供应器有多个实现类。正因为于此,所有的实现类对于开发者来说几乎是隐藏在,可以理解成工具或是服务类

下面对Authentication接口中的方法做详细扩展说明

1) getPrincipal(获得主体)getCredential(获得凭证),它们的设置过程:无论何种验证形式用户都是通过用户输入帐号和密码开始的,过滤器会拦截用户的请求,由验证管理器(AuthenticationManager)找到合适的身份验证供应器(AuthenticationProvider)提供身份验证。在AuthenticationManager接口只有一个方法authenticate(…),而该方法的参数与返回类型均是Authentication。所以大多数过滤器在请求身份验证前总是创建一个简单的UsernamePasswordAuthenticationToken(该类是Authentication接口的子类)对象,将其作为AuthenticationManager.authenticate(…)方法中的参数,再由AuthenticationProvider依据自身的特点创建相应的Authentication接口子类的实例,因此身份验证的具体信息也是在这最后一步完成的,即是在AuthenticationProvider中被创建与赋值的。其中就包括主体与凭证。如对于数据库来说也是账号与密码。

2) getAuthorities()得到当前用户所拥有的全部权限。Acegi提供一个GrantedAuthority接口,用于对应应用系统中有真正意义的权限实现。常用的是GrantedAuthorityImpl,它存储主体的已获授权的String表示,即权限也就是字符串的表示。而GrantedAuthority仅对字符做一层简单的封装。

3) getDetails()获取详细资料,目的是得到当前用户的详细信息,如在应用系统可以是用户的一个Bean(所在的部门,真实姓名…)。为此Acegi提供了一个UserDetails接口.

l         isAccountNonExpired         帐号是否未过期

l         isAccountNonLocked           帐号是否未锁定

l         isCredentialsNonExpired密码是否未过期

l         isEnabled                             是否可用(激活)

我们会发现对于一个应用系统来说,用户的详细信息中除与身份验证相关的信息外还要很多其它的信息,如用户所在的部门、电话。为此acegi提供了另一个接口AuthenticationDetailsSource,它仅提供一个方法buildDetails(HttpServletRequest request)用以创建客户化的用户信息。在过滤器中在执行身份验证之前调用该方法。

4) isAuthenticated(),是否通过了身份验证

当通过主体(帐号)从源(DAOCASJAAS…)中返回的信息中创建出Authentication实例后,还要校验用户的凭证(密码)的一致性。对于提供了以PasswordEncoder接口为代表的一组密码编码器的实现

4、 在我们讨论对于身份验证(Authentication)对象的保存之前,先来说明一下对它的访问。为了确保任何对身份验证感兴趣的类型都可以访问它,acegi通过SecurityContextHolder(该类中提供的所有成员方法均是静态的)用来保持SecurityContextThreadLocal对象。

通过SecurityContext类图可以看出在它很简单只是对Authentication对象的一个引用,之所以又做了这样一层包装的目的是为了客户可以做任意的扩展。由于SecurityContextHolder引用的是线程ThreadLocal集合,而对于B/S系统来说,一个线程的生命期还是太短了,无法保证SecurityContext对象的信息在一个线程执行后不会被JVM垃圾回收。一般来说我们会将SecurityContext放到session中,但这样又无法保证在逻辑层中访问到session。对此acegi的解决方案是通过javax.servlet.Filter使不同与的访问范围与SecurityContextHolder做整合(session为例,每次请求过滤器都会从session中取得SecurityContext然后再将其赋到SecurityContextHolder)Acegi提供了多种整合过滤器,命名规则为*IntegrationFilter

模块

功能

HttpSessionContextIntegrationFilter

请求之间使用HttpSession存储SecurityContext

HttpRequestIntegrationFilter

HttpServletRequest.getUserPrincipal()获得身份验证,但在请求结束时不能将该身份验证写回该位置

JbossIntegrationFilter

Jbossjava:comp/env/security/subjectJNDI位置获取身份验证,但在请求结束时不能将该身份验证写回该位置

接下来再来讨论对Authentication的设置,当处理过滤器调用AuthenticationManager. Authentication(…)获得Authentication对象后,它会调用自身的成员方法successfulAuthentication(…)将其封装为SecurityContext并设置到SecurityContextHolder中。

注意successfulAuthentication(…)方法是在抽象类AbstractProcessingFilter中,而有一些处理过程器并没有继承该类,所以只有AbstractProcessingFilter的子类才会在当前线程中取得SecurityContext对象。

再来描述授权的后台的处理过程

1、 我们可能希望在三个地方做安全验证,即是否可以访问当前页面,是否可以调用当前方法,是否可以控制当前方法参数的对象域

2、 对于安全验证的判断可能希望提供不同的表决策略,如一票否决制,还是有一票同意就通过或是同意大于否决票时才通过。

3、 总之,所有需要的处理无非是在做这样的工作。当前用户所拥有的权限中,否则有(或匹配)与当前被调用者(页面、方法、域)所指定的权限

Acegi对于授权的技术实现

从技术上话,授权完全与日志一样是一个横切关注点,与具体业务没有任何关系。而Acegi只是借助SpringAOPjavax.servlet.Filter,对这授权方面的拦截。对于拦截器与其持有的对象Acegi提供如下实现:

拦截器

描述

持有的对象

描述

AbstractSecurityInterceptor

是所有拦截器的抽象父类,实际授权的全部验证过程均在该类beforeInvocation()afterInvocation()两个方法中完成,子类只是区别不同类型的安全对象。

FilterSecurityInterceptor

是一个web的过滤器,用于对页面(URL)的授权验证

FilterInvocation

仅是对简单crequestresponseFilterChain包装的实现

MethodSecurityInterceptor

用于验证Spring容器中bean的方法,并且需要使用Spring中的代理

MethodInvocation

AOP Alliance提供的被代理对象接口,运行时的实际对象类型是由Spring提供的ReflectiveMethodInvocationCglibMethodInvocation

AspectJSecurityInterceptor

该类是在指定切入点调用AspectJ

JoinPoint AspectJCallback

执行AspectJ的回调






AbstractSecurityInterceptor的执行过程:

首先查询应用于该调用的配置属性。如果没有任何配置属性,该调用被认为是公有的,并且继续进行该调用。如果找到配置属性,包含在SecurityContextHolder中的Authentication是通过AuthenticationManager验证的,并且请求AccessDecisionManager来批准该请求,如果成功RunAsManager可以替代该Authentication的标识,然后继续进行该调用。调用完成时,将通过更新SecurityContextHolder来包含实际的身份验证对象以清除RunAsManager的替代标识。最后调用AfterInvocationManager,如果定义了的话。

RunAsManager:该接口的主要作用成员方法是Authentication buildRunAs(Authentication authentication, Object object, ConfigAttributeDefinition config)目的是当执行特定操作时用于选择性的替换Authentication对象,该方法是在安全验证之后,安全对象执行之前被调用。在安全对象执行之后该方法对Authentication对象替换后被还原。

AfterInvocationManager:作用是修改从安全对象调用中返回的对象。这通常被用来过滤仅针对已包含、已验证元素的集合,或者如果信息是受保护的,转换实例变量。如果该主体没有针对返回对象的权限,它还可以抛出AccessDeniedException。主要的方法是

Object decide(Authentication authentication, Object object, ConfigAttributeDefinition config,        Object returnedObject) throws AccessDeniedException,它需要返回一个对象,然后该对象将变成安全对象调用的结果。轮询AfterInvocationProvider接口的具体类。

最后分析权限表决的原理,还是让我们虚拟这样一个场景:

1、 要求有两个表决器,1)判断当前用户是否为超级管理员,2)判断当前用户是否拥有当前所调用的方法或页面的权限

2、 如果其中任何一个表决器投出赞成票,就认为通过。这是一种表决策略

Acegi结合分析

2、表决策略作用是轮询所有的表决器,根据当前策略结合表决器的表决结果,做出最终判断结果。Acegi表决策略管理的接口是

1)      decide(…)根据当前用户所拥有的权限信息[authentication],拦截器所持有的对象[object]以及在配置文件中所持有对象所必要的权限s[config],轮询表决器做出决策。

2)      supports(ConfigAttribute)判断所有表决器是否有一个以上支持这种权限的表示形式

3)      supports(Class) 判断所有表决器是否有一个以上支持拦截器所持有的对象类型

后两个方法主要在是各种拦截器初始化时调用,以但Spring验证bean定义的合法性

对于表决策略来说最终可能会得到三个结果中的一个赞成、否决或是弃权(结果是由表决器结合策略得到的)。对于弃权的产生是由于拦截器所持有的对象在配置中没有设置任何权限。为此该接口的抽象子类提供allowIfAllAbstainDecisions开关,如果所有表决器投出的均为弃权票,表示没有通过授权则将该属性设为true,默认为false.

Acegi提供了多种表决策略

注意:一个表决器只能投一票

AffirmativeBased       有一个决策器投出赞成票就通过

ConsensusBased          赞成票大于否决票就通过,相同也算是没有通过授权

UnanimousBased        一票否决制,也就是只要有一个表决器投否决票就算不通过

因此对于上面的场景我们应该选择AffirmativeBased表决策略,在决策略下还要有两个表决器,下面介绍表决器

1

posted on 2011-03-31 19:07 张昊 阅读(1971) 评论(1)  编辑  收藏

Feedback

# re: 通过场景分析acegi的设计原理 2011-03-31 23:07 popoer
写得不错~  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: