spring security

 

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对象。

你可以在你的应用程序中的任何地方使用下述的代码块:

Object obj =SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (obj instanceof UserDetails) {
    String username =((UserDetails)obj).getUsername();
} else {
    String username =obj.toString();
}

    上述的代码展示了一些有趣的联系和关键的对象。首先,你会注意到在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的基本组成构件是:

• SecurityContextHolder,提供对SecurityContext的所有访问方式。

• SecurityContext, 存储Authentication以及可能的请求相关的安全信息。

• HttpSessionContextIntegrationFilter, 在web请求之间把SecurityContext存储在HttpSession中。

• Authentication, 以Acegi Security的方式表现principal。

• GrantedAuthority, 表示赋予一个principal的应用范围的权限。

• UserDetails, 为从你的应用程序DAO中获取必要的信息来构建一个Authentication 对象。

• 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)"。当认证信息从客户代理收集过来以后,一个"认证请求(Authenticationrequest)"对象被创建,并发送到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中。这非常容易做,也是一种广泛支持的集成方式。

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重新画了一遍)

 

图1 关键"安全对象"模型

    只有那些希望实现全新的对请求进行截取截取和授权方式的开发者才需要直接使用安全对象。例如,可能构建一个新的安全对象安全调用一个消息系统。任何需要安全并且能够提供一种截取调用的方式(例如AOP around advice semantics)的东西都可以成为安全对象。虽然如此,大部分的Spring应用都会只是透明应用当前支持的三种安全对象类型(AOP Alliance MethodInvocation, AspectJ JoinPoint 和 web request FilterInterceptor)。

2.5. 结论

    恭喜!你已经获取了Acegi Security足够的概括性的图景来开始着手你的项目。我们探究了共享组件,认证过程,以及对"安全对象"的通用授权概念。手册中的余下部分你可能用到也可能用不到,可以按照任意顺序阅读。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值