登錄成功后,如果我們沒有使用框架的話,用戶信息會保存在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過濾器