对于最新的稳定版本,请使用 Spring Security 6.3.3! |
对于最新的稳定版本,请使用 Spring Security 6.3.3! |
一旦您有一个正在对请求进行身份验证的应用程序,重要的是要考虑如何在将来的请求中保留和恢复生成的身份验证。
默认情况下,这是自动完成的,因此不需要额外的代码,但了解 中的含义很重要。requireExplicitSave
HttpSecurity
如果你愿意,你可以阅读更多关于 requireExplicitSave 的作用或它为什么重要。否则,在大多数情况下,您已完成此部分。
但在您离开之前,请考虑以下用例是否适合您的应用程序:
-
我想限制用户可以同时登录的次数
-
我想自己直接存储身份验证,而不是 Spring Security 为我执行此操作
-
我正在手动存储身份验证,并且想要删除它
-
我正在使用
SessionManagementFilter
,我需要有关摆脱它的指导 -
我想将身份验证存储在会话以外的其他位置
-
我正在使用无状态身份验证,但我仍希望将其存储在会话中
-
我正在使用,但应用程序仍在创建会话。
SessionCreationPolicy.NEVER
了解会话管理的组件
Session Management 支持由几个组件组成,这些组件协同工作以提供功能。
这些组件是SecurityContextHolderFilter
、SecurityContextPersistenceFilter
和SessionManagementFilter
。
在 Spring Security 6 中,默认情况下不设置 and。
除此之外,任何应用程序都应该只具有 one 或 set,而不能同时具有两者。 |
这SessionManagementFilter
该 根据 的当前内容检查 的内容,以确定用户是否在当前请求期间已经过身份验证,通常是通过非交互式身份验证机制,例如 pre-authentication 或 remember-meSessionManagementFilter
SecurityContextRepository
SecurityContextHolder
[1].
如果存储库包含安全上下文,则筛选器不执行任何操作。
如果不是,并且 thread-local 包含一个 (非匿名) 对象,则 filter 会假定它们已经通过堆栈中前一个 filter 的身份验证。
然后,它将调用配置的 .SecurityContext
Authentication
SessionAuthenticationStrategy
如果用户当前未进行身份验证,则过滤器将检查是否请求了无效的会话 ID(例如,由于超时),并将调用配置的 ,如果已设置。
最常见的行为只是重定向到固定的 URL,这封装在 标准实施 中。
如前所述,当通过命名空间配置无效的会话 URL 时,也会使用后者。InvalidSessionStrategy
SimpleRedirectInvalidSessionStrategy
远离SessionManagementFilter
在 Spring Security 5 中,默认配置依赖于检测用户是否刚刚进行身份验证并调用SessionAuthenticationStrategy
。
这样做的问题在于,这意味着在典型设置中,必须为每个请求读取 the。SessionManagementFilter
HttpSession
在 Spring Security 6 中,默认情况下,身份验证机制本身必须调用 .
这意味着无需检测何时完成,因此不需要为每个请求读取 。SessionAuthenticationStrategy
Authentication
HttpSession
搬离时要考虑的事项SessionManagementFilter
在 Spring Security 6 中,默认情况下不使用,因此,DSL 中的某些方法不会产生任何效果。SessionManagementFilter
sessionManagement
方法 | 更换 |
---|---|
|
在身份验证机制中配置 |
|
在身份验证机制中配置 |
|
如果尝试使用这些方法中的任何一种,将引发异常。
在 Spring Security 6 中,默认情况下不设置 and。
除此之外,任何应用程序都应该只具有 one 或 set,而不能同时具有两者。 |
方法 | 更换 |
---|---|
|
在身份验证机制中配置 |
|
在身份验证机制中配置 |
|
自定义身份验证的存储位置
默认情况下,Spring Security 将安全上下文存储在 HTTP 会话中。但是,以下是您可能希望自定义该自定义的几个原因:
-
您可能希望在实例上调用单个 setter
HttpSessionSecurityContextRepository
-
您可能希望将安全上下文存储在缓存或数据库中,以启用水平扩展
首先,您需要创建 的实现或使用现有实现(如 ),然后您可以在 中设置它。SecurityContextRepository
HttpSessionSecurityContextRepository
HttpSecurity
SecurityContextRepository
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
SecurityContextRepository repo = new MyCustomSecurityContextRepository();
http
// ...
.securityContext((context) -> context
.securityContextRepository(repo)
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val repo = MyCustomSecurityContextRepository()
http {
// ...
securityContext {
securityContextRepository = repo
}
}
return http.build()
}
<http security-context-repository-ref="repo">
<!-- ... -->
</http>
<bean name="repo" class="com.example.MyCustomSecurityContextRepository" />
上述配置设置 on 和 participating 身份验证过滤器,如 .
要在无状态过滤器中也设置它,请参阅如何自定义 |
如果您使用的是自定义身份验证机制,则可能需要自己存储 Authentication
。
手动存储Authentication
例如,在某些情况下,您可能正在手动对用户进行身份验证,而不是依赖 Spring Security 过滤器。
你可以使用自定义过滤器或 Spring MVC 控制器端点来做到这一点。
如果要保存请求之间的身份验证,例如,在 中,您必须这样做:HttpSession
-
Java
private SecurityContextRepository securityContextRepository =
new HttpSessionSecurityContextRepository(); (1)
@PostMapping("/login")
public void login(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) { (2)
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
loginRequest.getUsername(), loginRequest.getPassword()); (3)
Authentication authentication = authenticationManager.authenticate(token); (4)
SecurityContext context = securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authentication); (5)
securityContextHolderStrategy.setContext(context);
securityContextRepository.saveContext(context, request, response); (6)
}
class LoginRequest {
private String username;
private String password;
// getters and setters
}
1 | 将 添加到控制器SecurityContextRepository |
2 | 注入 和 为了能够保存HttpServletRequest HttpServletResponse SecurityContext |
3 | 使用提供的凭证创建未经身份验证的UsernamePasswordAuthenticationToken |
4 | 用于对用户进行身份验证的调用AuthenticationManager#authenticate |
5 | 创建一个并在其中设置SecurityContext Authentication |
6 | 将 保存在SecurityContext SecurityContextRepository |
就是这样。
如果您不确定上述示例中的内容,可以在使用 SecurityContextStrategy
部分中阅读有关它的更多信息。securityContextHolderStrategy
正确清除身份验证
如果您使用的是 Spring Security 的 Logout Support,那么它会为您处理很多事情,包括清除和保存上下文。 但是,假设您需要手动将用户从您的应用程序中注销。在这种情况下,您需要确保正确清除和保存上下文。
为无状态身份验证配置持久性
有时不需要创建和维护 例如,在请求之间持久化身份验证。
某些身份验证机制(如 HTTP Basic)是无状态的,因此,在每个请求上都会重新验证用户。HttpSession
如果您不想创建会话,可以使用 ,如下所示:SessionCreationPolicy.STATELESS
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
sessionManagement {
sessionCreationPolicy = SessionCreationPolicy.STATELESS
}
}
return http.build()
}
<http create-session="stateless">
<!-- ... -->
</http>
上面的配置将SecurityContextRepository
配置为使用a,并且还阻止了请求被保存在会话中。NullSecurityContextRepository
如果您使用的是 ,您可能会注意到应用程序仍在创建一个 .
在大多数情况下,发生这种情况是因为请求保存在会话中,以便经过身份验证的资源在身份验证成功后重新请求。
为避免这种情况,请参考 如何防止被保存请求 部分。SessionCreationPolicy.NEVER
HttpSession
在会话中存储无状态身份验证
如果出于某种原因,您使用的是无状态身份验证机制,但仍希望将身份验证存储在会话中,则可以使用 代替 .HttpSessionSecurityContextRepository
NullSecurityContextRepository
对于 HTTP Basic,您可以添加一个 ObjectPostProcessor,该 ObjectPostProcessor
将SecurityContextRepository
BasicAuthenticationFilter
HttpSession
-
Java
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
// ...
.httpBasic((basic) -> basic
.addObjectPostProcessor(new ObjectPostProcessor<BasicAuthenticationFilter>() {
@Override
public <O extends BasicAuthenticationFilter> O postProcess(O filter) {
filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
return filter;
}
})
);
return http.build();
}
上述内容也适用于其他身份验证机制,例如 Bearer Token Authentication。
上述配置设置 on 和 participating 身份验证过滤器,如 .
要在无状态过滤器中也设置它,请参阅如何自定义 |
1 | 将 添加到控制器SecurityContextRepository |
2 | 注入 和 为了能够保存HttpServletRequest HttpServletResponse SecurityContext |
3 | 使用提供的凭证创建未经身份验证的UsernamePasswordAuthenticationToken |
4 | 用于对用户进行身份验证的调用AuthenticationManager#authenticate |
5 | 创建一个并在其中设置SecurityContext Authentication |
6 | 将 保存在SecurityContext SecurityContextRepository |
了解需要显式保存
在 Spring Security 5 中,默认行为是使用SecurityContextPersistenceFilter
将SecurityContext
自动保存到SecurityContextRepository
中。
必须在提交之前和之前完成保存。
遗憾的是,如果在请求完成之前(即在提交 之前)完成 的自动持久化,则可能会让用户感到惊讶。
跟踪状态以确定是否需要保存也很复杂,有时会导致对 (即 ) 进行不必要的写入。HttpServletResponse
SecurityContextPersistenceFilter
SecurityContext
HttpServletResponse
SecurityContextRepository
HttpSession
由于这些原因,已弃用 ,取而代之的是 .
在 Spring Security 6 中,默认行为是 SecurityContextHolderFilter
将仅读取 from 并将其填充到 .
如果用户希望 在请求之间持久保存,现在必须显式保存 。
这消除了歧义并提高了性能,因为只需要在必要时写入 (即 )。SecurityContextPersistenceFilter
SecurityContextHolderFilter
SecurityContext
SecurityContextRepository
SecurityContextHolder
SecurityContext
SecurityContextRepository
SecurityContext
SecurityContextRepository
HttpSession
运作方式
总之,当 is 时,Spring Security 会设置 SecurityContextHolderFilter
而不是 SecurityContextPersistenceFilter
requireExplicitSave
true
配置并发会话控制
如果您希望对单个用户登录应用程序的能力施加限制,Spring Security 通过以下简单的添加来支持开箱即用。 首先,您需要将以下侦听器添加到您的配置中,以使 Spring Security 了解会话生命周期事件:
-
Java
-
Kotlin
-
web.xml
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
open fun httpSessionEventPublisher(): HttpSessionEventPublisher {
return HttpSessionEventPublisher()
}
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
然后将以下行添加到您的安全配置中:
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.maximumSessions(1)
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
sessionConcurrency {
maximumSessions = 1
}
}
}
return http.build()
}
<http>
...
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
这将防止用户多次登录 - 第二次登录将导致第一次登录无效。
使用 Spring Boot,您可以通过以下方式测试上述配置场景:
-
Java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MaximumSessionsTests {
@Autowired
private MockMvc mvc;
@Test
void loginOnSecondLoginThenFirstSessionTerminated() throws Exception {
MvcResult mvcResult = this.mvc.perform(formLogin())
.andExpect(authenticated())
.andReturn();
MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();
this.mvc.perform(get("/").session(firstLoginSession))
.andExpect(authenticated());
this.mvc.perform(formLogin()).andExpect(authenticated());
// first session is terminated by second login
this.mvc.perform(get("/").session(firstLoginSession))
.andExpect(unauthenticated());
}
}
您可以使用 Maximum Sessions 示例进行尝试。
您通常希望阻止第二次登录,在这种情况下,您可以使用:
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
sessionConcurrency {
maximumSessions = 1
maxSessionsPreventsLogin = true
}
}
}
return http.build()
}
<http>
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>
然后,第二次登录将被拒绝。
“rejected” 是指如果使用基于表单的登录,则用户将被发送到 。
如果第二次身份验证通过另一个非交互式机制进行,例如“remember-me”,则会向客户端发送“未授权”(401) 错误。
如果您想使用错误页面,则可以将属性添加到元素中。authentication-failure-url
session-authentication-error-url
session-management
使用 Spring Boot,您可以通过以下方式测试上述配置:
-
Java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MaximumSessionsPreventLoginTests {
@Autowired
private MockMvc mvc;
@Test
void loginOnSecondLoginThenPreventLogin() throws Exception {
MvcResult mvcResult = this.mvc.perform(formLogin())
.andExpect(authenticated())
.andReturn();
MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();
this.mvc.perform(get("/").session(firstLoginSession))
.andExpect(authenticated());
// second login is prevented
this.mvc.perform(formLogin()).andExpect(unauthenticated());
// first session is still valid
this.mvc.perform(get("/").session(firstLoginSession))
.andExpect(authenticated());
}
}
如果您正在为基于表单的登录使用自定义身份验证过滤器,则必须显式配置并发会话控制支持。 您可以使用 Maximum Sessions Prevent Login 示例进行尝试。
检测超时
会话会自行过期,无需执行任何操作来确保删除安全上下文。
也就是说,Spring Security 可以检测会话何时过期并采取您指示的特定操作。
例如,您可能希望在用户使用已过期的会话发出请求时重定向到特定终端节点。
这是通过 in 实现的:invalidSessionUrl
HttpSecurity
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.invalidSessionUrl("/invalidSession")
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
invalidSessionUrl = "/invalidSession"
}
}
return http.build()
}
<http>
...
<session-management invalid-session-url="/invalidSession" />
</http>
请注意,如果使用此机制来检测会话超时,则当用户注销并重新登录而不关闭浏览器时,它可能会错误地报告错误。 这是因为当您使会话失效时,会话 Cookie 不会被清除,即使用户已注销,也会重新提交会话 Cookie。 如果是这种情况,您可能希望配置 logout 以清除会话 Cookie。
自定义无效会话策略
这是使用 SimpleRedirectInvalidSessionStrategy
实现进行设置的便捷方法。
如果要自定义行为,可以实现 InvalidSessionStrategy
接口,并使用以下方法对其进行配置:invalidSessionUrl
InvalidSessionStrategy
invalidSessionStrategy
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.invalidSessionStrategy(new MyCustomInvalidSessionStrategy())
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
invalidSessionStrategy = MyCustomInvalidSessionStrategy()
}
}
return http.build()
}
<http>
...
<session-management invalid-session-strategy-ref="myCustomInvalidSessionStrategy" />
<bean name="myCustomInvalidSessionStrategy" class="com.example.MyCustomInvalidSessionStrategy" />
</http>
注销时清除会话 Cookie
您可以在注销时显式删除 JSESSIONID Cookie,例如,通过在注销处理程序中使用 Clear-Site-Data
标头:
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.logout((logout) -> logout
.addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(COOKIES)))
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
logout {
addLogoutHandler(HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(COOKIES)))
}
}
return http.build()
}
<http>
<logout success-handler-ref="clearSiteDataHandler" />
<b:bean id="clearSiteDataHandler" class="org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler">
<b:constructor-arg>
<b:bean class="org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter">
<b:constructor-arg>
<b:list>
<b:value>COOKIES</b:value>
</b:list>
</b:constructor-arg>
</b:bean>
</b:constructor-arg>
</b:bean>
</http>
这样做的优点是与容器无关,并且适用于任何支持 header 的容器。Clear-Site-Data
作为替代方法,您还可以在 logout 处理程序中使用以下语法:
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.logout(logout -> logout
.deleteCookies("JSESSIONID")
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
logout {
deleteCookies("JSESSIONID")
}
}
return http.build()
}
<http>
<logout delete-cookies="JSESSIONID" />
</http>
遗憾的是,这不能保证适用于每个 servlet 容器,因此您需要在您的环境中对其进行测试。
如果您在代理后面运行应用程序,则还可以通过配置代理服务器来删除会话 cookie。
例如,通过使用 Apache HTTPD 的 ,以下指令通过在对 logout 请求的响应中使 cookie 过期来删除 cookie(假设应用程序部署在 path下): |
<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>
如果您在代理后面运行应用程序,则还可以通过配置代理服务器来删除会话 cookie。
例如,通过使用 Apache HTTPD 的 ,以下指令通过在对 logout 请求的响应中使 cookie 过期来删除 cookie(假设应用程序部署在 path下): |
了解 Session Fixation 攻击防护
会话固定攻击是一种潜在风险,恶意攻击者可能通过访问站点来创建会话,然后说服其他用户使用相同的会话登录(例如,通过向他们发送包含会话标识符作为参数的链接)。 Spring Security 通过在用户登录时创建新会话或更改会话 ID 来自动防止这种情况。
配置 Session Fixation Protection
您可以通过在三个建议选项之间进行选择来控制 Session Fixation Protection 的策略:
-
changeSessionId
- 不要创建新会话。 请改用 Servlet 容器 () 提供的会话固定保护。 此选项仅在 Servlet 3.1 (Java EE 7) 和更新的容器中可用。 在较旧的容器中指定它将导致异常。 这是 Servlet 3.1 和更高版本中的默认设置。HttpServletRequest#changeSessionId()
-
newSession
- 创建一个新的 “clean” session,而不复制现有的 session 数据(与 Spring Security 相关的属性仍然会被复制)。 -
migrateSession
- 创建新会话并将所有现有会话属性复制到新会话。 这是 Servlet 3.0 或更早版本中的默认设置。
您可以通过执行以下操作来配置会话固定保护:
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement((session) -> session
.sessionFixation((sessionFixation) -> sessionFixation
.newSession()
)
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
sessionFixation {
newSession()
}
}
}
return http.build()
}
<http>
<session-management session-fixation-protection="newSession" />
</http>
当发生会话固定保护时,它会导致 在应用程序上下文中发布。
如果使用 ,则此保护还会导致通知任何 s,因此,如果代码侦听这两个事件,请谨慎使用。SessionFixationProtectionEvent
changeSessionId
jakarta.servlet.http.HttpSessionIdListener
您还可以将会话固定保护设置为以禁用它,但不建议这样做,因为它会使您的应用程序容易受到攻击。none
用SecurityContextHolderStrategy
请考虑以下代码块:
-
Java
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword());
Authentication authentication = this.authenticationManager.authenticate(token);
// ...
SecurityContext context = SecurityContextHolder.createEmptyContext(); (1)
context.setAuthentication(authentication); (2)
SecurityContextHolder.setContext(context); (3)
-
通过静态访问 来创建空实例。
SecurityContext
SecurityContextHolder
-
设置实例中的对象。
Authentication
SecurityContext
-
在 static .
SecurityContext
SecurityContextHolder
虽然上述代码运行良好,但它可能会产生一些不希望的效果:当组件通过 静态访问 时,当有多个应用程序上下文想要指定 .
这是因为每个 classloader 都有一个策略,而不是每个应用程序上下文一个策略。SecurityContext
SecurityContextHolder
SecurityContextHolderStrategy
SecurityContextHolder
为了解决这个问题,组件可以从应用程序上下文进行连接。
默认情况下,他们仍将从 中查找策略。SecurityContextHolderStrategy
SecurityContextHolder
这些更改主要是内部的,但它们为应用程序提供了自动装配的机会,而不是静态访问 。
为此,您应该将代码更改为以下内容:SecurityContextHolderStrategy
SecurityContext
-
Java
public class SomeClass {
private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
public void someMethod() {
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
loginRequest.getUsername(), loginRequest.getPassword());
Authentication authentication = this.authenticationManager.authenticate(token);
// ...
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); (1)
context.setAuthentication(authentication); (2)
this.securityContextHolderStrategy.setContext(context); (3)
}
}
-
使用配置的 .
SecurityContext
SecurityContextHolderStrategy
-
设置实例中的对象。
Authentication
SecurityContext
-
在 .
SecurityContext
SecurityContextHolderStrategy
强制创建 Eager Session
有时,急切地创建会话可能很有价值。
这可以通过使用 ForceEagerSessionCreationFilter
来完成,该过滤器可以使用以下方法进行配置:
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
);
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
sessionCreationPolicy = SessionCreationPolicy.ALWAYS
}
}
return http.build()
}
<http create-session="ALWAYS">
</http>
接下来要读什么
-
使用 Spring Session 的群集会话
SessionManagementFilter