该篇记录一下SecurityContextHolder与SecurityContext两个类,当然还有与它们关系密码的SecurityContextPersistenceFilter.java这个过滤器
1. SecurityContext.java
查看spring security的源码,发现它就是个接口,spring security提供了一个默认的实现SecurityContextImpl.java. 仔细一看,该类其实就是对Authentication对象进行了封装,当然,覆写了equals和hashCode两个方法。
public class SecurityContextImpl implements SecurityContext { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; // ~ Instance fields // ================================================================================================ private Authentication authentication; public SecurityContextImpl() {} public SecurityContextImpl(Authentication authentication) { this.authentication = authentication; } // ~ Methods // ======================================================================================================== @Override public boolean equals(Object obj) { if (obj instanceof SecurityContextImpl) { SecurityContextImpl test = (SecurityContextImpl) obj; if ((this.getAuthentication() == null) && (test.getAuthentication() == null)) { return true; } if ((this.getAuthentication() != null) && (test.getAuthentication() != null) && this.getAuthentication().equals(test.getAuthentication())) { return true; } } return false; } @Override public Authentication getAuthentication() { return authentication; } @Override public int hashCode() { if (this.authentication == null) { return -1; } else { return this.authentication.hashCode(); } } @Override public void setAuthentication(Authentication authentication) { this.authentication = authentication; } }
2. SecurityContextHolder.java
官方解释就是: Associates a given {@link SecurityContext} with the current execution thread.(与当前线程的securitycontext有关)
其实,它就是存储SecurityContext对象。
默认的策略采用ThreadLocal
源代码如下:
public class SecurityContextHolder { // ~ Static fields/initializers // ===================================================================================== public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL"; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL"; public static final String MODE_GLOBAL = "MODE_GLOBAL"; public static final String SYSTEM_PROPERTY = "spring.security.strategy"; private static String strategyName = System.getProperty(SYSTEM_PROPERTY); private static SecurityContextHolderStrategy strategy; private static int initializeCount = 0; static { initialize(); } private static void initialize() { // 如果没有设置自定义的策略,就采用MODE_THREADLOCAL模式 if (!StringUtils.hasText(strategyName)) { // Set default strategyName = MODE_THREADLOCAL; } // ThreadLocal策略 if (strategyName.equals(MODE_THREADLOCAL)) { strategy = new ThreadLocalSecurityContextHolderStrategy(); } // 采用InheritableThreadLocal,它是ThreadLocal的一个子类 else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) { strategy = new InheritableThreadLocalSecurityContextHolderStrategy(); } // 全局策略,实现方式就是static SecurityContext contextHolder else if (strategyName.equals(MODE_GLOBAL)) { strategy = new GlobalSecurityContextHolderStrategy(); } else { // 自定义的策略,通过返回创建出 try { Class<?> clazz = Class.forName(strategyName); Constructor<?> customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); } catch (Exception ex) { ReflectionUtils.handleReflectionException(ex); } } initializeCount++; } }
补充说明: InheritableThreadLocal 与 ThreadLocal的区别
ThreadLocal , 存储变量只能被当前线程使用
InheritableThreadLocal , 父线程中存储的变量子线程也可使用
3. SecurityContextPersistenceFilter.java
该过滤器是spring security 过滤器链的第一个过滤器,所以请求进来时,第一个经过它,响应数据时,最后一个经过它。
请求进来时, 它会检测session中是否有SecurityContext,如果有,它会将SecurityContext从session中拿出来,放到线程中。
当请求响应时,它会检测线程是否有SecurityContext,如果有,它会将SecurityContext放到session中去。
这样,不同的请求,就可以拿到同一个认证信息 Authentication
下面看具体源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; ...... // 将request与response对象封装成一个HttpRequestResponseHolder对象,减少方法列表个数 HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,response); // 检测session中是否有SecurityContext,如果有就从session中获取,如果没有,创建一个新的 SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { // 将SecurityContext对象放到当前执行的线程中 SecurityContextHolder.setContext(contextBeforeChainExecution); // 调用过滤器链 chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { // 从当前线程中获取SecurityContext对象 SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); // 清空当前线程中的SecurityContext SecurityContextHolder.clearContext(); // 将SecurityContext放入到session中 repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse()); } }
接着看下loadContext(holder)方法的源码
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { HttpServletRequest request = requestResponseHolder.getRequest(); HttpServletResponse response = requestResponseHolder.getResponse(); // 获取session HttpSession httpSession = request.getSession(false); // 从session中获取SecurityContext对象 SecurityContext context = readSecurityContextFromSession(httpSession); // 如果是null,就创建一个新的 if (context == null) { context = generateNewContext(); } ....... return context; }
4. 总结
这几个相关的类看完了,感叹spring的代码就是写得好!
(1) SecurityContextHolder它的责任就是存储SecurityContext对象,但是怎么存储,采用何种存储策略则是通过SecurityContextHolderStrategy来实现的。SecurityContextHolderStrategy只是一个抽象接口,spring security 默认提供了几种存储策略,它们都实现了SecurityContextHolderStrategy接口。如果我们想自定义存储策略,肯定也得实现SecurityContextHolderStrategy。这样子,SecurityContextHolder 只需要提供存储策略的方式,至于如何实现这种存储策略,则完全交给了SecurityContextHolderStrategy及其实现类来控制,做到责任分离吧!
(2) SecurityContextPersistenceFilter也是骚了一逼,将交量转换用得神了!