zhaoyu@home:~$

spring-security-概述

springSecurity包括了authetication,authorization,OAuth2.0,Saml2,防入侵等特性。可以保障项目安全运行。

Core组件

spring-security-core是spring security的核心组件。其中最核心的对象是SecurityContextHolder。这个对象存储了应用SecurityContext的 详细细节。 也包括了应用当前使用的principal。SecurityContextHolder 使用了ThreadLocal类存储SecurityContext详情。

获取当前用户的信息

SecurityContextHolder 内部存储了principal详细信息,使用Authentication 来表示这个信息。可以使用下面代码获取当前认证用户的名称。

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

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

UserDetailsService

principal 只是一个对象,大多数情况下可以转换为UserDetails 对象,UserDetails 是Spring Security的核心接口。它代表了一个principal, 但是它也从应用角度进行了一些扩展。可以把它当做是用户数据库和Spring Security的SecurityContextHolder之间的适配器。如何去得到一个 UserDetails对象呢,我们可以通过UserDetailsService接口,该接口只有一个方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

GrantedAuthority

除了principal,Authentication 提供的另一个重要方法是getAuthorities(),这个方法提供了一个GrantedAuthority 数组。GrantedAuthority 是对principal的授权,通常是”roles”,如管理员。GrantedAuthority 通常通过UserDetailsService加载。GrantedAuthority 是应用级别的权限, 所以不能指定给一个特定的对象,如编号为54的employee对象,这样对导致产生数千个授权,而导致内存不足。

综上所述,spring Security 最主要的对象有:

  • SecurityContextHolder,提供访问SecurityContext。
  • SecurityContext,持有Authentication 及可能的请求特定的安全信息。
  • Authentication ,以spring security特定的方式表示一个主体(principal)。
  • GrantedAuthority, 表示授予主体(principal)的应用级别的权限,
  • UserDetails,从应用的DAO层或者其他安全数据源提供必要的信息构建一个Authentication对象。
  • UserDetailsService, 当传入一个字符串的name(或者id)时,创建一个UserDetails。

认证

认证即对一个用户身份的判定,如用户名密码认证。spring security的认证步骤,首先是下面的4步。

  1. 获取用户名和密码,并组装为UsernamePasswordAuthenticationToken 的实例。它是Authentication 接口的一个实现。
  2. token被传送给AuthenticationManager 的一个实例做验证。
  3. AuthenticationManager 在成功认证后,生成一个完整的Authentication 实例。
  4. 通过调用SecurityContextHolder.getContext().setAuthentication(…​)创建security context。

此后,用户可以被认为是认证通过。

直接设置SecurityContextHolder的内容

spring security是不关心你怎么把Authentication 放进SecurityContextHolder中的,只要确保在AbstractSecurityInterceptor 之前 SecurityContextHolder 包含了一个表示主体的Authentication。
即可以不基于spring security,写一个自己的filter 或者 MVC 控制器和认证系统交互。

在请求之间存储SecurityContext

一个典型的web应用中,用户一旦登录成功,后序会使用session ID认证。服务器在会话期间会缓存主体信息。在spring security中,负责存储 SecurityContext 的是SecurityContextPersistenceFilter,默认会将上下文存储为HTTPSession。它会为每个请求将SecurityContext恢复到 SecurityContextHolder 中,并且在请求完成时,清除SecurityContextHolder 。
其他很多应用不使用HTTP Session,每次请求都会重新认证。但是的是SecurityContextPersistenceFilter确保SecurityContextHolder 在每次 请求后清空依然很重要。

core services

AuthenticationManager

AuthenticationManager 是一个接口。Spring Security默认的实现是ProviderManager,并不是处理身份认证请求。它将认证任务委托给配置好的 AuthenticationProvider列表。按照顺序检查列表中是否有能够处理身份认证的。每个provider要么抛出一个异常,要么返回一个完整的Authentication。 常用的方法是加载userDetail看密码和用户输入的是否一致。这也是DaoAuthenticationProvider采用的方法。
但是如果使用namespace,ProviderManager 的一个实例会在内部被创建和维护。我们可以使用namespace 认证提供者相关的元素向其添加provider。 这种情况下,我们不用在应用中声明一个ProviderManager。如果我们不是使用namespace方式,那么我们需要声明如下的ProviderManager:

<bean id="authenticationManager"
        class="org.springframework.security.authentication.ProviderManager">
    <constructor-arg>
        <list>
            <ref local="daoAuthenticationProvider"/>
            <ref local="anonymousAuthenticationProvider"/>
            <ref local="ldapAuthenticationProvider"/>
        </list>
    </constructor-arg>
</bean>

认证成功后,擦除验证信息。

默认的(3.1后)ProviderManager 在认证成功后,会从Authentication 中清除敏感信息,为了避免如密码等被过长时间的保留。

这在使用缓存存储用户对象时会有问题。一种解决方法是先复制对象,在缓存执行时或者在AuthenticationProvider 创建Authentication 时,另一种, 禁用ProviderManager的eraseCredentialsAfterAuthentication 属性。

DaoAuthenticationProvider

最简单的AuthenticationProvider 是DaoAuthenticationProvider,它利用UserDetailsService (作为一个Dao)获得用户名和密码及 GrantedAuthority。通过对比UsernamePasswordAuthenticationToken 提交的密码和 UserDetailsService加载的密码完成用户的认证。 配置如下:

<bean id="daoAuthenticationProvider"
    class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
</bean>

UserDetailsService 的实现

内存实现

实现UserDetailsService一种最简单地选择就是使用namaspace的user-service元素。

<user-service id="userDetailsService">
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
NoOpPasswordEncoder should be used. This is not safe for production, but makes reading
in samples easier. Normally passwords should be hashed using BCrypt -->
<user name="jimi" password="{noop}jimispassword" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="{noop}bobspassword" authorities="ROLE_USER" />
</user-service>

上面的定义也可以使用属性文件代替:

<user-service id="userDetailsService" properties="users.properties"/>

属性文件的内容如下:

jimi=jimispassword,ROLE_USER,ROLE_ADMIN,enabled
bob=bobspassword,ROLE_USER,enabled