1.5 获取登录用户
1.5.1 SecurityContext
用户认证通过后,都会在 SecurityContext
中存储一个 Authentication 对象,该对象存储用户认证的详细信息。
SecurityContext 对象可以通过 SecurityContextHolder 类的静态方法 getContext() 获取。
在Web环境下,SecurityContextHolder 是利用 ThreadLocal 来存储 SecurityContext的。
SecurityContext 源码:
package org.springframework.security.core.context;
import java.io.Serializable;
import org.springframework.security.core.Authentication;
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication var1);
}
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 获取登录用户信息
@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 中任意一个请求,页面显示内容如下:
1.5.4 屏蔽用户敏感信息
上面获取到的用户信息中,用户的密码也暴露了出来,让 User 类实现 CredentialsContainer 接口,在接口方法eraseCredentials() 中,清除用户凭证信息。
public class User implements UserDetails, CredentialsContainer {
......
/**
* 清除用户凭证信息
*/
@Override
public void eraseCredentials() {
this.password = "";
}
}
测试验证: