5.1 核心组件

2016-06-09 13:27:48 8,889 0

在Spring Security 3.0中, spring-security-core jar中的内容被剥离成更小的部分。其不再包含任何web安全相关、LDAP和命名空间配置。我们将会看一下在这个核心组件中你可以发现的一些Java类。他们代表框架的基本组成部分,因此如果你希望可以在简单的命名空间配置的基础上可以更进一步,那么这对你更好的理解很重要,尽管事实上你不需要与其直接进行交互。

SecurityContextHolder, SecurityContext 和 Authentication 对象

最基础的对象是 SecurityContextHolder,这是我们在应用中存储当前安全细节的地方,包含了当前在应用中使用到的principal细节。默认请下, SecurityContextHolder 使用一个 ThreadLocal 对象来存储这些细节,这表示对于在同一个线程中执行的方法,安全上下文(security context)都是可用的,即使安全上下文没有显式的当做方法的参数进行传递。通过这种方式使用 ThreadLocal 是很安全的,如果当前使用到的规则需要在请求处理完之后被清空。当然,Spring Security帮你自动的处理了这些,你不需要考虑。

一些应用并不完全适用使用一个 ThreadLocal,这需要考虑使用线程完成工作的方式。例如,对于一个Swing客户端,可能希望在虚拟机中运行的所有线程使用同一个安全上下文(security context)。在应用启动的时候我们可以配置一个策略来指定 SecurityContextHolder如何来存储安全上下文。对于一个本地应用,你可能会使用 SecurityContextHolder.MODE_GLOBAL策略。其他的应用可能希望由安全线程( secure thread)启动的其他线程拥有同样的安全特性。这可以通过 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL实现。你可以通过2种方式来概念默认的安全策略( SecurityContextHolder.MODE_THREADLOCAL).第一种方式是设置系统属性,第二种方式是调用 SecurityContextHolder的静态方法。大部分应用不需要改变默认的配置,如果你确实需要,查看 SecurityContextHolder对象的java doc来获取更多的信息。

获取当前用户信息

SecurityContextHolder中我们存储了与系统交互的principal相关细节。Spring Security使用 Authentication 对象来描述这些信息。通常你不需要自己创建 Authentication 对象,但是对于 Authentication对象的查询是非常常见的。你可以在应用中的任何地方使用以下代码块来获取当前已验证的用户的名字,例如:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

通过调用 getContext()方法返回的对象是 SecurityContext 接口的一个实例。这个对象被存储在 thread local里。就像我们接下来要看到的,Spring Security大部分的验证机制通过返回一个 UserDetails 实例作为 principal

UserDetailsService

在上面的代码片段,我们需要注意的是可以从一个 Authentication 对象中获取一个principal。principal就是一个 Object而已。大部分情况下,可以强制转换为一个 UserDetails 对象。 UserDetails 是Spring Secuirty中的一个核心接口。它代表了一个principal,其是是可扩展的并且是应用相关的。你可以认为UserDetails是你自己的用户数据与Spring Security在 SecurityContextHolder对象中需要使用到的用户数据的适配器。作为你自己的用户数据的某种表现,你会经常强制转换 UserDetails 为应用中原来提供的数据类型,因此你可以调用特定的业务方法,(例如getEmail(), getEmployeeNumber()等等)。

到现在你可能想知道,什么时候我应该提供一个 UserDetails 对象?如何来做?我认为你仅仅是在声明这件事情,并不需要编写任何java代码。简单的回答是有一个特定的接口 UserDetailsService。这个接口中定义的唯一的方法接受一个String类型的用户名参数,返回 UserDetails对象。

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

这是在Spring Secuirty中加载用户信息最常用的方式,并且你将会看到在框架中任何需要获取一个用户的信息的时候使用的都是这种方式。

在验证成功的情况下, UserDetails 被用来构建 Authentication 对象,并存储在 SecurityContextHolder 中。一个好消息是我们已经提供了大量的 UserDetailsService 的实现,包括使用内存中的map( InMemoryDaoImpl),和使用JDBC( JdbcDaoImpl)。尽管如此,大部分用户倾向于基于使用应用中已经存在的用于描述员工、客户或者其他类型用户的数据访问对象(Data Access Object )来编写他们自己的实现。记住无论你的 UserDetailsService 返回类型是什么,总是可以在 SecurityContextHolder 中通过以上的代码片段来获取。

提示:读者经常会对 UserDetailsService产生疑惑。它是一个纯粹用于获取用户数据的DAO,没有任何其他功能,除了提供框架中其他组件需要的数据。特别的,其并不验证用户,验证是通过 AuthenticationManager完成。在很多场景下,如果我们需要自定义的验证过程,需要直接实现 AuthenticationProvider

GrantedAuthority

除了principal, Authentication 提供的另一个重要的方法是 getAuthorities()。这个方法用于提供一个 GrantedAuthority 对象数组。 GrantedAuthority 表示的是授予当前的 principal权利。这些权利通常是角色("roles"),例如 ROLE_ADMINISTRATOR 或者 ROLE_HR_SUPERVISOR。这些角色的配置是为了接下来的web授权、方法授权和域对象授权。Spring Security的其他部分可以解析角色中权利,执行某些操作时必须拥有这些权利。 GrantedAuthority 对象通常通过 UserDetailsService加载。

通常 GrantedAuthority 是整个应用范围的权利许可,并不是针对于某个特定的领域对象。因此,你不应该用一个 GrantedAuthority对象表示一个编号为54的 Employee 对象的许可,因为如果有数以千计的这样的权利,内存很快会被耗尽(或者至少会引起应用要花费很长的时间才能验证一个用户)。当然,Spring Security明确的设计可以处理这种常见需求,但是作为替代你应该使用项目的域对象的安全能力来实现这个目标。

总结

这里仅仅是简要概括,目前我们所看到的Spring Security的主要组成部分是:

SecurityContextHolder,提供对 SecurityContext的访问

SecurityContext,维护了 Authentication 和可能的特定请求的安全信息

Authentication,以Spring Security的方式描述principal。

GrantedAuthority,表示在应用范围内授予principal的权利许可。

GrantedAuthority,表示在应用范围内授予principal的权利许可。

UserDetailsService,用来根据传递的字符串形式的用户名(或者验证id等类似信息)来创建 UserDetails 对象。

现在你已经对这些总是被重复使用的组件有所理解,让我们来更进一步查看验证的过程。