对于最新的稳定版本,请使用 Spring Security 6.4.3! |
会话管理
强制创建 Eager 会话
有时,急切地创建会话可能很有价值。
这可以通过使用ForceEagerSessionCreationFilter
可使用以下方法进行配置:
-
Java
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
);
return http.build();
}
<http create-session="ALWAYS">
</http>
检测超时
您可以将 Spring Security 配置为检测无效会话 ID 的提交,并将用户重定向到相应的 URL。
这是通过session-management
元素:
-
Java
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.invalidSessionUrl("/invalidSession.htm")
);
return http.build();
}
<http>
...
<session-management invalid-session-url="/invalidSession.htm" />
</http>
请注意,如果使用此机制来检测会话超时,则当用户注销并重新登录而不关闭浏览器时,它可能会错误地报告错误。 这是因为当您使会话失效时,会话 Cookie 不会被清除,即使用户已注销,也会重新提交会话 Cookie。 您可以在注销时显式删除 JSESSIONID Cookie,例如,通过在注销处理程序中使用以下语法:
-
Java
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.logout(logout -> logout
.deleteCookies("JSESSIONID")
);
return http.build();
}
<http>
<logout delete-cookies="JSESSIONID" />
</http>
不幸的是,这不能保证每个 servlet 容器都有效,因此您需要在您的环境中对其进行测试
如果您在代理后面运行应用程序,则还可以通过配置代理服务器来删除会话 Cookie。
例如,使用 Apache HTTPD 的 mod_headers,以下指令将删除
|
并发会话控制
如果您希望对单个用户登录应用程序的能力施加限制,Spring Security 通过以下简单的添加来支持开箱即用。 首先,您需要将以下侦听器添加到您的配置中,以使 Spring Security 了解会话生命周期事件:
-
Java
-
XML
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
然后将以下行添加到您的应用程序上下文中:
-
Java
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.maximumSessions(1)
);
return http.build();
}
<http>
...
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
这将防止用户多次登录 - 第二次登录将导致第一次登录无效。 通常,您更愿意阻止第二次登录,在这种情况下,您可以使用
-
Java
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
);
return http.build();
}
<http>
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>
然后,第二次登录将被拒绝。
“rejected” 是指用户将被发送到authentication-failure-url
如果正在使用基于表单的登录。
如果第二次身份验证通过另一个非交互式机制进行,例如“remember-me”,则会向客户端发送“未授权”(401) 错误。
如果您想使用错误页面,则可以添加属性session-authentication-error-url
到session-management
元素。
如果您正在为基于表单的登录使用自定义身份验证过滤器,则必须显式配置并发会话控制支持。 更多详细信息可以在 Session Management 章节中找到。
会话固定攻击防护
会话固定攻击是一种潜在风险,恶意攻击者可能通过访问站点来创建会话,然后说服其他用户使用相同的会话登录(例如,通过向他们发送包含会话标识符作为参数的链接)。
Spring Security 通过在用户登录时创建新会话或更改会话 ID 来自动防止这种情况。
如果您不需要此保护,或者它与某些其他要求冲突,则可以使用session-fixation-protection
属性<session-management>
,它有四个选项
-
none
- 什么都不要做。 将保留原始会话。 -
newSession
- 创建一个新的 “clean” session,而不复制现有的 session 数据(与 Spring Security 相关的属性仍然会被复制)。 -
migrateSession
- 创建新会话并将所有现有会话属性复制到新会话。 这是 Servlet 3.0 或更早版本中的默认设置。 -
changeSessionId
- 不要创建新会话。 相反,请使用 Servlet 容器 (HttpServletRequest#changeSessionId()
). 此选项仅在 Servlet 3.1 (Java EE 7) 和更新的容器中可用。 在较旧的容器中指定它将导致异常。 这是 Servlet 3.1 和更高版本中的默认设置。
当发生会话固定保护时,它会导致SessionFixationProtectionEvent
在 Application 上下文中发布。
如果您使用changeSessionId
,则此保护还会导致任何javax.servlet.http.HttpSessionIdListener
,因此如果您的代码同时侦听这两个事件,请谨慎使用。
有关更多信息,请参阅 Session Management 章节。
会话管理过滤器
这SessionManagementFilter
检查SecurityContextRepository
针对SecurityContextHolder
确定用户是否在当前请求期间已经过身份验证,通常通过非交互式身份验证机制(如 pre-authentication 或 remember-me)进行身份验证[1].
如果存储库包含安全上下文,则筛选器不执行任何作。
如果没有,则线程本地的SecurityContext
包含一个 (非匿名)Authentication
对象,则筛选条件会假定它们已由堆栈中的前一个筛选条件进行身份验证。
然后,它将调用配置的SessionAuthenticationStrategy
.
如果用户当前未进行身份验证,则过滤器将检查是否请求了无效的会话 ID(例如,由于超时),并将调用配置的InvalidSessionStrategy
(如果已设置)。
最常见的行为只是重定向到一个固定的 URL,这被封装在标准实现中SimpleRedirectInvalidSessionStrategy
.
如前所述,当通过命名空间配置无效的会话 URL 时,也会使用后者。
SessionAuthenticationStrategy
SessionAuthenticationStrategy
被两者使用SessionManagementFilter
和AbstractAuthenticationProcessingFilter
,因此,如果你正在使用自定义的 form-login 类,例如,你需要将其注入到这两个类中。
在这种情况下,将名称空间和自定义 bean 组合在一起的典型配置可能如下所示:
<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" />
请注意,使用默认的SessionFixationProtectionStrategy
如果你在实现HttpSessionBindingListener
,包括 Spring 会话范围的 bean。
有关更多信息,请参阅此类的 Javadoc。
并发控制
Spring Security 能够防止主体同时对同一应用程序进行身份验证超过指定次数。 许多 ISV 利用此功能来强制实施许可,而网络管理员喜欢此功能,因为它有助于防止人们共享登录名。 例如,您可以阻止用户 “Batman” 从两个不同的会话登录到 Web 应用程序。 您可以使他们之前的登录过期,也可以在他们尝试再次登录时报告错误,从而阻止第二次登录。 请注意,如果您使用的是第二种方法,则尚未明确注销的用户(例如,刚刚关闭了浏览器的用户)将无法再次登录,直到其原始会话过期。
命名空间支持并发控制,因此请查看前面的命名空间章节以获取最简单的配置。 不过,有时您需要自定义内容。
该实现使用SessionAuthenticationStrategy
叫ConcurrentSessionControlAuthenticationStrategy
.
以前,并发身份验证检查由 |
要使用并发会话支持,您需要将以下内容添加到web.xml
:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
此外,您还需要添加ConcurrentSessionFilter
发送到您的FilterChainProxy
.
这ConcurrentSessionFilter
需要两个构造函数参数sessionRegistry
,它通常指向SessionRegistryImpl
和sessionInformationExpiredStrategy
,它定义会话过期时要应用的策略。
使用命名空间创建FilterChainProxy
和其他默认 bean 可能看起来像这样:
<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" />
将侦听器添加到web.xml
导致ApplicationEvent
发布到 SpringApplicationContext
每次HttpSession
开始或结束。
这很关键,因为它允许SessionRegistryImpl
在会话结束时收到通知。
如果没有它,一旦用户超过其会话限额,他们将永远无法再次登录,即使他们退出另一个会话或超时也是如此。
在 SessionRegistry 中查询当前已验证的用户及其会话
通过名称空间或使用普通 bean 设置 concurrency-control 具有有用的副作用,即为您提供对SessionRegistry
您可以直接在应用程序中使用,因此即使您不想限制用户可能拥有的会话数量,也可能值得设置基础设施。
您可以设置maximumSession
属性设置为 -1 以允许无限会话。
如果您使用的是命名空间,则可以为内部创建的SessionRegistry
使用session-registry-alias
属性,提供一个引用,您可以将其注入到自己的 bean 中。
这getAllPrincipals()
method 为您提供当前经过身份验证的用户的列表。
您可以通过调用getAllSessions(Object principal, boolean includeExpiredSessions)
方法,该方法返回SessionInformation
对象。
您还可以通过调用expireNow()
在SessionInformation
实例。
当用户返回应用程序时,将阻止他们继续。
例如,您可能会发现这些方法在管理应用程序中很有用。
有关更多信息,请查看 Javadoc。
SessionManagementFilter
,因为在身份验证请求期间不会调用过滤器。在这些情况下,必须单独处理会话管理功能。