SpringSecurity------Session Management(十二)
一、Force Eager Session Creation
有时,总是创建会话是很有价值的,我们可以通过配置ForceEagerSessionCreationFilter来完成:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
);
return http.build();
}
二、Detecting Timeouts
可以配置Spring Security来检测无效会话ID的提交,并将用户重定向到适当的URL,这是通过会话管理(session-management)实现的:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.invalidSessionUrl("/invalidSession.htm")
);
return http.build();
}
使用上面的配置来检测会话超时,如果用户在登出之后没有关闭浏览器的情况下重新登录,可能会报告一个错误。这是因为执行了登出,会话失效时存储在浏览器的Cookie不会被清除,而且会随着后续请求重新提交。可以使用显示方式在登出之后清除JSESSIONID Cookie(不一定生效,需要在实际环境测试):
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.logout(logout -> logout
.deleteCookies("JSESSIONID")
);
return http.build();
}
三、Concurrent Session Control
如果需要对单个用户登录应用程序的能力施加限制,首先,需要将以下侦听器添加到您的配置中,以保持Spring Security对Session生命周期事件的更新:
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
然后,添加以下配置,第二次登录将导致第一次登录的会话失效:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.maximumSessions(1)
);
return http.build();
}
如果想禁止第二次登录,可以配置如下:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
);
return http.build();
}
如果是基于表单登录,用户将被发送到authentication-failure-url;如果第二次登录是通过非交互机制(non-interactive mechanism)进行的,比如remember-me,则会向客户端发送一个401错误的响应。可以通过设置session-management的session-authentication-error-url属性来指定希望使用的错误处理地址。
四、Session Fixation Attack Protection
会话固定攻击是一种潜在的风险,恶意攻击者可能通过访问站点来创建会话,然后驱使用户使用相同的会话登录(例如,通过向他们发送一个将会话标识当作参数的链接)。Spring Security通过创建一个新的会话或在用户登录时更改会话ID来自动防止这种情况。 如果不需要这种保护,或者它与其他需求冲突,可以使用session-management上的session- fixed -protection属性控制行为,该属性有四个选项:
- none:不做处理,保留原始会话
- newSession:创建一个新的会话,不会复制现有会话的数据(Spring Security相关的数据会被复制)
- migrateSession:创建一个新会话,并将所有现有会话属性复制到新会话中,这是Servlet 3.0或更早的容器中的默认值
- changeSessionId:不创建新的会话,使用Servlet容器提供的会话固定保护(HttpServletRequest#changeSessionId())。该选项仅在Servlet 3.1 (Java EE 7)和更新的容器中可用。 在旧容器中指定它将导致异常。 这是Servlet 3.1和更新的容器中的默认值。
当发生会话固定保护时,应用程序会发布一个SessionFixationProtectionEvent事件。如果使用的是changeSessionId配置,事件也会通知到任何javax.servlet.http.httpessionidlisteners监听器,因此如果您的代码同时侦听这两个事件,请谨慎使用。
五、SessionManagementFilter
SessionManagementFilter通过对比SecurityContextRepository获取的内容和SecurityContextHolder中的内容来确定当前请求用户是否通过了身份验证,这种验证通常是非交互的身份验证机制(如:pre-authentication,remember-me)。如果存储库包含了一个SecurityContext,SessionManagementFilter不做任何处理。如果没有,并且线程本地包含一个SecurityContext非匿名(non-anonymous)的Authentication对象,SessionManagementFilter假定它们已经通过先前的过滤器进行了身份验证,然后调用配置好的SessionAuthenticationStrategy。
如果当前用户没有经过身份验证,SessionManagementFilter会检查是否请求包含了无效的session ID(比如会话过期),然后会调用InvalidSessionStrategy(如果有配置的话)。最常见的处理方式是重定向到一个固定的URL,SimpleRedirectInvalidSessionStrategy这个标准实现支持这种策略。
六、SessionAuthenticationStrategy
SessionAuthenticationStrategy由SessionManagementFilter和AbstractAuthenticationProcessingFilter使用,因此,如果使用定制的表单登录类, 需要将它注入到这两个类中:
<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
...
</beans:bean>
<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
注意,如果在实现HttpSessionBindingListener的会话中存储beans(包括 Spring session-scoped beans),使用默认的SessionFixationProtectionStrategy可能会导致问题。
七、Concurrency Control
Spring Security可以控制用户并发身份验证的次数。比如可以阻止一个用户在两个会话中同时登录应用程序,可以使之前的登录过期,也可以在第二次登录的时候报错以限制第二次登录。如果你使用限制第二次登录,那么尚未显式注销的用户直到其原始的会话过期之才能再次登录
需要在web.xml中配置HttpSessionEventPublisher才能使用:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
另外,需要将ConcurrentSessionFilter添加到FilterChainProxy中。 ConcurrentSessionFilter需要两个构造函数参数:sessionRegistry,它通常是SessionRegistryImpl的一个实例;以及sessionInformationExpiredStrategy,它定义了当会话过期时应用的策略。 使用xml的配置方式:
<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="redirectSessionInformationExpiredStrategy"
class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" />
</beans:bean>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" />
</beans:bean>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
将HttpSessionEventPublisher添加到web.xml,每当HttpSession开始或结束时,ApplicationEvent都会被发布到Spring ApplicationContext。 这是至关重要,因为它允许在会话结束时通知SessionRegistryImpl,如果没有它,用户将永远无法在超过会话登录限制时重新登录,即使注销了另一个会话或会话超时。
八、Querying the SessionRegistry for currently authenticated users and their sessions
匹配并发控制会引入一个SessionRegistry,可以在应用程序中直接使用这个bean,因此,即使不需要控制用户的会话数量,设置这些基础组件也是值得的,可以将maximumSession属性设置为-1,以允许无限的会话。如果使用xml配置,可以使用session-registry-alias属性为内部创建的SessionRegistry设置别名,从而提供一个可以注入到您自己的bean中的引用。
SessionRegistry.getAllPrincipals()方法可以获取到当前登录用户列表。SessionRegistry.getAllSessions(Object principal, boolean includeExpiredSessions)方法来列出用户的会话,该方法返回SessionInformation对象的列表。还可以通过在SessionInformation实例上调用expireNow()来终止用户的会话。
通过认证之后执行重定向的机制(比如form-login)不会被SessionManagementFilter检测到,在认证请求期间,过滤器不会被调用, 在这些情况下,必须单独处理会话管理功能。