Servlet Authentication Architecture

ProviderManager AuthenticationProvider GrantedAuthority Request Credentials AuthenticationEntryPoint AbstractAuthenticationProcessingFilter SecurityContext Spring Security :description: 这篇概述描述了 Spring Security 中用于处理认证的架构组件。核心是 SecurityContextHolder,其中存储了已认证用户的详细信息。Authentication 接口定义了 Spring Security 的过滤器如何执行认证,AuthenticationManager 管理认证流程。ProviderManager 是 AuthenticationManager 的最常见实现,它使用 AuthenticationProvider 列表来执行特定类型的认证。Request Credentials with AuthenticationEntryPoint 用于向客户端请求凭证,AbstractAuthenticationProcessingFilter 提供了一个基本过滤器用于认证用户凭证。这篇文章提供了认证流程的高级视图,其中涉及这些组件如何协同工作来确定用户的认证状态。

此讨论在 Servlet Security: The Big Picture 的基础上进行扩展,以描述 Spring Security 中用于 Servlet 身份验证的主要架构组件。如果你需要具体流程来解释这些部分如何配合使用,请查看 Authentication Mechanism 的特定部分。

SecurityContextHolder

Spring Security 认证模型的核心是 SecurityContextHolder。它包含 SecurityContext

securitycontextholder

SecurityContextHolder 是 Spring Security 存储谁 authenticated 的详细信息的位置。Spring Security 不关心如何填充 SecurityContextHolder。如果它包含一个值,那么它将用作当前经过身份验证的用户。

指示用户已认证的最简单方法是直接设置 SecurityContextHolder

Setting SecurityContextHolder
  • Java

  • Kotlin

SecurityContext context = SecurityContextHolder.createEmptyContext(); (1)
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); (2)
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context); (3)
val context: SecurityContext = SecurityContextHolder.createEmptyContext() (1)
val authentication: Authentication = TestingAuthenticationToken("username", "password", "ROLE_USER") (2)
context.authentication = authentication

SecurityContextHolder.setContext(context) (3)
1 我们首先创建一个空 SecurityContext。您应该创建一个新的 SecurityContext 实例,而不是使用 SecurityContextHolder.getContext().setAuthentication(authentication) 来避免跨多个线程中的竞争条件。
2 接下来,我们创建一个新的 <<`Authentication`,servlet-authentication-authentication>> 对象。Spring Security 并不关心在 SecurityContext 上设置了哪种类型的 Authentication 实现。在这里,我们使用 TestingAuthenticationToken,因为它非常简单。更常见的生产场景是 UsernamePasswordAuthenticationToken(userDetails, password, authorities)
3 最后,我们在 SecurityContextHolder 上设置 SecurityContext。Spring Security 使用此信息进行 authorization

要获取已认证主体的相关信息,请访问 SecurityContextHolder

Access Currently Authenticated User
  • Java

  • Kotlin

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
val context = SecurityContextHolder.getContext()
val authentication = context.authentication
val username = authentication.name
val principal = authentication.principal
val authorities = authentication.authorities

默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些详细信息,这意味着 SecurityContext 始终可供同一线程中的方法使用,即使 SecurityContext 并未明确作为参数传递给这些方法。如果你在处理当前负责人的请求后清除线程,那么以这种方式使用 ThreadLocal 是非常安全的。Spring Security 的 FilterChainProxy 确保始终清除 SecurityContext

由于某些应用程序与线程的工作方式很具体,因此并不完全适合使用 ThreadLocal。例如,Swing 客户端可能希望 Java Virtual Machine 中的所有线程使用相同的安全上下文。你可以在启动时使用 strategy 为 SecurityContextHolder 配置,以指定如何存储上下文。对于独立应用程序,你将使用 SecurityContextHolder.MODE_GLOBAL strategy。其他应用程序可能希望由安全线程生成的线程也假定相同安全标识。你可以使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 实现此目的。你可以通过两种方式更改模式,即从默认 SecurityContextHolder.MODE_THREADLOCAL 更改。第一种方法是设置系统属性。第二种方法是在 SecurityContextHolder 中调用一个 static 方法。大多数应用程序不需要改变默认设置。但是,如果你有需要,请查看 SecurityContextHolder 的 JavaDoc 以了解更多信息。

SecurityContext

SecurityContextHolder获得 {security-api-url}org/springframework/security/core/context/SecurityContext.html[SecurityContext]。`SecurityContext`包含一个 Authentication对象。

Authentication

在 Spring 安全中,{security-api-url}org/springframework/security/core/Authentication.html[Authentication] 接口有两个主要用途:

  • 提供用户提供的用来身份验证的凭据时输入到 <<`AuthenticationManager`,servlet-authentication-authenticationmanager>>。当在该场景中使用时,isAuthenticated() 返回 false

  • 表示当前经过身份验证的用户。您可以从 SecurityContext 获取当前 Authentication

Authentication 包含:

  • principal:识别用户。使用用户名/密码进行身份验证时,这通常是 UserDetails 的一个实例。

  • credentials:通常是一个密码。在许多情况下,它会在用户通过身份认证后被清除,以确保其未泄漏。

  • authorities:<<`GrantedAuthority`,servlet-authentication-granted-authority>> 实例是用户获得的高级别权限。有两个示例是角色和作用域。

GrantedAuthority

{security-api-url}org/springframework/security/core/GrantedAuthority.html[GrantedAuthority] 实例是授予用户的高级权限。两个示例是角色和作用域。

你可以从 <<`Authentication.getAuthorities(),servlet-authentication-authentication>> method. This method provides a `Collection of GrantedAuthority objects>> 中获取 GrantedAuthority 实例。GrantedAuthority 不出所料,它是一项授予负责人的权限。此类权限通常是 “roles”,例如 ROLE_ADMINISTRATORROLE_HR_SUPERVISOR。这些角色稍后将配置用于 Web 授权、方法授权和域对象授权。Spring Security 的其他部分会解释这些权限并希望它们存在。使用基于用户名/密码的身份验证时, GrantedAuthority 实例通常由 UserDetailsService 加载。

通常,GrantedAuthority 对象是应用程序范围的权限。它们不特定于给定的域对象。因此,你不太可能有一个 GrantedAuthority 来表示对权限为 Employee 对象 54 的权限,因为如果存在数千个这样的权限,你将很快耗尽内存(或至少导致应用程序花很长时间来认证用户)。当然,Spring Security 的设计明确满足此常见要求,但你应该使用项目的域对象安全功能来解决此目的。

AuthenticationManager

{security-api-url}org/springframework/security/authentication/AuthenticationManager.html[AuthenticationManager]是定义 Spring Security 的过滤器 authentication执行方式的 API。返回的 <<`Authentication`,servlet-authentication-authentication>> 随后由触发了 AuthenticationManager`的控制器(即,Spring Security’s `Filters instances)设置在 SecurityContextHolder上。如果你未与 Spring Security 的 Filters`实例集成,你可以直接设置 `SecurityContextHolder`且不需要使用 `AuthenticationManager

虽然 AuthenticationManager 的实现可以是任何东西,但最常见的实现是 <<`ProviderManager`,servlet-authentication-providermanager>>。

ProviderManager

{security-api-url}org/springframework/security/authentication/ProviderManager.html[ProviderManager] 是 <<`AuthenticationManager`,servlet-authentication-authenticationmanager>> 最常用的实现。ProviderManager`委托给 <<`AuthenticationProvider,servlet-authentication-authenticationprovider>> 实例的 List。每个 AuthenticationProvider`都有一个机会指出身份验证应成功、失败,或者指出它无法做出决定并允许下游 `AuthenticationProvider`决定。如果未配置的 `AuthenticationProvider`实例均无法进行身份验证,则身份验证会因出现 `ProviderNotFoundException`而失败,`ProviderNotFoundException`是一个特殊 `AuthenticationException,指出未配置 `ProviderManager`以支持传入的 `Authentication`类型。

providermanager

在实践中,每个 AuthenticationProvider 知道如何执行特定类型的身份验证。例如,一个 AuthenticationProvider 可能会验证用户名/密码,而另一个可能会验证 SAML 断言。这能让每个 AuthenticationProvider 在支持多种身份验证类型且仅公开一个 AuthenticationManager bean 的情况下,执行非常具体的身份验证类型。

ProviderManager 还允许配置一个可选的父 AuthenticationManager,在没有 AuthenticationProvider 可以执行身份验证的情况下咨询它。父元素可以是任何类型的 AuthenticationManager,但它通常是 ProviderManager 的一个实例。

providermanager parent

事实上,多个 ProviderManager 实例可以共享相同的父 AuthenticationManager。这在有多个 @{6} 实例共享一些相同的身份验证(共享父 AuthenticationManager),但身份验证机制又不相同的(不同的 ProviderManager 实例)的情况下比较常见。

providermanagers parent

默认情况下,ProviderManager 尝试清除成功身份验证请求返回的 Authentication 对象中的任何敏感凭据信息。这可以防止诸如密码之类的信息在 HttpSession 中保留的时间超过必要时间。

当使用用户对象的缓存时,可能会导致问题,例如,提高无状态应用程序的性能。如果 `Authentication`包含对缓存中对象的引用(例如 `UserDetails`实例)并且其凭据已删除,则无法再对缓存值进行身份验证。如果使用缓存,则需要考虑此问题。一个显而易见的解决方案是首先对对象进行复制,无论是在缓存实现还是在创建返回 `Authentication`对象的 `AuthenticationProvider`中。或者,可以在 `ProviderManager`上禁用 `eraseCredentialsAfterAuthentication`属性。请参阅 {security-api-url}org/springframework/security/authentication/ProviderManager.html[ProviderManager] 类的 Javadoc。

AuthenticationProvider

你可以注入多个 {security-api-url}org/springframework/security/authentication/AuthenticationProvider.html[AuthenticationProvider`s] instances into <<`ProviderManager,servlet-authentication-providermanager>>。每个 AuthenticationProvider`会执行特定类型的认证。例如,`DaoAuthenticationProvider支持基于用户名/密码的认证,而 `JwtAuthenticationProvider`支持验证 JWT 令牌。

Request Credentials with AuthenticationEntryPoint

{security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[AuthenticationEntryPoint] 用于发送一个 HTTP 响应,该响应请求客户端提供凭据。

有时候,客户端主动包含凭证(例如用户名和密码)来请求资源。在这些情况下,Spring Security 不需要提供要求客户端提供凭据的 HTTP 响应,因为它们已经包含在内了。

在其他情况下,客户端会向其无权访问的资源发出未经验证的请求。在这种情况下,会使用 `AuthenticationEntryPoint`的实现向客户端请求凭证。该 `AuthenticationEntryPoint`实现可能执行 redirect to a log in page,使用 WWW-Authenticate标题做出响应或采取其他操作。

AbstractAuthenticationProcessingFilter

{security-api-url}org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html[AbstractAuthenticationProcessingFilter] 用作用户凭据的身份验证的基 Filter。在对凭据进行身份验证之前,Spring 安全通常使用 <<`AuthenticationEntryPoint`,servlet-authentication-authenticationentrypoint>> 请求凭据。

接下来,AbstractAuthenticationProcessingFilter 可以对提交给它的任何身份验证请求进行身份验证。

abstractauthenticationprocessingfilter

number 1当用户提交其凭证时,AbstractAuthenticationProcessingFilter`会根据 `HttpServletRequest`创建一个待认证的 <<`Authentication,servlet-authentication-authentication>>。创建的 Authentication`类型取决于 `AbstractAuthenticationProcessingFilter`的子类。例如,`UsernamePasswordAuthenticationFilter根据在 HttpServletRequest`中提交的 _username_和 _password_创建一个 `UsernamePasswordAuthenticationToken

number 2接下来,将 <<`Authentication`,servlet-authentication-authentication>> 传递到 <<`AuthenticationManager`,servlet-authentication-authenticationmanager>> 进行身份验证。

number 3如果身份验证失败,则_Failure_。

  • SecurityContextHolder 被清除。

  • 调用 RememberMeServices.loginFail。如果未配置记住我,则这是一个无操作。请参阅 {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[rememberme] 包。

  • 调用 AuthenticationFailureHandler。请参阅 {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[AuthenticationFailureHandler] 接口。

number 4 如果身份验证成功,则 Success

  • 通知 SessionAuthenticationStrategy 有新登录。请参阅 {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[SessionAuthenticationStrategy] 接口。

  • AuthenticationSecurityContextHolder 上设置。稍后,如果你需要保存 SecurityContext,以便它可以在将来的请求中自动设置,则必须显式调用 SecurityContextRepository#saveContext。请参阅 {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[SecurityContextHolderFilter] 类。

  • RememberMeServices.loginSuccess`被调用。如果未配置自动登录,则这是一个空操作。请参阅 {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[`rememberme] 包。

  • ApplicationEventPublisher publishes an InteractiveAuthenticationSuccessEvent.

  • AuthenticationSuccessHandler`被调用。请参阅 {security-api-url}org/springframework/security/web/authentication/AuthenticationSuccessHandler.html[`AuthenticationSuccessHandler] 接口。