一、前言
在上篇
Spring Security 6.x 系列(13)—— 会话管理之会话概念及常用配置
Spring Security 6.x 系列(14)—— 会话管理之会话固定攻击防护及Session共享
中了清晰了协议和会话的概念、对 Spring Security
中的常用会话配置进行了说明,并了解会话固定攻击防护和 Session
共享,今天我们着重对会话流程和部分源码进行分析。
二、保存会话
2.1 认证流程
以表单登录为例,提交登录后,进入 UsernamePasswordAuthenticationFilter
过滤器,认证流程如下:
其中源码如下:
如果身份验证成功,执行如下:
- 在 ① 中 调用
SessionAuthenticationStrategy
进行会话策略处理,常用策略处理方式有:- 会话存储
- 更改
sessionid
- 防范会话固定攻击
- 在 ② 中调用
successfulAuthentication
方法:-
在
SecurityContextHolder
上存储认证信息 -
在
SecurityContextRepository
上显式保存认证信息 -
RememberMeServices#loginSuccess
被调用。 如果未配置“记住我”,则为空操作。 -
ApplicationEventPublisher#InteractiveAuthenticationSuccessEvent
被调用发布认证成功事件。 -
AuthenticationSuccessHandler
被调用,重定向登录前URL。
-
我们先沿着 ① 分析会话策略。
2.2 会话策略
2.2.1 SessionAuthenticationStrategy
SessionAuthenticationStrategy
源码比较简单,就一个 onAuthentication
方法,当发生新的身份验证时,执行与 Http
会话相关的功能:
/*** Allows pluggable support for HttpSession-related behaviour when an authentication* occurs.* <p>* Typical use would be to make sure a session exists or to change the session Id to guard* against session-fixation attacks.** @author Luke Taylor* @since*/
public interface SessionAuthenticationStrategy {/*** Performs Http session-related functionality when a new authentication occurs.* @throws SessionAuthenticationException if it is decided that the authentication is* not allowed for the session. This will typically be because the user has too many* sessions open at once.*/void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response)throws SessionAuthenticationException;}
SessionAuthenticationStrategy
只是一个接口,这个接口有很多实现类:
我们在 DEBUG
看下在 UsernamePasswordAuthenticationFilter
过滤器,在认证成功时 SessionAuthenticationStrategy
的实现类,发现 CompositeSessionAuthenticationStrategy
:
2.2.2 CompositeSessionAuthenticationStrategy
直接看 CompositeSessionAuthenticationStrategy
的 onAuthentication
方法实现:
/*** A {@link SessionAuthenticationStrategy} that accepts multiple* {@link SessionAuthenticationStrategy} implementations to delegate to. Each* {@link SessionAuthenticationStrategy} is invoked in turn. The invocations are short* circuited if any exception, (i.e. SessionAuthenticationException) is thrown.** <p>* Typical usage would include having the following delegates (in this order)* </p>** <ul>* <li>{@link ConcurrentSessionControlAuthenticationStrategy} - verifies that a user is* allowed to authenticate (i.e. they have not already logged into the application.</li>* <li>{@link SessionFixationProtectionStrategy} - If session fixation is desired,* {@link SessionFixationProtectionStrategy} should be after* {@link ConcurrentSessionControlAuthenticationStrategy} to prevent unnecessary* {@link HttpSession} creation if the* {@link ConcurrentSessionControlAuthenticationStrategy} rejects authentication.</li>* <li>{@link RegisterSessionAuthenticationStrategy} - It is important this is after* {@link SessionFixationProtectionStrategy} so that the correct session is registered.* </li>* </ul>** @author Rob Winch* @since 3.2*/
public class CompositeSessionAuthenticationStrategy implements SessionAuthenticationStrategy {private final Log logger = LogFactory.getLog(getClass());private final List<SessionAuthenticationStrategy> delegateStrategies;public CompositeSessionAuthenticationStrategy(List<SessionAuthenticationStrategy> delegateStrategies) {Assert.notEmpty(delegateStrategies, "delegateStrategies cannot be null or empty");for (SessionAuthenticationStrategy strategy : delegateStrategies) {Assert.notNull(strategy, () -> "delegateStrategies cannot contain null entires. Got " + delegateStrategies);}this.delegateStrategies = delegateStrategies;}@Overridepublic void onAuthentication(Authentication authentication, HttpServletRequest request,HttpServletResponse response) throws SessionAuthenticationException {int currentPosition = 0;int size = this.delegateStrategies.size();for (SessionAuthenticationStrategy delegate : this.delegateStrategies) {if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Preparing session with %s (%d/%d)",delegate.getClass().getSimpleName(), ++currentPosition, size));}delegate.onAuthentication(authentication, request, response);}}@Overridepublic String toString() {return getClass().getName() + " [delegateStrategies = " + this.delegateStrategies + "]";}}
是不是很熟悉?和我们在Spring Security 6.x 系列(11)—— Form表单认证和注销流程 中4.5 章节提到的注销处理器很相似。
CompositeSessionAuthenticationStrategy
中有一个 list
集合存储着 SessionAuthenticationStrategy
的实现类,然后遍历这个 list
,让它们去做真正的 session
处理操作。
现在的问题是,这个 delegateStrategies
中到底存储的是那些实现类呢?我们稍后进行解答。
2.2.3 SessionRegistry
可能有心急的小伙伴上来就想看 ConcurrentSessionControlAuthenticationStrategy
、ChangeSessionIdAuthenticationStrategy
、RegisterSessionAuthenticationStrategy
、CsrfAuthenticationStrategy
这四个strategy
,但抱歉我们还缺点置条件,这个条件就是 SessionRegistry
。
搞明白 SessionRegistry
就知道了 Spring Security
是怎么保存 认证对象
和 Session
的。
public interface SessionRegistry {/*** 获取所有的principal/List<Object> getAllPrincipals();/*** 获取principal的所有session/List<SessionInformation> getAllSessions(Object principal,boolean includeExpiredSessions);/*** 根据sessionId获取session的详细信息/SessionInformation getSessionInformation(String sessionId);/*** 根据sessionId刷新session信息/void refreshLastRequest(String sessionId);/*** 注册新的session信息/void registerNewSession(String sessionId, Object principal);/*** 根据sessionId删除session信息/void removeSessionInformation(String sessionId);
}
2.2.4 SessionRegistryImpl
Spring Security
通过SessionRegistryImpl
来实现 Session
会话的统一管理,先看这个类的属性:
// <principal:Object,SessionIdSet>
private final ConcurrentMap<Object, Set<String>> principals;// <sessionId:Object,SessionInformation>
private final Map<String, SessionInformation> sessionIds;public SessionRegistryImpl() {
this.principals = new ConcurrentHashMap<>();
this.sessionIds = new ConcurrentHashMap<>();
}public SessionRegistryImpl(ConcurrentMap<Object, Set<String>> principals