SpringSecurity(四):登錄用戶信息的獲取


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM