美文网首页程序员Java
Spring Security 源码之 SessionAuthe

Spring Security 源码之 SessionAuthe

作者: AlienPaul | 来源:发表于2020-11-13 08:40 被阅读0次

前言

本篇是Spring Security 源码之 UsernamePasswordAuthenticationFilterSessionAuthenticationStrategy部分的专项分析。

SessionAuthenticationStrategy

主要负责处理认证成功的时候session需要执行的逻辑。

包含如下子类:

  • SessionFixationProtectionStrategy:创建一个新的session,将老session的属性迁移到新的session中
  • ChangeSessionIdAuthenticationStrategy:仍使用原来的session,仅修改下session id
  • ConcurrentSessionControlAuthenticationStrategy:限制用户同时登陆的次数
  • CompositeSessionAuthenticationStrategy:组合多个SessionAuthenticationStrategy
  • CsrfAuthenticationStrategy:登陆成功之后,更换原来的csrf token
  • NullAuthenticatedSessionStrategy:空实现,什么都不操作
  • RegisterSessionAuthenticationStrategy:注册新session信息到SessionRegistry

AbstractSessionFixationProtectionStrategy

它是SessionFixationProtectionStrategyChangeSessionIdAuthenticationStrategy的父类,用来防御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防御策略这个父类,接下来我们分析下SessionFixationProtectionStrategyChangeSessionIdAuthenticationStrategyapplySessionFixation逻辑。

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());
}

本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。

相关文章

网友评论

    本文标题:Spring Security 源码之 SessionAuthe

    本文链接:https://www.haomeiwen.com/subject/zmxvbktx.html