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