Spring Security学习总结及源码分析

概要

Spring Security是 Spring 家族中的成员,基于 Spring 提供了一套 Web 应用安全性的完整解决方案。Spring Security重要的核心功能包含认证和授权两部分:

  • 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程
  • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情

同类产品比较

Spring Security

  • 与 Spring 无缝整合
  • 全面的权限控制
  • 专门为Web开发而设计(旧版本不能脱离 Web 环境使用,新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境)
  • 重量级

Shiro:Apache 旗下的轻量级权限控制框架

  • 轻量级(Shiro主张的理念是把复杂的事情变简单,针对对性能有更高要求的互联网应用有更好表现)
  • 通用性(不局限于 Web 环境,可以脱离 Web 环境使用,在 Web 环境下一些特定的需求需要手动编写代码定制)

相对于 Shiro,在 SSM 中整合 Spring Security 是比较麻烦的操作,所以Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。因此,一般来说,常见的安全管理技术栈的组合是这样的:

  • SSM + Shiro
  • Spring Boot/Spring Cloud + Spring Security

Spring Security基本原理

/**
 * SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链
 * 从启动是可以获取到过滤器链如下
 */
// 将 Security 上下文与 Spring Web 中用于 处理异步请求映射的 WebAsyncManager 进行集成
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
// 在每次请求处理之前将该请求相关的安全上 下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在 Session 中维护一个用户的安全信息就是这个过滤器处理的
org.springframework.security.web.context.SecurityContextPersistenceFilter
// 用于将头信息加入响应中
org.springframework.security.web.header.HeaderWriterFilter
// 用于处理跨站请求伪造
org.springframework.security.web.csrf.CsrfFilter 
// 用于处理退出登录
org.springframework.security.web.authentication.logout.LogoutFilter 
// 用于处理基于表单的登录请求,从表单中 获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码 时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个 过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 
// 如果没有配置登录页面,那系统初始化时就会 配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 
// 检测和处理 http basic 认证
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
// 用来处理请求的缓存
org.springframework.security.web.savedrequest.RequestCacheAwareFilter 
// 主要是包装请求对象 request
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter 
// 检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication
org.springframework.security.web.authentication.AnonymousAuthenticationFilter 
// 管理 session 的过滤器
org.springframework.security.web.session.SessionManagementFilter 
// 处理 AccessDeniedException 和 AuthenticationException 异常
org.springframework.security.web.access.ExceptionTranslationFilter 
// 可以看做过滤器链的出口
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
// 当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie,用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter


      /**
       * 其中需要重点查看的有以下三个
       */
      // FilterSecurityInterceptor:方法级的权限过滤器, 基本位于过滤链的最底部
      public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
			return;
		}
		// first time this request being called, so perform security checking
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
                // 查看之前的 filter 是否通过
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
                        // 真正的调用后台的服务
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		}
		finally {
			super.finallyInvocation(token);
		}
		super.afterInvocation(token, null);
	}

        // ExceptionTranslationFilter:异常过滤器,用来处理在认证授权过程中抛出的异常
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}
	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		try {
			chain.doFilter(request, response);
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
			RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
			if (securityException == null) {
				securityException = (AccessDeniedException) this.throwableAnalyzer
						.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
			}
			if (securityException == null) {
				rethrow(ex);
			}
			if (response.isCommitted()) {
				throw new ServletException("Unable to handle the Spring Security Exception "
						+ "because the response is already committed.", ex);
			}
			handleSpringSecurityException(request, response, chain, securityException);
		}
	}

      // UsernamePasswordAuthenticationFilter:对/login 的 POST请求做拦截,校验表单中用户名,密码
      public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		username = (username != null) ? username : "";
		username = username.trim();
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}

SpringSecurity 基本流程

Spring Security 采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器

绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以 使用 Spring Security 提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在 configure(HttpSecurity http)方法中配置,没有配置不生效。下面会重点介绍以下三个过滤器

UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证
ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)
FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源 权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由ExceptionTranslationFilter 过滤器进行捕获和处理

SpringSecurity 认证流程

/**
 * UsernamePasswordAuthenticationFilter 源码部分
 */
        // 当前端提交的是一个 POST 方式的登录表单请求,就会被该过滤器拦截,并进行身份认证。该过滤器的 doFilter() 方法实现在其抽象父类AbstractAuthenticationProcessingFilter中,相关源码如下
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (!requiresAuthentication(request, response)) {
                        // 判断该请求是否是POST方式的表单提交,不是则放行进入下一个过滤器
			chain.doFilter(request, response);
			return;
		}
		try {
                        // Authentication用来存储用户认证信息的类,attemptAuthentication调用子类重写的方法进行身份认证
			Authentication authenticationResult = attemptAuthentication(request, response);
			if (authenticationResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				return;
			}
                        // Session处理策略,如果配置了用户Session最大并发数,则在此进行判断处理
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			// Authentication success
                        // 默认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);
                        // 调用认证失败的处理器
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, ex);
		}
	}

/**
 * 上面的过程调用了UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法,源码如下
 */
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

        // 默认表单用户名参数名
	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        
        // 默认表单密码参数名
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

        // 默认路径/login,请求方式POST
	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
			"POST");
        // 默认只能是POST
	private boolean postOnly = true;

	// 身份认证方法
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
                // 默认如果不是POST会抛出异常
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
                // 获取用户名和密码
		String username = obtainUsername(request);
		username = (username != null) ? username : "";
		username = username.trim();
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
                // 构建Authentication对象,标记为未认证
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		// 将请求中的一些属性信息设置到Authentication中,如sessionId,remoteAddress等
		setDetails(request, authRequest);
                // 调用authenticate方法进行认证
		return this.getAuthenticationManager().authenticate(authRequest);
	}
}

/**
 * 上述过程创建的 UsernamePasswordAuthenticationToken 是 Authentication 接口的实现类,该类有两个构造器,一个用于封装前端请求传入的未认证的用户信息,一个用于封装认证成功后的用户信息
 */
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private final Object principal;

	private Object credentials;

	/**
	 * 用于封装未认证的用户信息
	 */
	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; // 封装的UserDetails对象,不再是用户名
		this.credentials = credentials; // 密码
		super.setAuthenticated(true); // 标记认证成功
	}
}

/**
 * ProviderManager 源码部分
 * 上述过程中,UsernamePasswordAuthenticationFilter 过滤器的 attemptAuthentication() 方法的将未认证的 Authentication 对象传入 ProviderManager 类的 authenticate() 方法进行身份认证
 * ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接口,也是认证的入口。
 * 在实际开发中,我们可能有多种不同的认证方式,例如:用户名+ 密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是 AuthenticationManager。
 * 在该接口的常用实现类 ProviderManager 内部会维护一个 List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式 (Delegate)的应用。
 * 每种认证方式对应着一个 AuthenticationProvider, AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托对应的 AuthenticationProvider 进行用户认证
 */
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                // 获取传入的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();
		for (AuthenticationProvider provider : getProviders()) {
                        // 判断当前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));
			}
			try {
                                // 调用DaoAuthenticationProvider的authenticate方法进行认证
				result = provider.authenticate(authentication);
				if (result != null) {
                                        // 认证成功之后将传入的Authentication中的details拷贝到已认证中的Authentication
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
                                // 认证失败,使用父类AuthenticateManager进行认证
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
                        // 认证成功之后,去除result的敏感信息,要求相关类实现CredentialsContainer接口
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
                                // 发布认证成功的事件
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
                        // 认证失败,抛出失败的异常信息
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}
}

/**
 * 上述认证成功之后,调用 CredentialsContainer 接口定义的 eraseCredentials() 方法去除敏感信息。查看 UsernamePasswordAuthenticationToken 实现的 eraseCredentials() 方法,该方法实现在其父类中
 */
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
        public void eraseCredentials() {
		eraseSecret(getCredentials());
		eraseSecret(getPrincipal());
		eraseSecret(this.details);
	}
        // 如果想要去除敏感信息,需要实现CredentialsContainer的eraseCredentials方法
	private void eraseSecret(Object secret) {
		if (secret instanceof CredentialsContainer) {
			((CredentialsContainer) secret).eraseCredentials();
		}
	}
}

/**
 * AbstractAuthenticationProcessingFilter中关于认证成功和认证失败的处理
 */
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {

        // 认证成功
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
                // 将认证成功的用户信息封装到SecurityContext中
                // SecurityContextHolder是对ThreadLocal的一个封装
		SecurityContextHolder.getContext().setAuthentication(authResult);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
		}
                // rememberMe的处理
		this.rememberMeServices.loginSuccess(request, response, authResult);
		if (this.eventPublisher != null) {
                        // 发布认证成功的事件
			this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
		}
                // 调用认证成功的处理器
		this.successHandler.onAuthenticationSuccess(request, response, authResult);
	}
        // 认证失败后的处理
        protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException, ServletException {
                // 清除该线程在SecurityContextHolder中对应的SecurityContext对象
		SecurityContextHolder.clearContext();
		this.logger.trace("Failed to process authentication request", failed);
		this.logger.trace("Cleared SecurityContextHolder");
		this.logger.trace("Handling authentication failure");
                // rememberMe的处理
		this.rememberMeServices.loginFail(request, response);
                // 调用认证失败的处理器
		this.failureHandler.onAuthenticationFailure(request, response, failed);
	}
}

SpringSecurity 权限访问流程

SpringSecurity权限访问流程,主要是对 ExceptionTranslationFilter 过滤器和 FilterSecurityInterceptor 过滤器进行介绍

/**
 * 该过滤器是用于处理异常的,不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)
 */
public class ExceptionTranslationFilter extends GenericFilterBean {
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		try {
                        // 对于请求直接放行
			chain.doFilter(request, response);
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// 对捕获的异常进行处理
			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
                        // 访问需要认证的资源,但当前请求未认证抛出的异常
			RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
			if (securityException == null) {
                                访问权限受限抛出的异常
				securityException = (AccessDeniedException) this.throwableAnalyzer
						.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
			}
			if (securityException == null) {
				rethrow(ex);
			}
			if (response.isCommitted()) {
				throw new ServletException("Unable to handle the Spring Security Exception "
						+ "because the response is already committed.", ex);
			}
			handleSpringSecurityException(request, response, chain, securityException);
		}
	}
}

/**
 * FilterSecurityInterceptor 是过滤器链的最后一个过滤器,该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,最终所抛出的异常会由前一个过滤器 ExceptionTranslationFilter 进行捕获和处理
 *  需要注意,Spring Security 的过滤器链是配置在 SpringMVC 的核心组件 DispatcherServlet 运行之前。也就是说,请求通过 Spring Security 的所有过滤器, 不意味着能够正常访问资源,该请求还需要通过 SpringMVC 的拦截器链
 */
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		invoke(new FilterInvocation(request, response, chain));
	}

        public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
			return;
		}
		// first time this request being called, so perform security checking
		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
                // 根据资源配置权限来判断当前请求是否有权限来访问对应的资源,如果不能访问则抛出异常
		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
		try {
                        // 访问相关资源需要通过SpringMVC的核心组件DispatcherServlet进行访问
			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
		}
		finally {
			super.finallyInvocation(token);
		}
		super.afterInvocation(token, null);
	}
}

SpringSecurity 请求间共享认证信息

一般认证成功后的用户信息是通过 Session 在多个请求之间共享,那么 Spring Security 中是如何实现将已认证的用户信息对象 Authentication 与 Session 绑定的?

       // 认证成功的处理
       protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			Authentication authResult) throws IOException, ServletException {
		SecurityContextHolder.getContext().setAuthentication(authResult);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
		}
		this.rememberMeServices.loginSuccess(request, response, authResult);
		if (this.eventPublisher != null) {
			this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
		}
		this.successHandler.onAuthenticationSuccess(request, response, authResult);
	}

/**
 * 该类其实是对 ThreadLocal 的封装,存储 SecurityContext 对象
 */
public class SecurityContextHolder {

        public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";

        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();
		}
		else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_GLOBAL)) {
			strategy = new GlobalSecurityContextHolderStrategy();
		}
		else {
			// Try to load a custom strategy
			try {
				Class<?> clazz = Class.forName(strategyName);
				Constructor<?> customStrategy = clazz.getConstructor();
				strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
			}
			catch (Exception ex) {
				ReflectionUtils.handleReflectionException(ex);
			}
		}
		initializeCount++;
	}

	public static void clearContext() {
                // 清除当前线程对应的ThreadLocal<SecurityContext>
		strategy.clearContext();
	}

        public static SecurityContext getContext() {
                // 需要注意,如果当前线程对应的ThreadLocal<SecurityContext>没有存储任何对象,会返回一个空的SecuriyContext对象
		return strategy.getContext();
	}

	public static void setContext(SecurityContext context) {
                // 设置当前线程对应的ThreadLocal<SecurityContext>
		strategy.setContext(context);
	}
}

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

	@Override
	public SecurityContext getContext() {
                // 当前线程对应的ThreadLocal<SecurityContext>没有存储任何对象,会返回一个空的SecuriyContext对象
		SecurityContext ctx = contextHolder.get();
		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}
		return ctx;
	}
}

/**
 * 查看 SecurityContext 接口及其实现类 SecurityContextImpl,该类其实就是对 Authentication 的封装
 */
public class SecurityContextImpl implements SecurityContext {

        private Authentication authentication;

	public SecurityContextImpl() {
	}

	public SecurityContextImpl(Authentication authentication) {
		this.authentication = authentication;
	}

}

/**
 *前面提到过,在 UsernamePasswordAuthenticationFilter 过滤器认证成功之 后,会在认证成功的处理方法中将已认证的用户信息对象 Authentication 封装进 SecurityContext,并存入 SecurityContextHolder
 *之后,响应会通过 SecurityContextPersistenceFilter 过滤器,该过滤器的位置 在所有过滤器的最前面,请求到来先进它,响应返回最后一个通过它,所以在该过滤器中 处理已认证的用户信息对象 Authentication 与 Session 绑定
 *认证成功的响应通过 SecurityContextPersistenceFilter 过滤器时,会从 SecurityContextHolder 中取出封装了已认证用户信息对象 Authentication 的 SecurityContext,放进 Session 中。当请求再次到来时,请求首先经过该过滤器,该过滤 器会判断当前请求的 Session 是否存有 SecurityContext 对象,如果有则将该对象取出再次 放入 SecurityContextHolder 中,之后该请求所在的线程获得认证用户信息,后续的资源访 问不需要进行身份认证;当响应再次返回时,该过滤器同样从 SecurityContextHolder 取出 SecurityContext 对象,放入 Session 中
 */
public class SecurityContextPersistenceFilter extends GenericFilterBean {

        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// ensure that filter is only applied once per request
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		if (this.forceEagerSessionCreation) {
			HttpSession session = request.getSession();
			if (this.logger.isDebugEnabled() && session.isNew()) {
				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
			}
		}
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
                // 请求到来时,检查当前Session中是否有SecurityContext对象,如果有从Session中取出,如果没有创建一个空的SecurityContext对象
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
                        // 将上述获得的SecurityContext对象放入SecurityContextHolder中
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			if (contextBeforeChainExecution.getAuthentication() == null) {
				logger.debug("Set SecurityContextHolder to empty SecurityContext");
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
				}
			}
                        // 进入下一个过滤器
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
                        // 响应返回时,从SecurityContextHolder中取出SecurityContext
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// 移除SecurityContextHolder中的SecurityContext
			SecurityContextHolder.clearContext();
                        // 将取出的SecurityContext放到Session
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);
			this.logger.debug("Cleared SecurityContextHolder to complete request");
		}
	}
}
posted @ 2021-03-16 13:39  叮叮叮叮叮叮当  阅读(218)  评论(0编辑  收藏  举报