/**
* 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);
}
}