前言
本篇是Spring Security 源码之 UsernamePasswordAuthenticationFilter中SessionAuthenticationStrategy
部分的专项分析。
SessionAuthenticationStrategy
主要负责处理认证成功的时候session需要执行的逻辑。
包含如下子类:
- SessionFixationProtectionStrategy:创建一个新的session,将老session的属性迁移到新的session中
- ChangeSessionIdAuthenticationStrategy:仍使用原来的session,仅修改下session id
- ConcurrentSessionControlAuthenticationStrategy:限制用户同时登陆的次数
- CompositeSessionAuthenticationStrategy:组合多个SessionAuthenticationStrategy
- CsrfAuthenticationStrategy:登陆成功之后,更换原来的csrf token
- NullAuthenticatedSessionStrategy:空实现,什么都不操作
- RegisterSessionAuthenticationStrategy:注册新session信息到SessionRegistry
AbstractSessionFixationProtectionStrategy
它是SessionFixationProtectionStrategy
和ChangeSessionIdAuthenticationStrategy
的父类,用来防御session fixation攻击。
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) {
// 是否已经有session
boolean hadSessionAlready = request.getSession(false) != null;
// 如果没有session,并且不用总是创建session,直接返回,不需要防御session fixation
if (!hadSessionAlready && !this.alwaysCreateSession) {
// Session fixation isn't a problem if there's no session
return;
}
// Create new session if necessary
// 获取session
HttpSession session = request.getSession();
// 如果已经有session,并且session有效
if (hadSessionAlready && request.isRequestedSessionIdValid()) {
// 原来的session id
String originalSessionId;
// 新的session id
String newSessionId;
// 获取session对应的互斥锁synchonized object
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// We need to migrate to a new session
// 保存原来的session id
originalSessionId = session.getId();
// 调用子类的applySessionFixation方法,执行攻击防御逻辑
// 这里返回一个session
session = applySessionFixation(request);
// 获取新的session id
newSessionId = session.getId();
}
// 如果新的session和老的还是相同,打印告警信息
if (originalSessionId.equals(newSessionId)) {
this.logger.warn("Your servlet container did not change the session ID when a new session "
+ "was created. You will not be adequately protected against session-fixation attacks");
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Changed session id from %s", originalSessionId));
}
}
// 发布SessionFixationProtectionEvent这个事件
onSessionChange(originalSessionId, session, authentication);
}
}
分析完上面session fixation防御策略这个父类,接下来我们分析下SessionFixationProtectionStrategy
和ChangeSessionIdAuthenticationStrategy
的applySessionFixation
逻辑。
SessionFixationProtectionStrategy
我们查看它的applySessionFixation
方法逻辑:
@Override
final HttpSession applySessionFixation(HttpServletRequest request) {
// 获取session
HttpSession session = request.getSession();
// 获取原来的session id
String originalSessionId = session.getId();
this.logger.debug(LogMessage.of(() -> "Invalidating session with Id '" + originalSessionId + "' "
+ (this.migrateSessionAttributes ? "and" : "without") + " migrating attributes."));
// 获取原来session中的所有属性
Map<String, Object> attributesToMigrate = extractAttributes(session);
// 获取session的超时时间
int maxInactiveIntervalToMigrate = session.getMaxInactiveInterval();
// 将原来的session失效
session.invalidate();
// 创建一个新的session
session = request.getSession(true); // we now have a new session
this.logger.debug(LogMessage.format("Started new session: %s", session.getId()));
// 将原先session的所有属性迁移到新的session上
transferAttributes(attributesToMigrate, session);
// 如果需要迁移session自身的属性
if (this.migrateSessionAttributes) {
// 设置新session的超时时间和旧session一致
session.setMaxInactiveInterval(maxInactiveIntervalToMigrate);
}
// 返回处理过的session
return session;
}
ChangeSessionIdAuthenticationStrategy
它的逻辑很简单,不去创建新的session,仅仅是修改下session id而已。代码如下所示:
@Override
HttpSession applySessionFixation(HttpServletRequest request) {
request.changeSessionId();
return request.getSession();
}
ConcurrentSessionControlAuthenticationStrategy
该策略用于限制用户并发session数量,可用于限制同一用户可同事存在的会话数。
代码分析如下所示:
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) {
// 获取最大允许的session数
int allowedSessions = getMaximumSessionsForThisUser(authentication);
// 如果值为-1,说明没有session数量限制
if (allowedSessions == -1) {
// We permit unlimited logins
return;
}
// 获取当前用户的所有session,不包含失效的session
List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
int sessionCount = sessions.size();
// 如果当前用户session数小于最大允许的session数,无需处理
if (sessionCount < allowedSessions) {
// They haven't got too many login sessions running at present
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
// 挨个遍历现有的session
// 如果有一个session的id和当前session的id相同,这种情况是允许的,无需处理
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失效
allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
}
allowableSessionsExceeded
方法如下所示:
protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,
SessionRegistry registry) throws SessionAuthenticationException {
// 如果需要session数超过最大允许session数时抛出异常,则抛出异常
if (this.exceptionIfMaximumExceeded || (sessions == null)) {
throw new SessionAuthenticationException(
this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
new Object[] { allowableSessions }, "Maximum sessions of {0} for this principal exceeded"));
}
// Determine least recently used sessions, and mark them for invalidation
// 按照session的最后访问时间排序
sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
// 计算超时的session个数
int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
// 截取已经超时的session信息
List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
// 将这些session设置为失效
for (SessionInformation session : sessionsToBeExpired) {
session.expireNow();
}
}
CompositeSessionAuthenticationStrategy
该策略包含了一系列代理策略(delegateStrategies
),逻辑为遍历执行所有的代理策咯。逻辑如下所示:
@Override
public 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);
}
}
CsrfAuthenticationStrategy
用于在认证成功之后,更换一个新的csrf token。
具体逻辑如下所示:
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) throws SessionAuthenticationException {
// 检测是否存在csrf token
boolean containsToken = this.csrfTokenRepository.loadToken(request) != null;
if (containsToken) {
// 清空原来的csrf token
this.csrfTokenRepository.saveToken(null, request, response);
// 生成一个新的csrf token
CsrfToken newToken = this.csrfTokenRepository.generateToken(request);
// 存储这个新的csrf token
this.csrfTokenRepository.saveToken(newToken, request, response);
// 设置csrf相关属性到request中
request.setAttribute(CsrfToken.class.getName(), newToken);
request.setAttribute(newToken.getParameterName(), newToken);
this.logger.debug("Replaced CSRF Token");
}
}
RegisterSessionAuthenticationStrategy
将session信息保存到sessionRegistry
中。
@Override
public void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) {
this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
}
本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。
网友评论