21. Session Management

HTTP 会话相关的功能由过滤器委托的SessionManagementFilterSessionAuthenticationStrategy接口组合处理。典型的用法包括防止会话固定保护攻击,检测会话超时以及限制已认证用户可以同时打开多少个会话。

21.1 SessionManagementFilter

SessionManagementFilter对照SecurityContextHolder的当前内容检查SecurityContextRepository的内容,以确定用户是否在当前请求期间已通过典型的非交互式身份验证机制(例如,预身份验证或“记住我的名字” [17])进行了身份验证。如果存储库包含安全上下文,则过滤器不执行任何操作。如果不是,并且线程本地SecurityContext包含(非匿名)Authentication对象,则过滤器将假定它们已由堆栈中的先前过滤器进行了身份验证。然后它将调用已配置的SessionAuthenticationStrategy

如果用户当前未通过身份验证,则过滤器将检查是否已请求了无效的会话 ID(例如,由于超时),并且将调用配置的InvalidSessionStrategy(如果已设置)。最常见的行为只是重定向到固定 URL,并将其封装在标准实现SimpleRedirectInvalidSessionStrategy中。通过名称空间如前所述配置无效的会话 URL 时,也会使用后者。

21.2 SessionAuthenticationStrategy

SessionManagementFilterAbstractAuthenticationProcessingFilter都使用SessionAuthenticationStrategy,因此,例如,如果您使用自定义的表单登录类,则需要将其注入这两个类中。在这种情况下,将名称空间和定制 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" />

请注意,如果您在实现HttpSessionBindingListener的会话中存储 Bean(包括 Spring 会话范围的 Bean),则使用默认值SessionFixationProtectionStrategy可能会引起问题。有关更多信息,请参见 Javadoc。

21.3 并发控制

Spring Security 可以防止主体同时向同一应用程序进行身份验证超过指定次数。许多 ISV 都利用此功能来实施许可,而网络 Management 员喜欢此功能,因为它有助于防止人们共享登录名。例如,您可以阻止用户“蝙蝠侠”从两个不同的会话登录到 Web 应用程序。您可以使他们的前一次登录到期,也可以在他们尝试再次登录时报告错误,从而阻止第二次登录。请注意,如果您使用第二种方法,则尚未明确注销的用户(例如,刚刚关闭浏览器的用户)将无法再次登录,直到他们的原始会话期满为止。

名称空间支持并发控制,因此,请查阅前面的名称空间一章以获取最简单的配置。有时您需要自定义内容。

该实现使用名为ConcurrentSessionControlAuthenticationStrategy的专用版本SessionAuthenticationStrategy

Note

以前,并发身份验证检查是由ProviderManager进行的,可以通过ConcurrentSessionController进行注入。后者将检查用户是否试图超过允许的会话数。但是,这种方法要求预先创建 HTTP 会话,这是不希望的。在 Spring Security 3 中,用户首先通过AuthenticationManager进行身份验证,一旦成功通过身份验证,就会创建一个会话,并检查是否允许他们打开另一个会话。

要使用并发会话支持,您需要将以下内容添加到web.xml

<listener>
	<listener-class>
	org.springframework.security.web.session.HttpSessionEventPublisher
	</listener-class>
</listener>

另外,您需要将ConcurrentSessionFilter添加到FilterChainProxyConcurrentSessionFilter要求两个属性sessionRegistry(通常指向SessionRegistryImpl的实例)和expiredUrl(指向在会话过期时要显示的页面)。使用名称空间创建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="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/session-expired.htm" />
</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时,每次HttpSession开始或终止时,ApplicationEvent都会发布到 Spring ApplicationContext。这很关键,因为它允许会话结束时通知SessionRegistryImpl。没有它,用户将无法再次登录,一旦他们超出了会话限制,即使他们退出了另一个会话或超时。

21.3.1 查询 SessionRegistry 中当前经过身份验证的用户及其会话

通过名称空间或使用普通 bean 设置并发控制具有有益的副作用,即为您提供了对SessionRegistry的引用,您可以在应用程序中直接使用该SessionRegistry,因此即使您不想限制用户可能拥有的会话,无论如何都值得构建基础架构。您可以将maximumSession属性设置为-1 以允许无限制的会话。如果使用的是命名空间,则可以使用session-registry-alias属性为内部创建的SessionRegistry设置别名,并提供一个可以注入到您自己的 bean 中的引用。

getAllPrincipals()方法向您提供当前已认证用户的列表。您可以通过调用getAllSessions(Object principal, boolean includeExpiredSessions)方法列出用户的会话,该方法返回SessionInformation对象的列表。您还可以通过在SessionInformation实例上调用expireNow()来终止用户的会话。当用户返回到应用程序时,将阻止他们 continue 操作。例如,您可能会发现这些方法在 Management 应用程序中很有用。看一下 Javadoc 以获得更多信息。


[17]通过在身份验证后执行重定向的机制(例如,表单登录)进行身份验证将不会被SessionManagementFilter检测到,因为在身份验证请求期间不会调用过滤器。在这种情况下,必须单独处理会话 Management 功能。