Spring Security提供了对认证的全面支持。

如果你愿意,你可以参考认证机制,了解用户认证的具体方式。这些章节着重于你可能想要的具体的认证方式,并指向架构部分,以描述具体的流程如何工作。

Authentication Mechanisms(认证机制)

Spring Security用于Servlet认证的主要架构组件:

  • SecurityContextHolder - 记录着所有登录用户的认证信息.
  • SecurityContext - 是从SecurityContextHolder中获得的,包含了当前被认证用户的认证信息.
  • Authentication - 是一个用于封装用户认证信息的类,作为AuthenticationManager的入参,也可能是从SecurityContext中取得的用户认证信息.
  • GrantedAuthority - 是赋予登录人的角色功能信息,被复制到 Authentication的principle中.
  • AuthenticationManager - 定义SpringSecurity如何认证一套接口.
  • ProviderManager - 是AuthenticationManager最常见的实现.
  • AuthenticationProvider - 被ProviderManager使用,去执行具体的一种认证方式.
  • Request Credentials withAuthenticationEntryPoint - 用于发送验证成功的响应和验证失败重定向到登录页面的操作.
  • AbstractAuthenticationProcessingFilter - 是一个过滤器,控制整个认证流程,规定以上组件如何协同工作共同完成认证工作.

    SecurityContextHolder

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

Authentication - 图2

SecurityContextHolder记录着所有登录用户的认证信息。Spring Security并不关心SecurityContextHolder是如何被填充的。如果它包含一个值,那么它就被用作当前的认证用户。

最简单的方法是直接设置SecurityContextHolder来表明用户已被认证。

  1. SecurityContext context = SecurityContextHolder.createEmptyContext();
  2. Authentication authentication =
  3. new TestingAuthenticationToken("username", "password", "ROLE_USER");
  4. context.setAuthentication(authentication);
  5. SecurityContextHolder.setContext(context);
  1. 我们从创建一个空的SecurityContext开始。重要的是要创建一个新的SecurityContext实例,而不是使用SecurityContextHolder.getContext().setAuthentication(authentication),以避免多个线程之间的竞赛条件。
  2. 接下来我们创建一个新的认证对象。Spring Security并不关心在SecurityContext上设置了什么类型的认证实现。这里我们使用TestingAuthenticationToken,因为它非常简单。一个更常见的生产场景是UsernamePasswordAuthenticationToken(userDetails, password, authorities)。
  3. 最后,我们在SecurityContextHolder上设置SecurityContext。Spring Security将使用这些信息进行授权

如果你想获得关于已认证用户的信息,你可以通过访问SecurityContextHolder来实现。

  1. SecurityContext context = SecurityContextHolder.getContext();
  2. Authentication authentication = context.getAuthentication();
  3. String username = authentication.getName();
  4. Object principal = authentication.getPrincipal();
  5. Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

默认情况下,SecurityContextHolder使用ThreadLocal来存储SecurityContext,这意味着SecurityContext对同一线程中的方法总是可用的,即使SecurityContext没有被明确地作为参数传递给这些方法。如果在处理完当前用户的请求后注意清除线程,以这种方式使用ThreadLocal是相当安全的。Spring Security的FilterChainProxy确保SecurityContext总是被清空。

image.png
SecurityContext

SecurityContext是从SecurityContextHolder中获得的。SecurityContext包含一个Authentication对象。

Authentication

Authentication在Spring Security中主要有两个作用:

  • 作为AuthenticationManager的入参,用于提供用户认证时需要的凭证。当在这种情况下使用时,可以调用 isAuthenticated()方法校验是否被认证。
  • 代表当前认证的用户。当前的认证信息可以从SecurityContext中获得。

Authentication 包含:

  • principal - 用户的身份信息,当使用用户名/密码进行认证时,这通常是UserDetails的一个实例。
  • credentials - 通常是一个密码。在许多情况下,这将在用户被认证后被清除,以确保它不会被泄露.
  • authorities - 通常是权限、角色.

image.png

GrantedAuthority

GrantedAuthoritys可以通过Authentication.getAuthorities()方法获得。这个方法提供了一个GrantedAuthority对象的集合。毫不奇怪,GrantedAuthority是授予用户的一种权限。这种授权通常是 “角色”,例如 ROLE_ADMINISTRATOR 或 ROLE_HR_SUPERVISOR。这些角色后来被配置为Web授权、方法授权和域对象授权。Spring Security的其他部分能够解释这些授权,并期待它们的存在。当使用基于用户名/密码的认证时,GrantedAuthoritys通常由UserDetailsService加载。
AuthenticationManager

AuthenticationManager

AuthenticationManger规划如何执行认证,返回的Authentication被设置到SecurityContextHolder中。也就是说你可以直接设置一个Authentication到SecurityContextHolder。这样也代表完成了认证。 虽然AuthenticationManager的实现可以是任何东西,但最常见的实现是ProviderManager。

ProviderManager

ProviderManager是AuthenticationManager最常用的实现。它将认证任务委托给其手下的AuthenticationProviders,每一个AuthenticationProvider都可以回答认证是否成功,或者说不能做,并允许下游的AuthenticationProvider来决定。如果配置的AuthenticationProviders都不能认证,则会报一个ProviderNotFoundException异常,这是一个特殊的AuthenticationException,表明ProviderManager没有被配置为支持传入它的认证类型。

Authentication - 图5
在实践中,每个 AuthenticationProvider 只执行特定类型的认证。例如,一个 AuthenticationProvider 可能能够验证一个用户名/密码,而另一个可能能够验证一个 SAML 断言。这允许每个AuthenticationProvider做非常具体的认证类型,同时支持多种类型的认证,并且只暴露出一个AuthenticationManager bean。
ProviderManager还允许配置一个可选的父级AuthenticationManager,在没有AuthenticationProvider可以执行认证的情况下,可以咨询该父级。父级可以是任何类型的AuthenticationManager,但它通常是ProviderManager的一个实例。
Authentication - 图6
事实上,多个ProviderManager实例可能共享同一个父级AuthenticationManager。这在有多个SecurityFilterChain实例的场景中常见,这些实例有一些共同的认证(共享的父认证管理器),但也有不同的认证机制(不同的ProviderManager实例)。
Authentication - 图7
默认情况下,ProviderManager将尝试从认证对象中清除任何敏感的凭证信息,该对象由一个成功的认证请求返回。这可以防止像密码这样的信息在HttpSession中保留超过必要的时间。
当你使用用户对象的缓存时,这可能会导致问题,例如,在一个无状态的应用程序中提高性能。如果Authentication包含对缓存对象的引用(比如UserDetails实例),而这个对象的证书被删除了,那么就不可能再对缓存的值进行认证了。如果你使用一个缓存,你需要考虑到这一点。一个显而易见的解决方案是先制作一个对象的副本,可以在缓存实现中,也可以在创建返回的认证对象的AuthenticationProvider中。另外,你可以禁用ProviderManager上的eraseCredentialsAfterAuthentication属性。

AuthenticationProvider

多个AuthenticationProviders可以被注入ProviderManager中。每个AuthenticationProvider都执行一种特定类型的认证。例如,DaoAuthenticationProvider支持基于用户名/密码的认证,而JwtAuthenticationProvider支持认证JWT令牌。

Request Credentials with AuthenticationEntryPoint

AuthenticationEntryPoint用于当用户未认证或者没有权限的时候发送一个HTTP响应。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter被用作验证用户凭证的基础过滤器。在认证凭证之前,Spring Security通常使用AuthenticationEntryPoint请求凭证。 接下来,AbstractAuthenticationProcessingFilter可以对提交给它的任何认证请求进行认证。

Authentication - 图8

  1. 当用户提交他们的证书时,AbstractAuthenticationProcessingFilter会从HttpServletRequest中创建一个要认证的认证。创建的认证的类型取决于AbstractAuthenticationProcessingFilter的子类。例如,UsernamePasswordAuthenticationFilter从HttpServletRequest中提交的用户名和密码创建一个UsernamePasswordAuthenticationToken。

image.png

  1. 接下来,Authentication被传入AuthenticationManager,以进行认证。

image.png

  1. 如果认证失败:
    1. 清空SecurityContextHolder
    2. 执行RememberMeServices.loginFail,如果配置了remember me的话
    3. 执行AuthenticationFailureHandler。

carbon.svg

  1. 如果认证成功:
    1. SessionAuthenticationStrategy被通知有新的登录
    2. 一个包含完整信息的Authentication将被设置到SecurityContextHolder,之后将SecurityContextPersistenceFilter设置到HttpSession中
    3. 执行RememberMeServices.loginSuccess,如果配置了 remeber me的话
    4. ApplicationEventPublisher发布了一个InteractiveAuthenticationSuccessEvent
    5. 执行AuthenticationSuccessHandler

image.png