記錄 Spring Security 之 ConcurrentSessionControlAuthenticationStrategy 的一個小問題


記錄 pmsys 中使用 ConcurrentSessionControlAuthenticationStrategy 作為 session 的並發控制策略,實現用戶單一登錄,擠掉前一個登錄的用戶。當出現了一些小問題,所以以此記錄。

問題

使用了 ConcurrentSessionControlAuthenticationStrategy 是實現了單一登錄, 但沒完全實現。因為后一個登錄的用戶也被擠掉, 即這個賬號所有用戶都被下線。

原因

ConcurrentSessionControlAuthenticationStrategy 源碼中 onAuthentication 方法.

官方 方法說明: In addition to the steps from the superclass, the sessionRegistry will be updated with the new session information.

機翻: 除了超類中的步驟之外,還將使用新的會話信息更新sessionRegistry。

    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
        // 全部的 session 數量
        int sessionCount = sessions.size();
        // 這里得到可允許的 session 並發數量
        int allowedSessions = this.getMaximumSessionsForThisUser(authentication);
        if (sessionCount >= allowedSessions) {
            if (allowedSessions != -1) {
                if (sessionCount == allowedSessions) {
                    HttpSession session = request.getSession(false);
                    if (session != null) {
                        Iterator var8 = sessions.iterator();

                        while(var8.hasNext()) {
                            SessionInformation si = (SessionInformation)var8.next();
                            if (si.getSessionId().equals(session.getId())) {
                                return;
                            }
                        }
                    }
                }
				// 超出允許的並發數量,就會調用此方法, 原因
                this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
            }
        }
    }

allowableSessionsExceeded 方法源碼:

    protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {
        if (!this.exceptionIfMaximumExceeded && sessions != null) {
            sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
            // 以下兩行代碼就是主要原因
            int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
            List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
            
            Iterator var6 = sessionsToBeExpired.iterator();

            while(var6.hasNext()) {
                SessionInformation session = (SessionInformation)var6.next();
                // 使當前 session 失效
                session.expireNow();
            }

        } else {
            throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded"));
        }
    }

為了實現用戶單一登錄, 那么最大並發數量肯定是 1 (默認也是 1 ), maximumSessionsExceededBy = 2 - 1 + 1 = 2 . 那么 sessionsToBeExpired 就是把兩個 session 都包含其中,然后都令其失效. 從而導致兩個用戶都下線.

這里提一下 subList 方法

List<E> subList(int fromIndex, int toIndex);

這個方法是左開右閉的, 即 tiIndex 這個位置是沒有選到的, 例如: sublist(0,2) , 得到只有集合中下標為 0 , 1 的元素.

還有一些情況,就自行百度.

個人解決方案

繼承 ConcurrentSessionControlAuthenticationStrategy 重寫 allowableSessionsExceeded 方法. 用自己的 session 並發控制策略

@Component
public class MyConcurrentSessionControlAuthenticationStrategy extends ConcurrentSessionControlAuthenticationStrategy {
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    public MyConcurrentSessionControlAuthenticationStrategy(SessionRegistry sessionRegistry) {
        super(sessionRegistry);
    }

    @Override
    protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {
        if (sessions != null) {
            sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
            int maximumSessionsExceededBy = sessions.size() - allowableSessions;
            List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
            Iterator var6 = sessionsToBeExpired.iterator();

            while(var6.hasNext()) {
                SessionInformation session = (SessionInformation)var6.next();
                session.expireNow();
            }

        } else {
            throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded"));
        }
    }
}

實驗結果可行.

GitHub 也有人提出這個問題,但問題待解決. ConcurrentSessionControlAuthenticationStrategy.allowableSessionsExceeded set the latest session to invalid, is this a bug? #9343


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM