登录成功后,如果我们没有使用框架的话,用户信息会保存在HttpSession中,Spring Security实质是对HttpSession进行了封装,我们可以直接从HttpSession中获取信息,也可以采用Spring Security提供的方法
(1)从SecurityContextHolder中获取
(2)从当前请求对象中获取
从SecurityContextHolder中获取
SecurityContextHolder中包含了SecurityContext对象,SecurityContext中包含了Authentication对象
Authentication对象的功能有两个:
(1)作为后续验证的入参
(2)获取当前验证通过的用户信息
Authentication包含三个属性:
principal:用户身份,如果是用户/密码认证,这个属性就是UserDetails实例
credentials:通常就是密码,在大多数情况下,在用户验证通过后就会被清除,以防密码泄露。
authorities:用户权限
不同的认证方式对应不同的Authentication实例,Spring Security中Authentication的实现类有很多,比较常用的有UsernamePasswordAuthebticationToken(表单登录时封装的用户对象),RememverMeAuthenticationToken(记住我登录时封装的用户对象)等
Authentication authentication=SecurityContxtHolder.getContext().getAuthentication();
SecurityContextHolder是用来存储SecurityContext,共有三种存储策略
(1)threadlocal(默认):存储在threadlocal中,每个线程享有自己的threadlocal
(2)global:存储在一个静态变量中,在web开发中很少使用
(3)InheritableThreadLocal:适用于多线程,子线程也能获得数据(实际上是子线程可以从主线程同步)
既然SecurityContextHolder默认是使用threadlocal的方式,但是springboot 每个请求都是一个新的线程,那么是如何做到共享用户信息呢?
实际上是通过SecurityContextPersistenceFilter
SecurityContextPersistenceFilter这个过滤器主要做两件事情
(1)收到一个请求后,从HttpSession中获得SecurityContext并存入SecurityContextHolder中
(2)当一个请求处理完后,从SecurityContextHolder中取出SecurityContext放入HttpSession中(异步Servlet),方便下一个请求获取,同时擦除SecurityContextHolder的信息
我们看一下核心源码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (request.getAttribute("__spring_security_scpf_applied") != null) {
chain.doFilter(request, response);
} else {
boolean debug = this.logger.isDebugEnabled();
request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
if (this.forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
this.logger.debug("Eagerly created session: " + session.getId());
}
}
// 包装 request ,response
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
// 从session中获取上下文信息
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
boolean var13 = false;
try {
// //请求开始时,SecurityContext存入SecurityContextHolder中
var13 = true;
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
var13 = false;
} finally {
// 过滤器走完之后就清空SecurityContextHolder中的SecurityContext 对象
if (var13) {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute("__spring_security_scpf_applied");
if (debug) {
this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
核心代码在SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
我们看下loadContext这个方法
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
HttpSession httpSession = request.getSession(false);
// 根据特定的key从session中获取SecurityContext
SecurityContext context = this.readSecurityContextFromSession(httpSession);
if (context == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No SecurityContext was available from the HttpSession: " + httpSession + ". A new one will be created.");
}
// 如果session里面没有就重新生成一个SecurityContext
context = this.generateNewContext();
}
HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper wrappedResponse = new HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper(response, request, httpSession != null, context);
requestResponseHolder.setResponse(wrappedResponse);
if (this.isServlet3) {
requestResponseHolder.setRequest(new HttpSessionSecurityContextRepository.Servlet3SaveToSessionRequestWrapper(request, wrappedResponse));
}
return context;
}
核心在SecurityContext context = this.readSecurityContextFromSession(httpSession);
我们看下readSecurityContextFromSession
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
boolean debug = this.logger.isDebugEnabled();
if (httpSession == null) {
if (debug) {
this.logger.debug("No HttpSession currently exists");
}
return null;
} else {
Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
if (contextFromSession == null) {
if (debug) {
this.logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
}
return null;
} else if (!(contextFromSession instanceof SecurityContext)) {
if (this.logger.isWarnEnabled()) {
this.logger.warn(this.springSecurityContextKey + " did not contain a SecurityContext but contained: '" + contextFromSession + "'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class?");
}
return null;
} else {
if (debug) {
this.logger.debug("Obtained a valid SecurityContext from " + this.springSecurityContextKey + ": '" + contextFromSession + "'");
}
return (SecurityContext)contextFromSession;
}
}
}
核心在Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
整体思路就是根据一个Key(springSecurityContextKey)从HttpSession获取SecurityContext,如果没有就新建一个
从当前请求对象中获取
可以直接在Controller请求参数中传入Authentication对象直接获取用户信息
@RequestMapping("/hello")
public void hello(Authentication authentication){
System.out.println("name="+authentication.getName());
}
根据Spring MVC我们可以知道Controller中的参数都是当前请求HttpServletRequest带来的,那么Authentication 是怎么放入HttpServletRequest?
在Spring Security中HttpServletRequest的实现类是Servlet3SecurityContextHolderAwareRequestWrapper,该类将Authentication封装进去了,除此之外还实现了:
(1)getAuthentication:获得Authentication对象
(2)getRemoteUser:获得当前登录的用户名
(3)getUserPrincipal:返会当前登录用户的Principal
(4)isUserInRole(String role):判断当前用户是否具有某个指定角色的功能
那么Spring Security是如何对HttpServletRequest进行封装的?
主要是靠Servlet3SecurityContextHolderAwareRequestFilter过滤器