持久化认证
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login
用户提交其用户名和密码。
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,该 cookie 用于在会话的剩余时间内认证用户。
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
SecurityContextRepository
在 Spring Security 中,用户与未来请求的关联是通过SecurityContextRepository进行的。SecurityContextRepository的默认实现是DelegatingSecurityContextRepository,它委托给以下对象:
HttpSessionSecurityContextRepository
HttpSessionSecurityContextRepository将SecurityContext关联到HttpSession。如果用户希望以其他方式或完全不将用户与后续请求关联,则可以替换HttpSessionSecurityContextRepository为SecurityContextRepository的另一个实现。
NullSecurityContextRepository
如果不需要将SecurityContext关联到HttpSession(即使用 OAuth 认证时),则NullSecurityContextRepository是SecurityContextRepository的一个实现,它不执行任何操作。
RequestAttributeSecurityContextRepository
RequestAttributeSecurityContextRepository将SecurityContext保存为请求属性,以确保SecurityContext在可能清除SecurityContext的调度类型中可用于单个请求。
例如,假设客户端发出请求,被认证,然后发生错误。根据 Servlet 容器的实现,错误意味着任何已建立的SecurityContext都会被清除,然后进行错误调度。当进行错误调度时,没有建立SecurityContext。这意味着错误页面无法使用SecurityContext进行授权或显示当前用户,除非SecurityContext以某种方式持久化。
-
Java
-
XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<b:bean name="contextRepository"
class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
DelegatingSecurityContextRepository
DelegatingSecurityContextRepository将SecurityContext保存到多个SecurityContextRepository委托中,并允许以指定顺序从任何委托中检索。
最常用的配置如下例所示,它允许同时使用RequestAttributeSecurityContextRepository和HttpSessionSecurityContextRepository。
-
Java
-
Kotlin
-
XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
securityContext {
securityContextRepository = DelegatingSecurityContextRepository(
RequestAttributeSecurityContextRepository(),
HttpSessionSecurityContextRepository()
)
}
}
return http.build()
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<bean name="contextRepository"
class="org.springframework.security.web.context.DelegatingSecurityContextRepository">
<constructor-arg>
<bean class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
</constructor-arg>
</bean>
|
在 Spring Security 6 中,上面显示的示例是默认配置。 |
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter负责使用SecurityContextRepository在请求之间持久化SecurityContext。
在运行应用程序的其余部分之前,SecurityContextPersistenceFilter从SecurityContextRepository加载SecurityContext并将其设置到SecurityContextHolder上。
接下来,运行应用程序。
最后,如果SecurityContext已更改,我们将使用SecurityContextRepository保存SecurityContext。这意味着当使用SecurityContextPersistenceFilter时,只需设置SecurityContextHolder即可确保SecurityContext使用SecurityContextRepository持久化。
在某些情况下,响应在SecurityContextPersistenceFilter方法完成之前被提交并写入客户端。例如,如果重定向发送到客户端,则响应会立即写入客户端。这意味着在步骤 3 中无法建立HttpSession,因为会话 ID 无法包含在已写入的响应中。可能发生的另一种情况是,如果客户端成功认证,则响应在SecurityContextPersistenceFilter完成之前提交,并且客户端在SecurityContextPersistenceFilter完成之前发出第二个请求。则第二个请求中可能存在错误的认证。
为避免这些问题,SecurityContextPersistenceFilter包装了HttpServletRequest和HttpServletResponse,以检测SecurityContext是否已更改,如果更改,则在响应提交之前保存SecurityContext。
SecurityContextHolderFilter
SecurityContextHolderFilter负责使用SecurityContextRepository在请求之间加载SecurityContext。
在运行应用程序的其余部分之前,SecurityContextHolderFilter从SecurityContextRepository加载SecurityContext并将其设置到SecurityContextHolder上。
接下来,运行应用程序。
与SecurityContextPersistenceFilter不同,SecurityContextHolderFilter只加载SecurityContext,它不保存SecurityContext。这意味着当使用SecurityContextHolderFilter时,需要显式保存SecurityContext。
-
Java
-
Kotlin
-
XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
securityContext {
requireExplicitSave = true
}
}
return http.build()
}
<http security-context-explicit-save="true">
<!-- ... -->
</http>
在使用此配置时,任何使用SecurityContext设置SecurityContextHolder的代码也必须将SecurityContext保存到SecurityContextRepository中,如果它需要在请求之间持久化。
例如,以下代码
SecurityContextPersistenceFilter 设置 SecurityContextHolder-
Java
-
Kotlin
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)
应该替换为
SecurityContextHolderFilter 设置 SecurityContextHolder-
Java
-
Kotlin
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)