翻译版本【spring-security 6.2.1】persistence
用户第一次请求受保护的资源时,系统会提示他们输入凭据。提示输入凭据的最常见方法之一是将用户重定向到登录页面。未经身份验证的用户请求受保护的资源的HTTP交换可能如下所示:
例1。未经认证的用户请求受保护的资源:
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
用户提交他们的用户名和密码。
提交用户名和密码
POST /login HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e
认证完成后,将用户关联到一个新的会话id,防止会话固定攻击。
“认证用户”关联新会话
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax
后续请求包括会话cookie,用于在会话的剩余时间内对用户进行身份验证。
作为凭据提供的身份验证会话
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
在Spring Security中,用户与未来请求的关联是使用SecurityContextRepository实现的。SecurityContextRepository的默认实现是DelegatingSecurityContextRepository,它委托以下内容:
HttpSessionSecurityContextRepository将SecurityContext关联到HttpSession。如果用户希望以另一种方式将用户与后续请求关联起来,或者根本不关联,则可以用SecurityContextRepository的另一个实现替换HttpSessionSecurityContextRepository 。
如果不希望将SecurityContext与HttpSession相关联(即,当使用OAuth进行身份验证时),则NullSecurityContextRepository是SecurityContextRepository的一个实现,它什么也不做。
RequestAttributeSecurityContextRepository将SecurityContext保存为请求属性,对于跨调度类型的单个请求可能清除SecurityContext的发生,来确保SecurityContext可用。
例如,假设客户端发出请求并通过身份验证,然后发生错误。根据servlet容器实现的不同,该错误意味着已建立的任何SecurityContext都将被清除,然后进行错误调度。当进行错误调度时,没有建立SecurityContext。这意味着错误页面不能使用SecurityContext进行授权或显示当前用户,除非SecurityContext以某种方式被持久化。
使用RequestAttributeSecurityContextRepository
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
DelegatingSecurityContextRepository将SecurityContext保存到多个SecurityContextRepositorys委托,并允许按指定顺序从任何委托中检索。
下面的例子配置了最有用的安排,它允许同时使用RequestAttributeSecurityContextRepository和HttpSessionSecurityContextRepository。
配置DelegatingSecurityContextRepository
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
注意
在SpringSecurity6中,上面显示的示例是默认配置。
SecurityContextPersistenceFilter负责使用SecurityContextRepository在请求之间持久化SecurityContext。
在运行应用程序的其余部分之前,SecurityContextPersistenceFilter从SecurityContextRepository加载SecurityContext,并将其设置在SecurityContextHolder上。
接下来,运行应用程序。
最后,如果SecurityContext已经更改,我们将使用SecurityContextPersistenceRepository保存SecurityContext。这意味着,当使用SecurityContextPersistenceFilter时,只需设置SecurityContextHolder就可以确保使用SecurityContextRepository持久化SecurityContext。
在某些情况下,响应在SecurityContextPersistenceFilter方法完成之前被提交并写入到客户端。例如,如果将重定向发送到客户端,则立即将响应写回客户端。这意味着在步骤3中不可能建立HttpSession,因为会话id不能包含在已经写好的响应中。另一种可能发生的情况是,如果客户端身份验证成功,则在SecurityContextPersistenceFilter完成之前提交响应,并且客户端在SecurityContextPersistenceFilter完成之前发出第二个请求,第二个请求中可能存在错误的身份验证。
为了避免这些问题,SecurityContextPersistenceFilter包装HttpServlet请求和HttpServlet响应,以检测SecurityContext是否已更改,如果已更改,则在提交响应之前保存SecurityContext。
SecurityContextHolderFilter负责使用SecurityContextRepository在请求之间加载SecurityContext。
在运行应用程序的其余部分之前,SecurityContextHolderFilter从securitycontexrepository加载SecurityContext,并将其设置在SecurityContextHolder上。
接下来,运行应用程序。
与SecurityContextPersistenceFilter不同,SecurityContextHolderFilter只加载SecurityContext,而不保存SecurityContext。这意味着在使用SecurityContextHolderFilter时,需要显式保存SecurityContext。
SecurityContext的显式保存
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
在使用配置时,如果需要在请求之间持久化SecurityContext,那么使用SecurityContext设置SecurityContextHolder的任何代码也要将SecurityContext保存到SecurityContextRepository中,这一点很重要。
例如,以下代码:
使用SecurityContextPersistenceFilter设置SecurityContextHolder
SecurityContextHolder.setContext(securityContext);
应该替换为
使用SecurityContextHolderFilter设置SecurityContextHolder
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);