持久化认证

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

当用户第一次请求受保护的资源时,他们会被 提示提供凭证。最常见的提示凭证的方式之一是将用户重定向到一个 登录页面。一个未经认证的用户请求受保护资源的HTTP交换摘要可能是这样的。

Example 1. Unauthenticated User Requests Protected Resource
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login

用户提交他们的用户名和密码。

Username and Password Submitted
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,以防止 session fixation攻击

Authenticated User is Associated to New Session
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax

随后的请求包括Session cookie,用于在接下来的会话中验证用户的身份。

Authenticated Session Provided as Credentials
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8

SecurityContextRepository

在Spring Security中,用户与未来请求的关联是通过 SecurityContextRepository 实现的。SecurityContextRepository 的默认实现是 DelegatingSecurityContextRepository,它委托给:

HttpSessionSecurityContextRepository

HttpSessionSecurityContextRepositorySecurityContextHttpSession 相关联。如果用户希望以另一种方式或根本不与随后的请求相关联,他们可以用 SecurityContextRepository 的另一种实现来替换 HttpSessionSecurityContextRepository

NullSecurityContextRepository

如果不希望将 SecurityContextHttpSession 相关联(例如,当用OAuth认证时), NullSecurityContextRepositorySecurityContextRepository 的一个实现,不做任何事情。

RequestAttributeSecurityContextRepository

RequestAttributeSecurityContextRepositorySecurityContext 保存为请求属性(request attribute),以确保 SecurityContext 可用于跨调度(dispatch)类型发生的单个请求,这些调度类型可能会清除 SecurityContext

例如,假设一个客户发出请求,经过验证,然后发生了错误。根据servlet容器的实现,该错误意味着任何已建立的 SecurityContext 被清除,然后进行Error调度(dispatch)。当进行Error调度时,没有建立任何 SecurityContext。这意味着Error页面不能使用 SecurityContext 进行授权或显示当前用户,除非 SecurityContext 以某种方式被持久化。

Use RequestAttributeSecurityContextRepository
  • 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

DelegatingSecurityContextRepositorySecurityContext 保存到多个 SecurityContextRepository 委托(delegate),并允许按指定顺序从任何委托(delegate)中检索。

这方面最有用的安排是用下面的例子配置的,它允许同时使用 RequestAttributeSecurityContextRepositoryHttpSessionSecurityContextRepository

Configure DelegatingSecurityContextRepository
  • 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

number 1 在运行应用程序的其余部分之前,SecurityContextPersistenceFilterSecurityContextRepository 加载 SecurityContext 并将其设置在 SecurityContextHolder 上。

number 2 接下来,应用程序被运行。

number 3 最后,如果 SecurityContext 发生了变化,我们会使用 SecurityContextPersistenceRepository 保存 SecurityContext。这意味着在使用 SecurityContextPersistenceFilter 时,只要设置 SecurityContextHolder 就可以确保使用 SecurityContextRepository 来持久化 SecurityContext。

在某些情况下,在 SecurityContextPersistenceFilter 方法完成之前,响应被提交并写入客户端。例如,如果一个重定向被发送到客户端,响应会立即写回给客户端。这意味着在步骤3中建立一个 HttpSession 是不可能的,因为会话ID不能包含在已经写入的响应中。另一种可能发生的情况是,如果客户端认证成功,在 SecurityContextPersistenceFilter 完成之前提交了响应,而客户端在 SecurityContextPersistenceFilter 完成之前发起了第二个请求,错误的认证可能出现在第二个请求中。

为了避免这些问题,SecurityContextPersistenceFilter 同时封装了 HttpServletRequestHttpServletResponse,以检测 SecurityContext 是否发生了变化,如果是的话,就在响应提交之前保存 SecurityContext

SecurityContextHolderFilter

SecurityContextHolderFilter 负责使用 SecurityContextRepository 在请求之间加载 SecurityContext

securitycontextholderfilter

number 1 在运行应用程序的其余部分之前,SecurityContextHolderFilter 从S ecurityContextRepository 加载 SecurityContext 并将其设置在 SecurityContextHolder 上。

number 2 接下来,应用程序被运行。

SecurityContextPersistenceFilter 不同,SecurityContextHolderFilter 只加载 SecurityContext,并不保存 SecurityContext。这意味着在使用 SecurityContextHolderFilter 时,需要明确地保存 SecurityContext

Explicit Saving of 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>

Upon using the configuration, it is important that any code that sets the SecurityContextHolder with a SecurityContext also saves the SecurityContext to the SecurityContextRepository if it should be persisted between requests.

For example, the following code:

Setting SecurityContextHolder with SecurityContextPersistenceFilter
  • Java

  • Kotlin

SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)

should be replaced with

Setting SecurityContextHolder with SecurityContextHolderFilter
  • Java

  • Kotlin

SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)