本文主要整理一下SecurityContext的存储方式。java
顾名思义,安全上下文。即存储认证受权的相关信息,实际上就是存储"当前用户"帐号信息和相关权限。这个接口只有两个方法,Authentication对象的getter、setter。spring
package org.springframework.security.core.context; import java.io.Serializable; import org.springframework.security.core.Authentication; public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication var1); }
注意:SecurityContext存储的Authentication对象是通过认证的,因此它会带有权限,它的getAuthorities()方法会返回相关权限。编程
package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
前面说的"当前用户"实际上指的是当前这个请求所对应的用户,那么怎么知道当前用户是谁呢?因为一个请求从开始到结束都由一个线程处理,这个线程中途也不会去处理其余的请求。因此在这段时间内,至关于这个线程跟当前用户是一一对应的。SecurityContextHolder工具类就是把SecurityContext存储在当前线程中。浏览器
SecurityContextHolder能够用来设置和获取SecurityContext。它主要是给框架内部使用的,能够利用它获取当前用户的SecurityContext进行请求检查,和访问控制等。安全
在Web环境下,SecurityContextHolder是利用ThreadLocal来存储SecurityContext的。服务器
咱们知道Sevlet中线程是被池化复用的,一旦处理完当前的请求,它可能立刻就会被分配去处理其余的请求。并且也不能保证用户下次的请求会被分配到同一个线程。因此存在线程里面,请求一旦结束,就没了。若是没有保存,不是每次请求都要从新认证登陆?想一想看,若是没有权限框架咱们是怎么处理的?session
想到了吧,若是不用权限框架,咱们通常是把认证结果存在Session中的。同理,它也把认证结果存储到Session了。app
对应的Key是:"SPRING_SECURITY_CONTEXT"框架
SecurityContextPersistenceFilter是Security的拦截器,并且是拦截链中的第一个拦截器,请求来临时它会从HttpSession中把SecurityContext取出来,而后放入SecurityContextHolder。在全部拦截器都处理完成后,再把SecurityContext存入HttpSession,并清除SecurityContextHolder内的引用。工具
注:其中repo对象是HttpSessionSecurityContextRepository
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) { // ensure that filter is only applied once per request chain.doFilter(request, response); return; } final boolean debug = logger.isDebugEnabled(); request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (forceEagerSessionCreation) { HttpSession session = request.getSession(); if (debug && session.isNew()) { logger.debug("Eagerly created session: " + session.getId()); } } HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); //利用HttpSecurityContextRepository从HttpSesion中获取SecurityContext对象 //若是没有HttpSession,即浏览器第一次访问服务器,尚未产生会话。 //它会建立一个空的SecurityContext对象 SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { //把SecurityContext放入到SecurityContextHolder中 SecurityContextHolder.setContext(contextBeforeChainExecution); //执行拦截链,这个链会逐层向下执行 chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { //当拦截器都执行完的时候把当前线程对应的SecurityContext从SecurityContextHolder中取出来 SecurityContext contextAfterChainExecution = SecurityContextHolder .getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything // else. SecurityContextHolder.clearContext(); //利用HttpSecurityContextRepository把SecurityContext写入HttpSession repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); if (debug) { logger.debug("SecurityContextHolder now cleared, as request processing completed"); } } }
有人可能对Tomcat创建会话的流程还不熟悉,这里稍微整理一下。是这样的,当客户浏览器打开后第一次访问Tomcat服务器,Tomcat会建立一个HttpSesion对象,存入一个ConcurrentHashMap,Key是SessionId,Value就是HttpSession。而后请求完成后,在返回的报文中添加Set-Cookie:JSESSIONID=xxx,而后客户端浏览器会保存这个Cookie。当浏览器再次访问这个服务器的时候,都会带上这个Cookie。Tomcat接收到这个请求后,根据JSESSIONID把对应的HttpSession对象取出来,放入HttpSerlvetRequest对象里面。
重点:
1.HttpSession会一直存在服务端,其实是存在运行内存中。除非Session过时 OR Tomcat奔溃 OR 服务器奔溃,不然会话信息不会消失。
2.如无特殊处理,Cookie JSESSIONID会在浏览器关闭的时候清除。
3.Tomcat中HttpSesion的默认过时时间为30分钟。
4.这些处理都在Security的拦截链以前完成。
——————————————————2019年1月21日更正——————————————
浏览器访问Web项目并不会建立Session,只有在编程调用request.getSession()方法后Session才会创建,浏览器才会有JSESSIONID的Cookie。
通常是认证成功后,调用getSession()(其实在这以前Session并不存在),得到Session对象,而后把信息存里面。
因此,通常的,在登陆以前,你无论怎么访问网站,都不会创建会话。