文章目录
- 一、SpringSecurity 过滤器介绍
- 1. WebAsyncManagerIntegrationFilter
- 2. SecurityContextPersistenceFilter
- 3. HeaderWriterFilter
- 4. CsrfFilter
- 5. LogoutFilter
- 6. UsernamePasswordAuthenticationFilter
- 7. DefaultLoginPageGeneratingFilter
- 8. BasicAuthenticationFilter
- 9. RequestCacheAwareFilter
- 10. SecurityContextHolderAwareRequestFilter
- 11. AnonymousAuthenticationFilter
- 12. SessionManagementFilter
- 13. ExceptionTranslationFilter
- 14. FilterSecurityInterceptor
- 15. RememberMeAuthenticationFilter
- 二、SpringSecurity基本流程
- 三、SpringSecurity认证流程
- 四、 SpringSecurity 权限访问流程
- 五、 SpringSecurity 请求间共享认证信息
一、SpringSecurity 过滤器介绍
SpringSecurity采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的15个过滤器进行说明:
1. WebAsyncManagerIntegrationFilter
将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
2. SecurityContextPersistenceFilter
在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将SecurityContextHolder 中的信息清除。
例如在Session中维护一个用户的安全信息就是这个过滤器处理的。
3. HeaderWriterFilter
用于将头信息加入响应中。
4. CsrfFilter
用于处理跨站请求伪造。
5. LogoutFilter
用于处理退出登录。
6. UsernamePasswordAuthenticationFilter
用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。
从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 参数来进行修改。
7. DefaultLoginPageGeneratingFilter
如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
8. BasicAuthenticationFilter
检测和处理 http basic 认证。
9. RequestCacheAwareFilter
用来处理请求的缓存。
10. SecurityContextHolderAwareRequestFilter
主要是包装请求对象request。
11. AnonymousAuthenticationFilter
检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。
12. SessionManagementFilter
管理 session 的过滤器
13. ExceptionTranslationFilter
处理 AccessDeniedException 和 AuthenticationException 异常。
14. FilterSecurityInterceptor
可以看做过滤器链的出口。
15. RememberMeAuthenticationFilter
当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
二、SpringSecurity基本流程
Spring Security采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器:
绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以使用Spring Security提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在configure(HttpSecurity http)方法中配置,没有配置则不生效。下面会重点介绍以下三个过滤器:
UsernamePasswordAuthenticationFilter过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证。
ExceptionTranslationFilter过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。
FilterSecurityInterceptor过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由ExceptionTranslationFilter过滤器进行捕获和处理。
三、SpringSecurity认证流程
认证流程是在UsernamePasswordAuthenticationFilter过滤器中处理的,具体流程如下所示:
1. 抽象父类AbstractAuthenticationProcessingFilter,doFilter方法源码
当前端提交一个 POST 方式的登录表单请求,就会被该过滤器拦截,并进行身份认证。该过滤器的 doFilter() 方法实现在其抽象父类AbstractAuthenticationProcessingFilter中,查看相关源码:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!requiresAuthentication(request, response)) {
// 1. 判断该请求是否为POST方式的登录表单提交请求,若不是,则直接放行,进入下一个过滤器
chain.doFilter(request, response);
return;
}
try {
// 2.attemptAuthentication调用的是子类UsernamePasswordAuthenticationFilter重写的方法进行身份认证
// Authentication是用来存放用户信息的类
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
return;
}
// 3.session策略处理(如果配置了用户session最大并发数,则在此处进行判断并处理)
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// 4.1认证成功的处理
// continueChainBeforeSuccessfulAuthentication默认为false,所以认证成功后不进入下一个过滤器
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authenticationResult);
}
catch (InternalAuthenticationServiceException failed) {
this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
// 4.2认证失败的处理
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// 4.2认证失败的处理
unsuccessfulAuthentication(request, response, ex);
}
}
2. 子类UsernamePasswordAuthenticationFilter,attemptAuthentication方法源码
UsernamePasswordAuthenticationFilter重写attemptAuthentication方法进行身份认证
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// 提交的登录表单,用户名参数名称默认为“username”,密码参数名称默认为“password”
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
// 默认请求方式只能为POST
private boolean postOnly = true;
// 提交的登录表单,默认路径是/login,提交方式为POST
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
// 1. 默认情况下,如果请求不是POST,会抛出异常
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
// 获取请求携带的username 、password
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
// 3. UsernamePasswordAuthenticationToken是 Authentication 接口的实现类
// 使用前端传入的username 、password构造Authentication对象,标记该对象为未认证状态
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 4.允许子类设置“详细信息”属性到Authentication对象中,如:remoteAddress,sessionId
setDetails(request, authRequest);
// 5.this.getAuthenticationManager()返回的是AuthenticationManager接口,实现类是ProviderManager,
// 调用ProviderManager类的authenticate方法进行身份认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
3. Authentication接口的UsernamePasswordAuthenticationToken实现类源码
UsernamePasswordAuthenticationToken是 Authentication 接口的实现类,该类有两个构造器:
一个用于封装前端请求传入的未认证的用户信息,一个用于封装认证成功后的用户信息
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// 用于封装前端请求传入的未认证的用户信息
// UsernamePasswordAuthenticationFilter重写attemptAuthentication方法中的authRequest对象就是调用该构造器进行构造的
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null); // 用户权限为null
this.principal = principal; // 前端传入的用户名
this.credentials = credentials; // 前端传入的密码
setAuthenticated(false); // 标记为未认证状态
}
// 用于封装认证成功的用户信息
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities); // 用户权限集合
this.principal = principal; // 封装认证用户信息的UserDetail对象,不再是用户名
this.credentials = credentials; // 前端传入的密码
super.setAuthenticated(true); // 标记为认证成功状态
}
}
4. Authentication 接口源码
Authentication 接口的实现类用于存储用户认证信息
一旦 AuthenticationManager.authenticate(Authentication) 方法处理了请求,则表示身份验证请求或经过身份验证的主体的令牌。一旦请求通过身份验证,身份验证通常将存储在由正在使用的身份验证机制的 SecurityContextHolder 管理的线程本地 SecurityContext 中。通过创建 Authentication 实例并使用代码,可以在不使用 Spring Security 的身份验证机制之一的情况下实现显式身份验证:
SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(anAuthentication);
SecurityContextHolder.setContext(context);
请注意,除非 Authentication 将 authenticated 属性设置为 true,否则它仍将由遇到它的任何安全拦截器(用于方法或 Web 调用)进行身份验证。在大多数情况下,框架会透明地为您管理安全上下文和身份验证对象。
public interface Authentication extends Principal, Serializable {
// 用户权限集合
// 由 AuthenticationManager 设置以指示已授予主体的权限。
// 请注意,类不应依赖此值作为有效值,除非它已由受信任的 AuthenticationManager 设置。
// 实现应确保对返回集合数组的修改不会影响 Authentication 对象的状态,或使用不可修改的实例。
Collection<? extends GrantedAuthority> getAuthorities();
// 用户密码
// 证明委托人正确的凭据。这通常是一个密码,但可以是与 AuthenticationManager 相关的任何内容。调用者应填充凭据。
Object getCredentials();
// 存储有关身份验证请求的其他详细信息。这些可能是 IP 地址、证书序列号等。
Object getDetails();
// 被认证的主体的身份。在使用用户名和密码的身份验证请求的情况下,这将是用户名。调用者应填充身份验证请求的主体。
// AuthenticationManager 实现通常会返回一个包含更丰富信息的 Authentication 作为应用程序使用的主体。
// 许多身份验证提供程序将创建一个 UserDetails 对象作为主体。
Object getPrincipal();
// 用于向 AbstractSecurityInterceptor 指示它是否应该向 AuthenticationManager 提供身份验证令牌。
// 通常,AuthenticationManager(或者更常见的是它的 AuthenticationProviders 之一)将在成功认证后返回一个不可变的认证令牌,在这种情况下,该令牌可以安全地向该方法返回 true。
// 返回 true 将提高性能,因为不再需要为每个请求调用 AuthenticationManager。
// 出于安全原因,这个接口的实现应该非常小心地从这个方法返回 true,除非它们是不可变的,或者有某种方式确保属性自最初创建以来没有被更改。
boolean isAuthenticated();
// 设置是否被认证
// 实现应始终允许使用 false 参数调用此方法,因为各种类使用它来指定不应信任的身份验证令牌。
// 如果实现希望拒绝使用 true 参数的调用(这将表明身份验证令牌是可信的 - 潜在的安全风险),则实现应该抛出 IllegalArgumentException。
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
5. ProviderManager 源码
UsernamePasswordAuthenticationFilter过滤器的 attemptAuthentication() 方法将未认证的 Authentication 对象传入 ProviderManager 类的 authenticate() 方法进行身份认证。
ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接口,也是认证的入口。
在实际开发中,我们可能有多种不同的认证方式,例如:用户名+密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是 AuthenticationManager。在该接口的常用实现类 ProviderManager 内部会维护一个List< AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式(Delegate)的应用。每种认证方式对应着一个 AuthenticationProvider,AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托对应的 AuthenticationProvider 进行用户认证。
// 传入未认证的Authentication对象
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 1.获取传入的Authentication类型,即UsernamePasswordAuthenticationToken.class
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
// 2.遍历List<AuthenticationProvider>认证方式列表
for (AuthenticationProvider provider : getProviders()) {
// 3.判断当前AuthenticationProvider是否适用UsernamePasswordAuthenticationToken.class类型的Authentication
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
// 成功找到适配当前认证方式的AuthenticationProvider,此处为DaoAuthenticationProvider
try {
// 4.调用DaoAuthenticationProvider的authenticate方法进行认证
result = provider.authenticate(authentication);
// 4.1如果认证成功,会返回一个已认证标记的Authentication对象
if (result != null) {
// 5.1认证成功后,将传入的Authentication对象中的details信息拷贝到已认证的Authentication对象
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
if (result == null && this.parent != null) {
try {
// 5.2认证失败后,使用父类型的AuthenticationManager进行认证
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
// 6.认证成功后,去除result敏感信息,要求相关类实现CredentialsContainer接口
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// 6.1去除result敏感信息的过程就是调用CredentialsContainer接口的eraseCredentials方法
((CredentialsContainer) result).eraseCredentials();
}
// 7.发布认证成功事件
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// 8.认证失败后,抛出失败的异常信息
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
6. eraseCredentials方法源码
调用 CredentialsContainer 接口定义的 eraseCredentials() 方法去除敏感信息。查看 UsernamePasswordAuthenticationToken 实现的 eraseCredentials() 方法,该方法实现在其父类AbstractAuthenticationToken中:
// 实现了CredentialsContainer接口
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
@Override
public void eraseCredentials() {
// credentials(前端传入的密码)会置为null
eraseSecret(getCredentials());
// principal在已认证的Authentication中是UserDetails实现类
// 如果该实现类想要去除敏感信息,需要实现CredentialsContainer接口的eraseCredentials方法
// 由于我们自定义的User类没有实现该接口,所以不进行任何操作
eraseSecret(getPrincipal());
eraseSecret(this.details);
}
private void eraseSecret(Object secret) {
if (secret instanceof CredentialsContainer) {
((CredentialsContainer) secret).eraseCredentials();
}
}
}
7. 认证成功/失败处理源码
在父类AbstractAuthenticationProcessingFilter的doFilter方法会调用认证成功/失败的方法
successfulAuthentication 源码:
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
// 1.将认证成功后的用户信息Authentication对象封装进SecurityContext中,并存入SecurityContextHolder
// SecurityContextHolder封装了ThreadLocal
SecurityContextHolder.getContext().setAuthentication(authResult);
// 2.rememberMe的处理
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
// 3.发布认证成功事件
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
// 4.调用认证成功处理器
successHandler.onAuthenticationSuccess(request, response, authResult);
}
unsuccessfulAuthentication 源码:
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
// 清除该线程在SecurityContextHolder中对应的SecurityContext对象
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString(), failed);
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler " + failureHandler);
}
// 2. rememberMe处理
rememberMeServices.loginFail(request, response);
// 3. 调用认证失败处理器
failureHandler.onAuthenticationFailure(request, response, failed);
}
四、 SpringSecurity 权限访问流程
权限访问流程,主要是对ExceptionTranslationFilter 过滤器和 FilterSecurityInterceptor 过滤器进行介绍。
1. ExceptionTranslationFilter 源码
该过滤器是用于处理异常的,不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)
public class ExceptionTranslationFilter extends GenericFilterBean {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// 1.对于前端提交的请求会直接放行,不进行拦截
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
// 2.尝试从堆栈跟踪中提取 SpringSecurityException
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
// 2.1访问需要认证的资源时,请求未认证则抛出此异常
RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
// 2.2访问被拒绝时的异常
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
}
2. FilterSecurityInterceptor 源码
FilterSecurityInterceptor 是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果
访问受限会抛出相关异常,最终所抛出的异常会由前一个过滤器ExceptionTranslationFilter 进行捕获和处理。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
// 先调用过滤器的doFilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 再调用invoke方法
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 1.根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 2.访问相关资源时,根据在 SpringMVC 的核心组件DispatcherServlet进行访问
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
}
需要注意,Spring Security 的过滤器链是配置在 SpringMVC 的核心组件DispatcherServlet 运行之前。也就是说,请求通过 Spring Security 的所有过滤器,不意味着能够正常访问资源,该请求还需要通过 SpringMVC 的拦截器链。
五、 SpringSecurity 请求间共享认证信息
一般认证成功后的用户信息是通过 Session 在多个请求之间共享, Spring Security 会将已认证的用户信息对象 Authentication 与 Session 进行绑定
、
在前面讲解认证成功的处理方法 successfulAuthentication() 时,有以下代码:
protected void successfulAuthentication() {
...
// 1.将认证成功后的用户信息Authentication对象封装进SecurityContext中,并存入SecurityContextHolder
// SecurityContextHolder封装了ThreadLocal
SecurityContextHolder.getContext().setAuthentication(authResult);
...
}
1. SecurityContext 源码
SecurityContextImpl是SecurityContext接口的实现类, 主要对Authentication进行封装
public class SecurityContextImpl implements SecurityContext {
public SecurityContextImpl(Authentication authentication) {
this.authentication = authentication;
}
}
2. SecurityContextHolder 源码
SecurityContextHolder类其实是对ThreadLocal的封装 , 存储SecurityContext对象
将给定的 SecurityContext 与当前执行线程相关联。此类提供了一系列委托给 SecurityContextHolderStrategy 实例的静态方法。该类的目的是提供一种方便的方法来指定应该用于给定 JVM 的策略。这是 JVM 范围的设置,因为此类中的所有内容都是静态的,以便于调用代码时使用。要指定应使用哪种策略,您必须提供模式设置。模式设置是定义为Static fields字段的三个有效 MODE_ 设置之一,或者是提供公共无参数构造函数的 SecurityContextHolderStrategy 具体实现的完全限定类名。有两种方法可以指定所需的策略模式字符串。第一种是通过在 SYSTEM_PROPERTY 上键入的系统属性来指定它。第二种是在使用类之前调用 setStrategyName(String)。如果这两种方法都没有使用,则该类将默认使用MODE_THREADLOCAL,它向后兼容,具有较少的 JVM 不兼容性并且适用于服务器(而 MODE_GLOBAL 绝对不适合服务器使用)。
public class SecurityContextHolder {
// ~ Static fields/initializers
// =====================================================================================
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;
static {
initialize();
}
// ~ Methods
// ========================================================================================================
/**
* 从当前线程显式清除上下文值
*/
public static void clearContext() {
strategy.clearContext();
}
/**
* 获取当前的 SecurityContext
*/
public static SecurityContext getContext() {
// 注意:如果当前线程对应的ThreadLocal<SecurityContext>没有任何对象存储
// strategy.getContext()会创建一个空的SecurityContext对象,并且该空的SecurityContext对象会存入ThreadLocal<SecurityContext>
return strategy.getContext();
}
/**
* 将新的 SecurityContext 与当前执行线程相关联
*/
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
/**
* 主要出于故障排除目的,此方法显示该类重新初始化其 SecurityContextHolderStrategy 的次数
*
* 返回:计数(应该是一,除非您调用 setStrategyName(String) 来切换到备用策略
*/
public static int getInitializeCount() {
return initializeCount;
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// 默认使用MODE_THREADLOCAL模式
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
// 默认使用ThreadLocalSecurityContextHolderStrategy创建Strategy,其内部使用ThreadLocal对SecurityContext进行存储
strategy = new ThreadLocalSecurityContextHolderStrategy();
}
initializeCount++;
}
...
}
3. ThreadLocalSecurityContextHolderStrategy 源码
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
// ~ Static fields/initializers
// =====================================================================================
// 使用ThreadLocal存储SecurityContext
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
// ~ Methods
// ========================================================================================================
public void clearContext() {
contextHolder.remove();
}
public SecurityContext getContext() {
// 注意:如果当前线程对应的ThreadLocal<SecurityContext>没有任何对象存储
// strategy.getContext()会创建一个空的SecurityContext对象,并且该空的SecurityContext对象会存入ThreadLocal<SecurityContext>
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
// 设置当前线程对应的ThreadLocal<SecurityContext>
public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
public SecurityContext createEmptyContext() {
// 创建一个空的SecurityContext对象
return new SecurityContextImpl();
}
}
4. SecurityContextPersistenceFilter 源码
前面提到过,在 UsernamePasswordAuthenticationFilter 过滤器认证成功之后,会在认证成功的处理方法中将已认证的用户信息对象 Authentication 封装进SecurityContext,并存入 SecurityContextHolder。
之后,响应会通过 SecurityContextPersistenceFilter 过滤器,该过滤器的位置在所有过滤器的最前面,请求到来时,先进入该过滤器;响应返回时,最后一个通过它,所以在该过滤器中会处理已认证的用户信息对象 Authentication 与 Session 的绑定。
认证成功的响应通过 SecurityContextPersistenceFilter 过滤器时,会从SecurityContextHolder 中取出封装了已认证用户信息对象 Authentication 的SecurityContext,放进 Session 中。当请求再次到来时,请求首先经过该过滤器,该过滤器会判断当前请求的 Session 是否存有 SecurityContext 对象,如果有则将该对象取出再次放入 SecurityContextHolder 中,之后该请求所在的线程获得认证用户信息,后续的资源访问不需要进行身份认证;当响应再次返回时,该过滤器同样从 SecurityContextHolder 取出SecurityContext 对象,放入 Session 中。
public class SecurityContextPersistenceFilter extends GenericFilterBean {
// 执行该过滤器的doFilter方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// 确保每个请求只应用一次过滤器
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);
// 1.请求到来时,检查当前session中是否存有SecurityContext
// 若有,则从session中取出SecurityContext;若没有,则创建一个空的SecurityContext
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
// 2.将上述获得的SecurityContext存入SecurityContextHolder
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 3.进入下一个过滤器
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
// 4.响应返回时,从SecurityContextHolder取出SecurityContext
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// 5. 删除 SecurityContextHolder 内容的关键 - 在其他任何事情之前执行此操作
SecurityContextHolder.clearContext();
// 6.将取出的SecurityContext存入session,实现请求间共享认证信息
repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
}