上一篇讲了 Security 的 Filter 是怎么运行的 , 这一篇我们来看看 Security 的认证流程 .
二 . 认证信息的流转
2.1 SecurityContext 基本对象信息
Security 核心信息就是
SecurityContext
, 我们来看看认证信息是怎么确定和流转的
SecurityContextHolder
是 Spring Security 存储被验证者的详细信息的地方。Spring Security 不关心 SecurityContextHolder 是如何填充的。如果它包含一个值,则将其用作当前经过身份验证的用户。
生成一个 SecurityContext
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
获得已经认证的用户
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
SecurityContextHolder 的相关逻辑
默认情况下 SecurityContextHolder 使用 ThreadLocal 来存储这些细节 (PS : 也可以通过 SecurityContextHolder.MODE global 配置)
第一个是设置系统属性
第二个是调用 SecurityContextHolder 上的静态方法
Spring Security 的 FilterChainProxy 确保 SecurityContext 总是被清除
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();
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
strategyName = MODE_THREADLOCAL;
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
}else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
}else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
}else {
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
initializeCount++;
public static void setContext(SecurityContext context) {
strategy.setContext(context);
GlobalSecurityContextHolderStrategy : 其中 SecurityContext 就是个静态变量
InheritableThreadLocalSecurityContextHolderStrategy : 其中包含一个 ThreadLocal<SecurityContext>
ThreadLocalSecurityContextHolderStrategy : 和上一个没什么区别
2.2 SecurityContext 流程
sequenceDiagram
FilterChainProxy->>AbstractAuthenticationProcessingFilter: 调用 doFilter
AbstractAuthenticationProcessingFilter-->>DatabaseAuthenticationFilter: 调用attemptAuthentication
DatabaseAuthenticationFilter-->>AuthenticationManager: 通过 Manager 调用 provider
AuthenticationManager-->> Provider: 调用 Provider 请求 Authentication
Provider-->> AuthenticationManager: 然会 Authentication
AuthenticationManager-->>DatabaseAuthenticationFilter: 返回 Authentication
DatabaseAuthenticationFilter-->>AbstractAuthenticationProcessingFilter: 返回 Authentication
Note right of AbstractAuthenticationProcessingFilter: 这里会调用相关Handler 最终处理 Authentication
Step 1 : 调用 Provider 处理情况 , 这里认证完成后返回了一个 Authentication
DatabaseUserToken authRequest = new DatabaseUserToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
Step 2 : Provider 处理
这里的 AuthenticationManager 主要是 ProviderManager 主要是这些 ,我们仅保留其中比较重要的逻辑 :
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}catch (AccountStatusException|InternalAuthenticationServiceException e)
prepareException(e, authentication);
throw e;
}catch (AuthenticationException e) {
lastException = e;
if (result == null && parent != null) {
try {
result = parentResult = parent.authenticate(authentication);
}catch (AuthenticationException e) {
lastException = e;
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
((CredentialsContainer) result).eraseCredentials();
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
return result;
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
prepareException(lastException, authentication);
throw lastException;
可以看到 ,到这一步 Provider 返回了一个 Authentication 回去
Step 3 : AbstractAuthenticationProcessingFilter 处理
从第二步 Prodiver 返回了 Authentication , 他最终被传递到 AbstractAuthenticationProcessingFilter 中
authResult = attemptAuthentication(request, response);
sessionStrategy.onAuthentication(authResult, request, response)
successfulAuthentication(request, response, chain, authResult)
|- SecurityContextHolder.getContext().setAuthentication(authResult)
|- rememberMeServices.loginSuccess(request, response, authResult)
|- successHandler.onAuthenticationSuccess(request, response, authResult)
至此 , Provider 产生的 Authentication 成功放入 容器中
扩展 SavedRequestAwareAuthenticationSuccessHandler 处理 Success 结果
总结一下就是定制缓存和跳转关系 >>>
C- SavedRequestAwareAuthenticationSuccessHandler
P- RequestCache requestCache : Request 缓存工具 , 用户获取缓存的 Request 对象
M- onAuthenticationSuccess
- SavedRequest savedRequest = requestCache.getRequest(request, response) : 先获取缓存的对象
- String targetUrlParameter = getTargetUrlParameter() : 这里是看看有没有成功的跳转地址
?- 如果想实现不同用户不同跳转 ,定制这里
- clearAuthenticationAttributes(request) : 删除与身份验证相关的临时数据,这些数据可能在身份验证过程中存储在会话中 , 避免敏感信息泄露
- String targetUrl = savedRequest.getRedirectUrl();
- getRedirectStrategy().sendRedirect(request, response, targetUrl);
?- 重定向出去
以上是认证从和认证失败的流程图 , 可以看到具体的处理类 :
总结一下认证成功和认证失败分别干了什么 :
如果认证失败:
Security contextholder 被清空了。
调用 RememberMeServices.loginFail。如果没有配置 rememberme,这是一个 no-op
调用 AuthenticationFailureHandler。
同时对比一下认证成功:
会在新登录时通知 SessionAuthenticationStrategy
在 SecurityContextHolder 上设置身份验证,然后 SecurityContextPersistenceFilter 将 SecurityContext 保存到 HttpSession 中
调用 RememberMeServices.loginSuccess。如果没有配置 remember me,这是一个 no-op
ApplicationEventPublisher 发布交互式身份验证连接
调用 AuthenticationSuccessHandler
三 . 再次访问和退出
上面说了一个认证过程中发生了什么 , 这里我们看下认证完成后再次访问>>>
3.1 认证后访问
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
C- HttpSessionSecurityContextRepository
SecurityContext context = readSecurityContextFromSession(httpSession);
org.springframework.security.core.context.SecurityContextImpl@45eed422:
Authentication: com.security.demo.token.DatabaseUserToken@45eed422:
Principal: gang;
Credentials: [PROTECTED];
Authenticated: true;
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffdaa08: RemoteIpAddress: 127.0.0.1;
SessionId: A58D946FBFCB17743E2E0A44DBAB7A76;
Granted Authorities: ROLE_USER
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());
PS : 因为是基于 Session 管理 , 所以过一会就过期了
当然这是基于 Session 的模式 ,生命周期和 Session 等同 ,但是通常会常用更长的生命周期方案 ,比如 AccessToken , Cookie 等等 ,而 Session 只是为了维持一个认证的临时状态
3.2 Logout 退出
Logout 相关类 :
PersistentTokenBasedRememberMeServices
TokenBasedRememberMeServices
CookieClearingLogoutHandler
CsrfLogoutHandler
SecurityContextLogoutHandler
HeaderWriterLogoutHandler
同样的 , Logout 也有 Filter 和 Handler
LogoutFilter
SimpleUrlLogoutSuccessHandler
HttpStatusReturningLogoutSuccessHandler
和前面分析 Filter 一样 , 其核心还是通过 LogoutFilter 来进行 :
this.handler.logout(request, response, auth);
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
C- SecurityContextLogoutHandler : 核心类 , 处理 Context
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
logger.debug("Invalidating session: " + session.getId());
session.invalidate();
if (clearAuthentication) {
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(null);
SecurityContextHolder.clearContext();
至此 , 一个完整的 Security 生命周期就看完了, 其实很简单 , 总结起来就是 :
Filter 做业务决定
AuthenticationManager 决定校验方式
Provider 进行认证校验
Handler做结果处理已经外部跳转
后面一章我们来详细看看Security 的配置逻辑 ,看看底层发生了什么