前言
spring security的登录逻辑是在UsernamePasswordAuthenticationFilter#attemptAuthentication方法中实现的,但是attemptAuthentication的调用者却是UsernamePasswordAuthenticationFilter的父类AbstractAuthenticationProcessingFilter间接调用的,源码如下
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
//这里就是调用UsernamePasswordAuthenticationFilter#attemptAuthentication
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
//这里就是今天的重点,session并发管理
sessionStrategy.onAuthentication(authResult, request, response);
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
分析
sessionStrategy.onAuthentication(authResult, request, response);
(1)首先通过源码分析,这里的sessionStrategy是一个SessionAuthenticationStrategy接口对象,onAuthentication调用的是此接口的一个实现类ConcurrentSessionControlAuthenticationStrategy,
下面具体看下ConcurrentSessionControlAuthenticationStrategy#onAuthentication
public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
/*
authentication.getPrincipal()获取当前登录的用户对象,以此对象为key,去找对应的value,
这里的value就是此用户所对应的session集合
*/
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
authentication.getPrincipal(), false);
int sessionCount = sessions.size();
//获取允许的 session 并发数
int allowedSessions = getMaximumSessionsForThisUser(authentication);
/*下面三个if,比较当前session与allowedSessions
*/
if (sessionCount < allowedSessions) {
// They haven't got too many login sessions running at present
return;
}
if (allowedSessions == -1) {
// We permit unlimited logins
return;
}
if (sessionCount == allowedSessions) {
HttpSession session = request.getSession(false);
if (session != null) {
// Only permit it though if this request is associated with one of the
// already registered sessions
for (SessionInformation si : sessions) {
if (si.getSessionId().equals(session.getId())) {
return;
}
}
}
// If the session is null, a new one will be created by the parent class,
// exceeding the allowed number
}
//若当前session数大于所限制的session数
allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
}
- 如果当前 session 数(sessionCount)小于 session 并发数(allowedSessions),则不做任何处理;如果 allowedSessions 的值为 -1,表示对 session 数量不做任何限制。
- 如果当前 session 数(sessionCount)等于 session 并发数(allowedSessions),那就先看看当前 session 是否不为 null,并且已经存在于 sessions 中了,如果已经存在了,那都是自家人,不做任何处理;如果当前 session 为 null,那么意味着将有一个新的 session 被创建出来,届时当前 session 数(sessionCount)就会超过 session 并发数(allowedSessions)。
- 如果前面的代码中都没能 return 掉,那么将进入策略判断方法 allowableSessionsExceeded 中。
(2)#allowableSessionsExceeded源码如下
protected void allowableSessionsExceeded(List<SessionInformation> sessions,
int allowableSessions, SessionRegistry registry)
throws SessionAuthenticationException {
//exceptionIfMaximumExceeded 表示是否禁止新的登录
//对应SecurityConfig 中配置的 maxSessionsPreventsLogin 的值
if (exceptionIfMaximumExceeded || (sessions == null)) {
throw new SessionAuthenticationException(messages.getMessage(
"ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
new Object[] { Integer.valueOf(allowableSessions) },
"Maximum sessions of {0} for this principal exceeded"));
}
// 通过遍历session集合方式找出最早登录的session,并将其致为过期session
SessionInformation leastRecentlyUsed = null;
for (SessionInformation session : sessions) {
if ((leastRecentlyUsed == null)
|| session.getLastRequest()
.before(leastRecentlyUsed.getLastRequest())) {
leastRecentlyUsed = session;
}
}
leastRecentlyUsed.expireNow();
}
总结
到这我们了解了spring security如何处理session并发的,分为两步1.判断session是否超过所允许的 session 并发数。2.若超过,则将最早登录的session做过期处理。
下一节: