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