1.5 获取登录用户

1.5.1 SecurityContext

用户认证通过后,都会在 SecurityContext 中存储一个 Authentication 对象,该对象存储用户认证的详细信息。
SecurityContext 对象可以通过 SecurityContextHolder 类的静态方法 getContext() 获取。
在Web环境下,SecurityContextHolder 是利用 ThreadLocal 来存储 SecurityContext的。
SecurityContext 源码:

  1. package org.springframework.security.core.context;
  2. import java.io.Serializable;
  3. import org.springframework.security.core.Authentication;
  4. public interface SecurityContext extends Serializable {
  5. Authentication getAuthentication();
  6. void setAuthentication(Authentication var1);
  7. }

Authentication 源码:

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;
}

1.5.2 SecurityContextPersistenceFilter

SecurityContextPersistenceFilter 是Spring Security的拦截器,而且是拦截链中的第一个拦截器,请求来临时它会从 HttpSession 中把 SecurityContext 取出来,然后放入SecurityContextHolder。在所有拦截器都处理完成后,再把 SecurityContext 存入 HttpSession,并清除SecurityContextHolder内的引用。

我们可以理解为用户的信息还是存储在 HttpSession 中的。

该过滤器 doFilter 方法源码:

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");
        }
    }
}

SecurityContext 对象在HttpSession 对象中对应的 key 是: SPRING_SECURITY_CONTEXT

1.5.3 获取登录用户信息

image.png

@RestController
public class InfoController {

    @RequestMapping("/info01")
    public UserDetails info01(@AuthenticationPrincipal User user) {
        return user;
    }

    @RequestMapping("/info02")
    public UserDetails info02(Authentication authentication) {
        UserDetails user = (UserDetails) authentication.getPrincipal();
        return user;
    }

    @GetMapping("/info03")
    public User info03() {
        // 获取认证对象,该方式在Controller之前的其它方法中使用
        SecurityContext securityContext = SecurityContextHolder.getContext();
        Authentication authentication = securityContext.getAuthentication();
        User user = (User) authentication.getPrincipal();
        return user;
    }

    @RequestMapping("/info04")
    public User info04(HttpSession session) {
        SecurityContext securityContext =
                (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
        Authentication authentication = securityContext.getAuthentication();
        User user = (User) authentication.getPrincipal();
        return user;
    }
}

启动系统: 登录后访问上面 Controller 中任意一个请求,页面显示内容如下:
image.png

1.5.4 屏蔽用户敏感信息

上面获取到的用户信息中,用户的密码也暴露了出来,让 User 类实现 CredentialsContainer 接口,在接口方法eraseCredentials() 中,清除用户凭证信息。

public class User implements UserDetails, CredentialsContainer {

    ......

    /**
     * 清除用户凭证信息
     */
    @Override
    public void eraseCredentials() {
        this.password = "";
    }
}

测试验证:
image.png