天天看点

(翻译)Spring Security-2.0.x参考文档”技术概述“

技术概述

5.1. 运行环境

Spring Security可以运行在标准的Java 1.4运行环境下。 它也支持Java 5.0,不过这部分代码单独打包起来,放到发布的,文件名是"tiger"前缀的JAR文件里。 因为Spring Security的目标是自己容器内管理,所以不需要为你的Java运行环境进行什么特别的配置。 特别是,不需要特别配置一个Java Authentication and Authorization Service (JAAS)政策文件,也不需要把Spring Security放到server的classLoader下。

上面这些设计,确保了发布时的最大轻便性,你可以简单把你的目标文件(JAR或WAR或EAR)从一个系统复制到另一个系统,它会立即正常工作。

5.2. 共享组件

让我们展示一些Spring Security中很重要的共享组件。 被成为"shared"的组件,是指它在框架中占有很重要的位置,框架离开它们就没法运行。 这些java类表达了维持系统的构建代码块,所以理解他们的位置是非常重要的,即使你不需要直接跟他们打交道。

5.2.1. SecurityContextHolder, SecurityContext 和 Authentication对象

最基础的对象就是SecurityContextHolder。 我们把当前应用程序的当前安全环境的细节存储到它里边了。 默认情况下,SecurityContextHolder使用ThreadLocal存储这些信息,这意味着,安全环境在同一个线程执行的方法一直是有效的,即使这个安全环境没有作为一个方法参数传递到那些方法里。 这种情况下使用ThreadLocal是非常安全的,只要记得在处理完当前主体的请求以后,把这个线程清除就行了。 当然,Spring Security自动帮你管理这一切了,你就不用担心什么了。

有些程序并不适合使用ThreadLocal,因为它们处理线程的特殊方法。 比如,swing客户端也许希望JVM里的usoyou线程都使用同一个安全环境。 为了这种情况,我们而已使用SecurityContextHolder.MODE_GLOBAL。 其他程序可能想让一个线程创建的线程也使用相同的安全主体。 这时可以使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL。 想要修改默认的SecurityContextHolder.MODE_THREADLOCAL模式,可以使用两种方法。 第一个是设置系统属性。 另一个是调用SecurityContextHolder的静态方法。 大多数程序不需要修改默认值,但是如果你需要做修改,先看一下SecurityContextHolder的JavaDoc中的详细信息。

我们把安全主体和系统交互的信息都保存在SecurityContextHolder中了。 Spring 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。

5.2.2. UserDetailsService

从上面的代码片段中还可以看出另一件事,就是你可以从Authentication对象中获得安全主体。 这个安全主体就是一个对象。 大多数情况下,可以强制转换成UserDetails对象。 UserDetails是一个Spring Security的核心接口。 它代表一个主体,是扩展的,而且是为特定程序服务的。 想一下UserDetails章节,在你自己的用户数据库和如何把Spring Security需要的数据放到SecurityContextHolder里。 为了让你自己的用户数据库起作用,我们常常把UserDetails转换成你系统提供的类,这样你就可以直接调用业务相关的方法了(比如getEmail(), getEmployeeNumber()等等)。

现在,你可能想知道,我应该什么时候提供这个UserDetails对象呢? 我怎么做呢? 我想你说这个东西是声明式的,我不需要写任何代码,怎么办? 简单的回答是,这里有一个特殊的接口,叫UserDetailsService。 这个接口里的唯一一个方法,接收String类型的用户名参数,返回UserDetails。 大多数验证提供器使用Spring Security代理UserDetailsService,作为验证过程的一部分。 这个UserDetailsService用来建立Authentication对象,保存在SecurityContextHolder里。 好消息是我们提供了好几个UserDetailsService实现,其中一个使用内存里的map,另一个使用JDBC。 虽然,大多数用户倾向于写自己的,使用这些实现常常放到已有的数据访问对象(DAO)上,表示它们的雇员,客户或其他企业应用中的用户。 记住这个优势,无论你用什么UserDetailsService返回的数据都可以通过SecurityContextHolder获得,就像上面的代码片段讲的一样。

5.2.3. GrantedAuthority

除了主体,另一个Authentication提供的重要方法是getAuthorities()。 这个方法提供了GrantedAuthority对象数组。 毫无疑问,GrantedAuthority是赋予到主体的权限。 这些权限通常使用角色表示,比如ROLE_ADMINISTRATOR 或 ROLE_HR_SUPERVISOR。 这些角色会在后面,对web验证,方法验证和领域对象验证进行配置。 Spring Security的其他部分用来拦截这些权限,期望他们被表现出现。 GrantedAuthority对象通常使用UserDetailsService读取的。

通常情况下,GrantedAuthority对象是应用程序范围下的授权。 它们不会特意分配给一个特定的领域对象。 因此,你不能设置一个GrantedAuthority,让它有权限展示编号54的Employee对象,因为如果有成千上网的这种授权,你会很快用光内存(或者,至少,导致程序花费大量时间去验证一个用户)。 当然,Spring Security被明确设计成处理常见的需求,但是你最好别因为这个目的使用项目领域模型安全功能。

最后,但不是最不重要的,优势你需要在HTTP请求之间共享SecurityContext. 其他时候,主体会在每个请求里重新验证,虽然大多数情况下它可以存储。 HttpSessionContextIntegrationFilter就是实现在HTTP请求之间存储SecurityContext的。 就像它的类名一样,HttpSession被用来保存这些信息。 你不应该因为安全的问题,直接与HttpSession打交道。 根本不存在这样做的理由-一直使用SecurityContextHolder作替代方式。

5.2.4. 小结

简单回顾一下,Spring Security主要是由一下几部分组成的:

*

SecurityContextHolder,提供几种访问SecurityContext的方式。

*

SecurityContext,保存Authentication信息,和请求对应的安全信息。

*

HttpSessionContextIntegrationFilter,为了在不同请求使用,把SecurityContext保存到 HttpSession里。

*

Authentication,展示Spring Security特定的主体。

*

GrantedAuthority,反应,在应用程序范围你,赋予主体的权限。

*

UserDetails,通过你的应用DAO,提供必要的信息,构建Authentication对象。

*

UserDetailsService,创建一个 UserDetails,传递一个 String类型的用户名(或者证书ID或其他)。

现在,你应该对这种重复使用的组件有一些了解了。 让我们贴近看一下验证的过程。

5.3. 验证

就像这篇指南开头提到的那样,Spring Security可在很多不同的验证环境下使用。 虽然我们推荐人们使用Spring Security,不与已存在的容器管理认证系统整合,但它也是支持的-使用你自己的属性验证系统进行整合。 让我们先看看Spring Security完全依靠自己,管理web安全,这里会演示最复杂和最常见的情况。

讨论一个典型的web应用验证过程:

1.

你访问首页,点击一个链接。

2.

向服务器发送一个请求,服务器判断你是否在访问一个受保护的资源。

3.

如果你还没有进行过认证,服务器发回一个响应,提示你必须进行认证。 响应可能是HTTP响应代码,或者是重新定向到一个特定的web页面。

4.

依据验证机制,你的浏览器将重定向到特定的web页面,这样你可以添加表单, 或者浏览器使用其他方式校验你的身份(比如,一个基本校验对话框,cookie,或者X509证书,或者其他)。

5.

浏览器会发回一个响应给服务器。 这将是HTTP POST包含你填写的表单内容,或者是HTTP头部,包含你的验证信息。

6.

下一步,服务器会判断当前的证书是否是有效的, 如果他们是有效的,下一步会执行。 如果他们是非法的,通常你的浏览器会再尝试一次(所以你返回的步骤二)。

7.

你发送的原始请求,会导致重新尝试验证过程。 有希望的是,你会通过验证,得到猪狗的授权,访问被保护的资源。 如果你有足够的权限,请求会成功。否则,你会收到一个HTTP错误代码403,意思是访问被拒绝。

Spring Security使用鲜明的类负责上面提到的每个步骤。 主要的部分是(为了使用他们)是ExceptionTranslationFilter, 一个AuthenticationEntryPoint, 一个验证机制,一个AuthenticationProvider。

5.3.1. ExceptionTranslationFilter

ExceptionTranslationFilter是一个Spring Security过滤器,用来检测是否抛出了Spring Security异常。 这些异常会被AbstractSecurityInterceptor抛出,它主要用来提供验证服务。 我们会在下一节讨论AbstractSecurityInterceptor,但是现在,我们只需要知道,它是用来生成Java异常,和知道跟HTTP没啥关系,或者如何验证一个主体。 而ExceptionTranslationFilter提供这些服务,使用特点那个的响应,返回错误代码403(如果主题被验证了,但是权限不足-在上边的步骤七),或者启动一个AuthenticationEntryPoint(如果主体没有认证,然后我们需要进入步骤三)。

5.3.2. AuthenticationEntryPoint

AuthenticationEntryPoint对应中上面列表中的步骤三。 如你所想的,每个web应用程序都有默认的验证策略(好的,这可以在Spring Security里配置一切,但是让我们现在保持简单)。 每个主要验证系统会有它自己的AuthenticationEntryPoint实现, 会执行动作,如同步骤三里的描述一样。

在你的浏览器决定提交你的认证证书之后(使用HTTP表单发送或者是HTTP头),服务器部分需要有一些东西来“收集”这些验证信息。 现在我们到了上述的第六步。 在Spring Security里,我们需要一个特定的名字,来描述从用户代码(通常是浏览器)收集验证信息的功能,这个名字就是“验证机制”。 在从用户代码哪里收集了验证细节之后,一个"Authentication 请求"对象会被AuthenticationProvider建立。

5.3.3. AuthenticationProvider

Spring Security认证过程的最后一个角色是AuthenticationProvider。 非常简单,这是跟获得Authentication请求对象相关的,决定它是否是有效的。 这个供应器或者抛出一个异常,或者返回一个完整的Authentication对象。 还记得我们的好朋友UserDetails和UserDetailsService吗? 如果不记得,回头看看前面的章节,刷新你的记忆。 大多数AuthenticationProvider都会要求UserDetailsService提供一个UserDetails对象。 像上面提到的那样,大多数程序会提供他们自己的UserDetailsService虽然一些可以使用Spring Security提供的JDBC和内存实现。 由此产生的UserDetails对象-特别是UserDetails中包含的GrantedAuthority[]-将被用来组装Authentication对象。

在验证机制重新获得了组装好的Authentication对象以后,他会认为请求有效,把Authentication放到SecurityContextHolder里,然后导致原始请求重审(第七步)。 另一方面,如果AuthenticationProvider驳回了请求,验证机制会让用户代码重试(第二步)。

5.3.4. 直接设置SecurityContextHolder的内容

虽然这表述了一个典型的验证流程,但是好消息是Spring Security不在意你如何把一个Authentication放到SecurityContextHolder里的。 唯一关键的需求是SecurityContextHolder包含Authentication,用来表现一个主体,在AbstractSecurityInterceptor之前验证请求的。

你可以(很多用户都这样做)写一个自己的过滤器或MVC控制器来提供验证系统的交互,这些都不是基于Spring Security的。 比如,你也许使用容器管理验证,从ThreadLocal或JNDI里获得当前用户信息。 或者,你的公司可能有一个遗留系统,它是一个企业标准,你不能控制它。 这种情况下,很容易让Spring Security工作,也能提供验证能力。 你所需要的就是写一个过滤器(或等价物)从指定位置读取第三方用户信息,把它放到SecurityContextHolder里。 实现这些很简单,这种整合是完全被支持的方法。

5.4. 安全对象

如果你熟悉AOP的话,就会知道有几种不同的拦截方式:之前,之后,抛异常和环绕。 其中环绕是非常有用的,因为advisor可以决定是否执行这个方法,是否修改返回的结果,是否抛出异常。 Spring Security为方法调用提供了一个环绕advice,就像web请求一样。 我们使用AOP联盟制作了一个方法调用的环绕advice,我们使用标准filter建立了对web请求的环绕advice。

对那些不熟悉AOP的人,需要理解的关键问题是Spring Security可以帮助你保护方法的调用,就像保护web请求一样。 大多数人对保护服务层里的安全方法非常按兴趣。 这是因为在目前这一代J2EE程序里,服务器放了更多业务相关的逻辑(需要澄清,作者不建议这种设计方法,作为替代的,而是应该使用DTO,集会,门面和透明持久模式压缩领域对象,但是贫血领域对象是当前的主流思路,我们会在这里讨论它)。 如果你只是需要保护服务层的方法调用,使用Spring标准AOP平台(被称作AOP联盟)就够了。 如果你想直接保护领域对象,你会发现AspectJ非常值得考虑。

可以选择使用AspectJ还是AOP联盟处理方法验证,或者你可以选择使用filter处理web请求验证。 你可以不选,选择其中一个,选择两个,或者三个都选。 主流的应用是处理一些web请求验证,再结合一些在服务层里的AOP联盟方法调用验证。

Spring Security使用"secure object"来表示任何需要安全管理的对象。 Spring Security支持的每个安全对象都有它自己的类,他们都是AbstractSecurityInterceptor的子类。 重要的是,在AbstractSecurityInterceptor运行的时候,如果主体通过了认证,SecurityContextHolder里就会包含一个合法的 Authentication。

AbstractSecurityInterceptor提供了一致的流程,来处理安全方法的请求。 这个流程包括查找分配给当前请求的"配置属性"。 这个"配置属性"可以被想像成一个字符串,对AbstractSecurityInterceptor有特殊含义。 通常使用XML配置你的AbstractSecurityInterceptor。 无论如何,AbstractSecurityInterceptor会告诉AccessDecisionManager“这里是配置属性,这里是当前Authentication对象,这里是当前Authentication对象,这里是当前请求的细节-然后,这个特定的主体,是否允许执行这个特定的操作吗?”

假设AccessDecisionManager决定允许执行这个请求,AbstractSecurityInterceptor会正常执行这个请求。 话虽如此,罕见情况下,用户可能需要把SecurityContext的Authentication换成另一个Authentication,通过访问RunAsManager。 这也许在,有原因,不常见的情况下有用,比如,服务层方法需要调用远程系统,表现不同的身份。 因为Spring Security自动传播安全身份,从一个服务器到另一个(假设你使用了配置好的RMI或者HttpInvoker远程调用协议客户端),就可以用到它了。

按照下面安全对象执行和返回的方式-可能意味着完全的方法调用或过滤器链的执行。 这种状态下AbstractSecurityInterceptor对有可能修改返回对象感兴趣。 你可能想让它发生,因为验证决定不能“关于如何在”一个安全对象调用。 高可插拔性,AbstractSecurityInterceptor通过控制AfterInvocationManager,实际上在需要的时候,修改对象。 这里类实际上可能替换对象,或者抛出异常,或者什么也不做。

因为AbstractSecurityInterceptor是中央模板类,更像第一时间为它服务。

关键"secure object"模型

Figure 5.1. 关键"secure object"模型

只有开发者才会关心使用全心的方法,进行拦截和验证请求,将直接使用安全方法。 比如,可能新建一个安全方法,控制对消息系统的权限。 安全需要的任何事情,也可以提供一种拦截的方法(好像AOP的环绕advice语法那样)有可能在安全对象里处理。 这样说的话,大多数Spring应用简单拥有三种当前支持的安全类型(AOP联盟的MethodInvocation,AspectJ JoinPoint和web请求FilterInterceptor)完全透明的。

5.5. 结论

祝贺你! 你已经看过了足够多的Spring Security的高级特性,着手于你的程序。 我们探讨了共享组件,验证是如何工作的,并审核了“安全对象”的正常验证过程。 参考指南的后面部分,可能也可能灭有满足你的特定要求,可以使用任何顺序阅读。

继续阅读