SpringSecurity访问多人在线Session管理的实现原理



/**
 * http.sessionManagement,表示开启session管理,会导入一个SessionManagementConfigurer配置信息
 * 而SessionManagementConfigurer导入一些核心配置
 */
class SessionSource {
    // HttpSecurity: 是SecurityFilterChain的构造者,该SecurityFilterChain是对于某个,或者某部分,甚至所有的url进行拦截的一个过滤器链
    // 根据HttpSecurity的配置决定最终SecurityFilterChain过滤器链包含的过滤器有多少,如何工作
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // session会话管理
        /**
         * 开启会话管理{@link SessionManagementConfigurer}
         */
        http.sessionManagement(session -> {
                    // 最多一个账号允许在几个不同的客户端登录
                    session.maximumSessions(1)
                            .maxSessionsPreventsLogin(true)
                            .expiredSessionStrategy(sessionExpiredHandler).expiredUrl("/login.html")
                            // 会话的注册表,就是保存session会话的地方
                            // 如果是分布式会话,则需要改为分布式会话的注册表
                            .sessionRegistry(new SessionRegistryImpl());

                    // 在认证成功之后,会回调该sessionAuthenticationStrategy接口
                    // 比authenticationSuccessHandler先执行
                    session.sessionAuthenticationStrategy(sessionAuthenticationStrategy);
                    // session失效会重定向到的url
                    // session.invalidSessionUrl("/invalid");
                    // session失效的处理策略
                    session.invalidSessionStrategy(new InvalidSessionStrategy() {
                        @Override
                        public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
                            log.error("当前会话失效");
                        }
                    });
                    // 会话固定的保护策略
                    // session.sessionFixation((sessionFixation) -> sessionFixation.newSession());
                    // 创建会话的策略ALWAYS,立刻创建还是不创建等等选项
                    session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
                }
        );
        return http.build();
    }
}

// 会话管理配置
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<SessionManagementConfigurer<H>, H> {

    // 默认的策略Session认证策略,在认证成功之后会执行
    private final SessionAuthenticationStrategy DEFAULT_SESSION_FIXATION_STRATEGY = new ChangeSessionIdAuthenticationStrategy();
    // 固定需要的Session认证策略,就是为了改变SessionID
    private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = this.DEFAULT_SESSION_FIXATION_STRATEGY;
    // 最终保存需要使用的Session认证策略
    private SessionAuthenticationStrategy sessionAuthenticationStrategy;
    // 用户自己提供了(自定义设置)的Session认证策略
    private SessionAuthenticationStrategy providedSessionAuthenticationStrategy;
    // Session失效的策略,例如到了过期时间
    private InvalidSessionStrategy invalidSessionStrategy;
    // 会话信息过期的策略,一般用在多端登录会话被挤出session会话过期的策略
    private SessionInformationExpiredStrategy expiredSessionStrategy;
    // 所有的会话策略,在别的配置中也可以将SessionAuthenticationStrategy保存到当前变量中
    // 额外的Session认证策略
    private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();
    // 会话的注册表,就是会话保存到哪里
    private SessionRegistry sessionRegistry;
    // 最大同时支持的会话个数
    private Integer maximumSessions;
    // 会话过期(被挤)跳转的url
    private String expiredUrl;
    // 达到最大的会话个数是否阻止登录
    private boolean maxSessionsPreventsLogin;
    // Session的创建策略
    private SessionCreationPolicy sessionPolicy;
    // 启用会话Url重写功能
    private boolean enableSessionUrlRewriting;
    // session失效跳转的url(例如到了过期时间)
    private String invalidSessionUrl;
    // 认证失败需要跳转的url
    private String sessionAuthenticationErrorUrl;
    // 认证失败的处理器
    private AuthenticationFailureHandler sessionAuthenticationFailureHandler;
    // 需要隐式身份验证的属性信息
    private Set<String> propertiesThatRequireImplicitAuthentication = new HashSet<>();
    // 是否需要显式的进行身份验证,就是不经过session认证,直接身份认证
    private Boolean requireExplicitAuthenticationStrategy;
    // 基于Session的SecurityContext持久化策略
    private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository();

    // session失效跳转的url(例如到了过期时间)
    public SessionManagementConfigurer<H> invalidSessionUrl(String invalidSessionUrl) {
        this.invalidSessionUrl = invalidSessionUrl;
        // 添加隐式身份验证(session认证)的属性
        this.propertiesThatRequireImplicitAuthentication.add("invalidSessionUrl = " + invalidSessionUrl);
        return this;
    }

    // 是否需要显式的进行身份验证,就是不经过session认证,直接身份认证
    public SessionManagementConfigurer<H> invalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) {
        this.invalidSessionStrategy = invalidSessionStrategy;
        // 添加隐式身份验证(session认证)的属性
        this.propertiesThatRequireImplicitAuthentication.add("invalidSessionStrategy = " + invalidSessionStrategy);
        return this;
    }

    // session会话认证失败的处理器
    public SessionManagementConfigurer<H> sessionAuthenticationFailureHandler(
            AuthenticationFailureHandler sessionAuthenticationFailureHandler) {
        this.sessionAuthenticationFailureHandler = sessionAuthenticationFailureHandler;
        // 添加隐式身份验证(session认证)的属性
        this.propertiesThatRequireImplicitAuthentication.add("sessionAuthenticationFailureHandler = " + sessionAuthenticationFailureHandler);
        return this;
    }


    // 添加额外的Session认证策略
    public SessionManagementConfigurer<H> addSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
        this.sessionAuthenticationStrategies.add(sessionAuthenticationStrategy);
        return this;
    }

    /**
     * <pre>
     *      设置固定会话的保护策略
     *      1. Security提供了三种推荐选项
     *         1. changeSessionId ,修改JSESSIONID
     *         2. newSession,创建一个新会话,不复制当前会话信息(但是Security相关会话信息还是回复制)
     *         3. migrateSession ,创建一个新会话,合并已存在的会话信息
     *         4. none,关闭防护
     *      2. 当会话固定攻击触发时,会发布SessionFixationProte  ctionEvent事件,如果使用changeSessionId ,还会导致`HttpSessionIdListener`的触发
     * </pre>
     */
    public SessionFixationConfigurer sessionFixation() {
        return new SessionFixationConfigurer();
    }

    // 开启会话的固定防护 设置固定会话的保护策略
    public SessionManagementConfigurer<H> sessionFixation(Customizer<SessionFixationConfigurer> sessionFixationCustomizer) {
        sessionFixationCustomizer.customize(new SessionFixationConfigurer());
        return this;
    }

    // 设置最大同时支持的会话个数
    public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
        this.maximumSessions = maximumSessions;
        this.propertiesThatRequireImplicitAuthentication.add("maximumSessions = " + maximumSessions);
        // 返回一个并发控制的配置信息
        return new ConcurrencyControlConfigurer();
    }

    // 设置Session的并发配置
    public SessionManagementConfigurer<H> sessionConcurrency(Customizer<ConcurrencyControlConfigurer> sessionConcurrencyCustomizer) {
        sessionConcurrencyCustomizer.customize(new ConcurrencyControlConfigurer());
        return this;
    }

    // 当前配置类的初始化信息,到时候会执行所有配置类的init方法
    @Override
    public void init(H http) {
        // 获取共享的SecurityContext持久化策略
        SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
        // 如果session设置了是无状态的
        boolean stateless = this.isStateless();
        // 如果没有将SecurityContextRepository设置到共享对象中
        if (securityContextRepository == null) {
            // 无状态
            if (stateless) {
                // 不需要持久化SecurityContext
                http.setSharedObject(SecurityContextRepository.class, new RequestAttributeSecurityContextRepository());
                this.sessionManagementSecurityContextRepository = new NullSecurityContextRepository();
            } else {
                // 创建基于Session的持久化SecurityContext策略
                HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
                httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);
                httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
                AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
                if (trustResolver != null) {
                    httpSecurityRepository.setTrustResolver(trustResolver);
                }
                this.sessionManagementSecurityContextRepository = httpSecurityRepository;
                // 创建一个基于代理的DelegatingSecurityContextRepository保存都共享对象池中
                // 其中包括Session和Request,则说明SecurityContext策略可以保存到这个地方
                DelegatingSecurityContextRepository defaultRepository = new DelegatingSecurityContextRepository(httpSecurityRepository, new RequestAttributeSecurityContextRepository());
                http.setSharedObject(SecurityContextRepository.class, defaultRepository);
            }
        } else {
            // 已经在共享变量中
            this.sessionManagementSecurityContextRepository = securityContextRepository;
        }
        // 获取共享变量中的RequestCache请求缓存,该接口保存了当前请求的信息
        RequestCache requestCache = http.getSharedObject(RequestCache.class);
        if (requestCache == null) {
            // 如果是无状态的,就无效保存请求数据
            if (stateless) {
                http.setSharedObject(RequestCache.class, new NullRequestCache());
            }
        }
        // 设置为共享的对象
        http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));
        http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
    }

    // 当前配置类的配置信息,到时候会执行所有配置类的configure方法
    @Override
    public void configure(H http) {
        // 创建标准的会话过滤器
        SessionManagementFilter sessionManagementFilter = createSessionManagementFilter(http);
        if (sessionManagementFilter != null) {
            http.addFilter(sessionManagementFilter);
        }
        // 如果设置了并发session控制,添加并发会话管理的过滤器
        if (this.isConcurrentSessionControlEnabled()) {
            ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http);
            concurrentSessionFilter = postProcess(concurrentSessionFilter);
            http.addFilter(concurrentSessionFilter);
        }
        if (!this.enableSessionUrlRewriting) {
            http.addFilter(new DisableEncodeUrlFilter());
        }
        if (this.sessionPolicy == SessionCreationPolicy.ALWAYS) {
            http.addFilter(new ForceEagerSessionCreationFilter());
        }
    }

    // 是否需要显示的进行身份认证,如果为true,不经过seesion处理,直接要进行身份认证
    private boolean shouldRequireExplicitAuthenticationStrategy() {
        // 是否存在隐式身份验证的属性信息
        boolean defaultRequireExplicitAuthenticationStrategy = this.propertiesThatRequireImplicitAuthentication.isEmpty();
        // 如果没有设置需要显示的进行身份认证
        if (this.requireExplicitAuthenticationStrategy == null) {
            // 也没有设置隐式身份验证的属性信息,则返回true
            return defaultRequireExplicitAuthenticationStrategy;
        }
        // 既设置了需要显示的进行身份认证,又设置了隐式身份验证的属性信息,抛出异常
        if (this.requireExplicitAuthenticationStrategy && !defaultRequireExplicitAuthenticationStrategy) {
            throw new IllegalStateException(
                    "Invalid configuration that explicitly sets requireExplicitAuthenticationStrategy to "
                            + this.requireExplicitAuthenticationStrategy
                            + " but implicitly requires it due to the following properties being set: "
                            + this.propertiesThatRequireImplicitAuthentication);
        }
        return this.requireExplicitAuthenticationStrategy;
    }

    // 创建标准的会话过滤器
    private SessionManagementFilter createSessionManagementFilter(H http) {
        // 是否需要显示的进行身份认证,如果为true,不经过seesion处理,直接要进行身份认证
        if (this.shouldRequireExplicitAuthenticationStrategy()) {
            return null;
        }
        SecurityContextRepository securityContextRepository = this.sessionManagementSecurityContextRepository;
        // 创建标准的会话过滤器
        SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy(http));
        // 如果设置了认证失败需要跳转的url
        if (this.sessionAuthenticationErrorUrl != null) {
            // 设置一个认证失败的处理器
            sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(this.sessionAuthenticationErrorUrl));
        }
        // 设置Session失效策略
        InvalidSessionStrategy strategy = getInvalidSessionStrategy();
        if (strategy != null) {
            sessionManagementFilter.setInvalidSessionStrategy(strategy);
        }
        // 设置认证失败处理器,会覆盖sessionAuthenticationErrorUrl设置的
        AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler();
        if (failureHandler != null) {
            sessionManagementFilter.setAuthenticationFailureHandler(failureHandler);
        }
        // AuthenticationTrustResolver接口提供身份类型的判断,会在ExceptionTranslationFilter处理该接口
        AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
        if (trustResolver != null) {
            sessionManagementFilter.setTrustResolver(trustResolver);
        }
        sessionManagementFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
        // 执行该Bean的生命周期
        return postProcess(sessionManagementFilter);
    }

    // 创建并发会话管理的过滤器
    private ConcurrentSessionFilter createConcurrencyFilter(H http) {
        SessionInformationExpiredStrategy expireStrategy = this.getExpiredSessionStrategy();
        SessionRegistry sessionRegistry = getSessionRegistry(http);
        // 创建并发会话管理的过滤器
        ConcurrentSessionFilter concurrentSessionFilter = (expireStrategy != null)
                ? new ConcurrentSessionFilter(sessionRegistry, expireStrategy)
                : new ConcurrentSessionFilter(sessionRegistry);
        // 获取注销配置
        LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
        // 如果存在注销配置
        if (logoutConfigurer != null) {
            // 获取注销处理器
            List<LogoutHandler> logoutHandlers = logoutConfigurer.getLogoutHandlers();
            if (!CollectionUtils.isEmpty(logoutHandlers)) {
                // 设置注销处理器
                concurrentSessionFilter.setLogoutHandlers(logoutHandlers);
            }
        }
        concurrentSessionFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
        return concurrentSessionFilter;
    }


    // Session失效的策略,例如到了过期时间
    public InvalidSessionStrategy getInvalidSessionStrategy() {
        if (this.invalidSessionStrategy != null) {
            return this.invalidSessionStrategy;
        }
        if (this.invalidSessionUrl == null) {
            return null;
        }
        this.invalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy(this.invalidSessionUrl);
        return this.invalidSessionStrategy;
    }

    // 会话信息过期的策略,一般用在多端登录会话被挤出session会话过期的策略
    public SessionInformationExpiredStrategy getExpiredSessionStrategy() {
        if (this.expiredSessionStrategy != null) {
            return this.expiredSessionStrategy;
        }
        if (this.expiredUrl == null) {
            return null;
        }
        this.expiredSessionStrategy = new SimpleRedirectSessionInformationExpiredStrategy(this.expiredUrl);
        return this.expiredSessionStrategy;
    }

    // 获取session认证失败处理器,只要设置了才会有
    public AuthenticationFailureHandler getSessionAuthenticationFailureHandler() {
        if (this.sessionAuthenticationFailureHandler != null) {
            return this.sessionAuthenticationFailureHandler;
        }
        if (this.sessionAuthenticationErrorUrl == null) {
            return null;
        }
        this.sessionAuthenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler(this.sessionAuthenticationErrorUrl);
        return this.sessionAuthenticationFailureHandler;
    }

    // 获取Session的创建策略
    public SessionCreationPolicy getSessionCreationPolicy() {
        if (this.sessionPolicy != null) {
            return this.sessionPolicy;
        }
        // 先从Spring容器中获取SessionCreationPolicy,如果不存在,则使用默认策略,有需要就创建
        SessionCreationPolicy sessionPolicy = getBuilder().getSharedObject(SessionCreationPolicy.class);
        return (sessionPolicy != null) ? sessionPolicy : SessionCreationPolicy.IF_REQUIRED;
    }

    // 如果SessionCreationPolicy允许创建会话,则返回true
    private boolean isAllowSessionCreation() {
        SessionCreationPolicy sessionPolicy = getSessionCreationPolicy();
        return SessionCreationPolicy.ALWAYS == sessionPolicy || SessionCreationPolicy.IF_REQUIRED == sessionPolicy;
    }

    // 如果Session是无状态的,返回true,无状态就不需要创建会话,一次请求就消失了
    private boolean isStateless() {
        SessionCreationPolicy sessionPolicy = getSessionCreationPolicy();
        return SessionCreationPolicy.STATELESS == sessionPolicy;
    }

    // 获取Session的认证策略,包括默认的自定义的
    private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
        // 最终保存需要使用的Session认证策略,说明已经保存过,直接返回
        if (this.sessionAuthenticationStrategy != null) {
            return this.sessionAuthenticationStrategy;
        }
        // 所有的会话策略,在别的配置中也可以将SessionAuthenticationStrategy保存到当前变量中
        // 额外的Session认证策略
        List<SessionAuthenticationStrategy> delegateStrategies = this.sessionAuthenticationStrategies;
        SessionAuthenticationStrategy defaultSessionAuthenticationStrategy;
        // 如果用户没有提供自定义的会话认证策略,则使用默认的(改变SessionId)
        if (this.providedSessionAuthenticationStrategy == null) {
            defaultSessionAuthenticationStrategy = postProcess(this.sessionFixationAuthenticationStrategy);
        } else {
            // 如果用户自定义了,则使用用户的
            defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy;
        }
        // 是否开启多端登录,会话并发管理,如果设置了并发会话数,则返回true。
        if (this.isConcurrentSessionControlEnabled()) {
            // 获取到会话注册表
            SessionRegistry sessionRegistry = getSessionRegistry(http);
            // 创建并发控制的Session认证策略
            ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
            // 设置最大并发数
            concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);
            // 达到最大的会话个数是否阻止登录
            concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);
            // 执行Bean的初始化和自动装配
            concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy);
            // 再创建注册Session的Session认证策略
            RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry);
            // 执行Bean的初始化和自动装配
            registerSessionStrategy = postProcess(registerSessionStrategy);
            // 保存所有的会话认证策略
            // 有三个
            delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy, defaultSessionAuthenticationStrategy, registerSessionStrategy));

        } else {
            // 如果没有开启并发策略,只添加用户自定的或者默认的改变SessionId的认证策略
            delegateStrategies.add(defaultSessionAuthenticationStrategy);
        }
        // 包装成CompositeSessionAuthenticationStrategy合并的认证策略
        this.sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy(delegateStrategies));
        return this.sessionAuthenticationStrategy;
    }

    // 获取会话的注册表,就是会话保存到哪里
    private SessionRegistry getSessionRegistry(H http) {
        if (this.sessionRegistry == null) {
            // 从容器中获取SessionRegistry的Bean
            this.sessionRegistry = getBeanOrNull(SessionRegistry.class);
        }
        if (this.sessionRegistry == null) {
            // 如果容器中不存在,使用默认的
            SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
            // 注册sessionRegistry这个监听器
            this.registerDelegateApplicationListener(http, sessionRegistry);
            this.sessionRegistry = sessionRegistry;
        }
        return this.sessionRegistry;
    }

    // 注册监听器,用于监控Session会话的事件
    private void registerDelegateApplicationListener(H http, ApplicationListener<?> delegate) {
        DelegatingApplicationListener delegating = getBeanOrNull(DelegatingApplicationListener.class);
        if (delegating == null) {
            return;
        }
        SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate);
        delegating.addListener(smartListener);
    }

    // 是否开启多端登录,会话并发管理,如果设置了并发会话数,则返回true。
    private boolean isConcurrentSessionControlEnabled() {
        return this.maximumSessions != null;
    }


    // 从Spring容器中获取Bean
    private <T> T getBeanOrNull(Class<T> type) {
        ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
        if (context == null) {
            return null;
        }
        try {
            return context.getBean(type);
        } catch (NoSuchBeanDefinitionException ex) {
            return null;
        }
    }

    /**
     * <pre>
     *      设置固定会话的保护策略
     *      1. Security提供了三种推荐选项
     *         1. changeSessionId ,修改JSESSIONID
     *         2. newSession,创建一个新会话,不复制当前会话信息(但是Security相关会话信息还是回复制)
     *         3. migrateSession ,创建一个新会话,合并已存在的会话信息
     *         4. none,关闭防护
     *      2. 当会话固定攻击触发时,会发布SessionFixationProte  ctionEvent事件,如果使用changeSessionId ,还会导致`HttpSessionIdListener`的触发
     * </pre>
     */
    public final class SessionFixationConfigurer {

        // 修改JSESSIONID
        // newSession,创建一个新会话,不复制当前会话信息(但是Security相关会话信息还是会复制)
        public SessionManagementConfigurer<H> newSession() {
            SessionFixationProtectionStrategy sessionFixationProtectionStrategy = new SessionFixationProtectionStrategy();
            sessionFixationProtectionStrategy.setMigrateSessionAttributes(false);
            setSessionFixationAuthenticationStrategy(sessionFixationProtectionStrategy);
            return SessionManagementConfigurer.this;
        }

        // 创建一个新会话,合并已存在的会话信息
        public SessionManagementConfigurer<H> migrateSession() {
            setSessionFixationAuthenticationStrategy(new SessionFixationProtectionStrategy());
            return SessionManagementConfigurer.this;
        }

        // 修改JSESSIONID
        public SessionManagementConfigurer<H> changeSessionId() {
            setSessionFixationAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy());
            return SessionManagementConfigurer.this;
        }

        // 不进行任何防护
        public SessionManagementConfigurer<H> none() {
            setSessionFixationAuthenticationStrategy(new NullAuthenticatedSessionStrategy());
            return SessionManagementConfigurer.this;
        }

    }

    /**
     * Session并发控制,多端登录的配置,同{@link SessionManagementConfigurer}的配置
     */
    public final class ConcurrencyControlConfigurer {

        private ConcurrencyControlConfigurer() {
        }


        public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
            SessionManagementConfigurer.this.maximumSessions = maximumSessions;
            return this;
        }

        public ConcurrencyControlConfigurer expiredUrl(String expiredUrl) {
            SessionManagementConfigurer.this.expiredUrl = expiredUrl;
            return this;
        }


        public ConcurrencyControlConfigurer expiredSessionStrategy(SessionInformationExpiredStrategy expiredSessionStrategy) {
            SessionManagementConfigurer.this.expiredSessionStrategy = expiredSessionStrategy;
            return this;
        }

        public ConcurrencyControlConfigurer maxSessionsPreventsLogin(boolean maxSessionsPreventsLogin) {
            SessionManagementConfigurer.this.maxSessionsPreventsLogin = maxSessionsPreventsLogin;
            return this;
        }

        public ConcurrencyControlConfigurer sessionRegistry(SessionRegistry sessionRegistry) {
            SessionManagementConfigurer.this.sessionRegistry = sessionRegistry;
            return this;
        }

    }

}

// 并发会发控制认证策略
public class ConcurrentSessionControlAuthenticationStrategy {
    // 会话的注册表,保存会话信息的地方
    private final SessionRegistry sessionRegistry;
    // 达到最大的会话个数是否阻止登录
    private boolean exceptionIfMaximumExceeded = false;
    // 支持的最大并发数
    private int maximumSessions = 1;

    public ConcurrentSessionControlAuthenticationStrategy(SessionRegistry sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
    }

    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        // 支持的最大并发数
        int allowedSessions = this.getMaximumSessionsForThisUser(authentication);
        // 我们允许无限登录
        if (allowedSessions == -1) {
            return;
        }
        // 获取同一个身份信息的所有会话信息
        List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
        int sessionCount = sessions.size();
        // 如果实际会话小于允许的会话,就不处理
        if (sessionCount < allowedSessions) {
            // 目前没有太多的登录会话运行
            return;
        }
        // 如果相等,表示达到了最大的数量
        if (sessionCount == allowedSessions) {
            // 获取当前会话的session,不存在不创建直接返回空
            HttpSession session = request.getSession(false);
            if (session != null) {
                // 只允许已经存在的会话通过
                for (SessionInformation si : sessions) {
                    if (si.getSessionId().equals(session.getId())) {
                        return;
                    }
                }
            }
        }
        // 如果到这里,表示会话达到了最大值,并且是新会话
        // 处理超过会话数量的方案
        this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
    }

    protected int getMaximumSessionsForThisUser(Authentication authentication) {
        return this.maximumSessions;
    }


    // 处理超过会话数量的方案
    protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {
        // 如果需要阻止登录,或者不存在任何会话信息,则抛出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"));
        }
        // 如果到达了最大的会话数,并没有阻止登录,则后面登录的会挤出最近最少使用的会话,并标记为无效
        sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
        int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
        List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
        // 让这些会话信息标记过期,在过滤器中会使用
        for (SessionInformation session : sessionsToBeExpired) {
            session.expireNow();
        }
    }
}

// 将Session信息注册到Session注册表中
public class RegisterSessionAuthenticationStrategy {
    private final SessionRegistry sessionRegistry;

    public RegisterSessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
    }

    @Override
    public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());
    }
}

public class SessionManagementFilter {
    // SecurityContextHolder策略
    private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
    // SecurityContext的持久化策略
    private final SecurityContextRepository securityContextRepository;
    // Session认证的策略接口
    private SessionAuthenticationStrategy sessionAuthenticationStrategy;
    // AuthenticationTrustResolver接口提供身份类型的判断,会在ExceptionTranslationFilter处理该接口
    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
    // Session失效(时间过期)的策略接口
    private InvalidSessionStrategy invalidSessionStrategy = null;
    // 认证失败的处理器
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

    public SessionManagementFilter(SecurityContextRepository securityContextRepository) {
        // 默认的Session认证的策略接口为基于Session的固定会话的保护策略
        this(securityContextRepository, new SessionFixationProtectionStrategy());
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    // 开始过滤
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 如果存在该标识,表示已经经过了当前过滤器
        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }
        // 设置标记
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        // 如果securityContextRepository持久化对象中不存在当前请求的认证信息
        if (!this.securityContextRepository.containsContext(request)) {
            // 获取存在securityContextHolder策略中的认证信息
            Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
            // 如果存在认证信息,并且不是匿名的认证信息,表示用户已经认证
            if (authentication != null && !this.trustResolver.isAnonymous(authentication)) {
                // 在当前请求期间已对用户进行了身份验证,调用Session认证策略的onAuthentication方法
                // 通知这些接口,说当前用户已经认证了,在根据认证信息做自定以的操作
                try {
                    // 在会话策略可以拒绝身份验证
                    // 被拒绝之后会抛出异常
                    this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
                } catch (SessionAuthenticationException ex) {
                    // 清空securityContext上下文信息
                    this.securityContextHolderStrategy.clearContext();
                    // 回调认证失败处理器的方法
                    this.failureHandler.onAuthenticationFailure(request, response, ex);
                    return;
                }
                // 保存securityContext上下文
                // 可能在当前请求之前发生的可重入请求,所以上面设置了重入标记(FILTER_APPLIED)
                this.securityContextRepository.saveContext(this.securityContextHolderStrategy.getContext(), request, response);
            } else {
                // 当前请求没有securityContext或没有经过身份验证
                // 校验当前会话是否超时
                // 如果会话失效了,回调失效策略接口的onInvalidSessionDetected方法
                if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
                    if (this.invalidSessionStrategy != null) {
                        this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
                        return;
                    }
                }
            }
        }
        // 放行
        chain.doFilter(request, response);
    }

}

// 控制并发会话的过滤器
public class ConcurrentSessionFilter {
    // SecurityContextHolder策略
    private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
    // 会话注册表
    private final SessionRegistry sessionRegistry;
    // 会话过期跳转的url
    private String expiredUrl;
    // 重定向的策略
    private RedirectStrategy redirectStrategy;
    // 注销的处理器,默认存在处理清空SecurityContext的处理器
    private LogoutHandler handlers = new CompositeLogoutHandler(new SecurityContextLogoutHandler());
    // session被挤的信息过期策略处理
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        this.doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    // 开始过滤
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取Session
        HttpSession session = request.getSession(false);
        // 如果存在该会话
        if (session != null) {
            // 从session注册表中获取session信息
            SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
            // 如果存在会话信息
            if (info != null) {
                // 如果会话信息已经被标记了过期,表示被挤了
                if (info.isExpired()) {
                    // 注销操作,调用LogoutHandler接口的处理方法
                    this.doLogout(request, response);
                    // 再调用Session信息失效(被挤)的接口回调
                    this.sessionInformationExpiredStrategy.onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
                    return;
                }
                // 未过期-更新最后一次请求日期/时间
                this.sessionRegistry.refreshLastRequest(info.getSessionId());
            }
        }
        // 放行
        chain.doFilter(request, response);
    }

    // 注销操作,调用LogoutHandler接口的处理方法
    private void doLogout(HttpServletRequest request, HttpServletResponse response) {
        // 获取认证信息
        Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication();
        // 使用注销处理器对认证信息进行处理,默认存在处理清空SecurityContext的处理器
        this.handlers.logout(request, response, auth);
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值