Spring Security提供了对认证的全面支持。
如果你愿意,你可以参考认证机制,了解用户认证的具体方式。这些章节着重于你可能想要的具体的认证方式,并指向架构部分,以描述具体的流程如何工作。
Authentication Mechanisms(认证机制)
- Username and Password - how to authenticate with a username/password
- OAuth 2.0 Login - OAuth 2.0 Log In with OpenID Connect and non-standard OAuth 2.0 Login (i.e. GitHub)
- SAML 2.0 Login - SAML 2.0 Log In
- Central Authentication Server (CAS) - Central Authentication Server (CAS) Support
- Remember Me - how to remember a user past session expiration
- JAAS Authentication - authenticate with JAAS
- OpenID - OpenID Authentication (not to be confused with OpenID Connect)
- Pre-Authentication Scenarios - authenticate with an external mechanism such as SiteMinder or Java EE security but still use Spring Security for authorization and protection against common exploits.
- X509 Authentication - X509 Authentication
Servlet Authentication Architecture(认证架构)
本讨论对Servlet Security: The Big Picture 进行了扩展。阐述了Spring Security用于Servlet认证的主要架构组件。如果你需要具体的流程来解释这些部分是如何结合在一起的,请看认证机制的具体章节。
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。
SecurityContextHolder记录着所有登录用户的认证信息。Spring Security并不关心SecurityContextHolder是如何被填充的。如果它包含一个值,那么它就被用作当前的认证用户。
最简单的方法是直接设置SecurityContextHolder来表明用户已被认证。
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
- 我们从创建一个空的SecurityContext开始。重要的是要创建一个新的SecurityContext实例,而不是使用SecurityContextHolder.getContext().setAuthentication(authentication),以避免多个线程之间的竞赛条件。
- 接下来我们创建一个新的认证对象。Spring Security并不关心在SecurityContext上设置了什么类型的认证实现。这里我们使用TestingAuthenticationToken,因为它非常简单。一个更常见的生产场景是UsernamePasswordAuthenticationToken(userDetails, password, authorities)。
- 最后,我们在SecurityContextHolder上设置SecurityContext。Spring Security将使用这些信息进行授权。
如果你想获得关于已认证用户的信息,你可以通过访问SecurityContextHolder来实现。
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
默认情况下,SecurityContextHolder使用ThreadLocal来存储SecurityContext,这意味着SecurityContext对同一线程中的方法总是可用的,即使SecurityContext没有被明确地作为参数传递给这些方法。如果在处理完当前用户的请求后注意清除线程,以这种方式使用ThreadLocal是相当安全的。Spring Security的FilterChainProxy确保SecurityContext总是被清空。
SecurityContext
SecurityContext是从SecurityContextHolder中获得的。SecurityContext包含一个Authentication对象。
Authentication
Authentication在Spring Security中主要有两个作用:
- 作为AuthenticationManager的入参,用于提供用户认证时需要的凭证。当在这种情况下使用时,可以调用
isAuthenticated()
方法校验是否被认证。 - 代表当前认证的用户。当前的认证信息可以从SecurityContext中获得。
Authentication 包含:
- principal - 用户的身份信息,当使用用户名/密码进行认证时,这通常是UserDetails的一个实例。
- credentials - 通常是一个密码。在许多情况下,这将在用户被认证后被清除,以确保它不会被泄露.
- authorities - 通常是权限、角色.
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没有被配置为支持传入它的认证类型。
在实践中,每个 AuthenticationProvider 只执行特定类型的认证。例如,一个 AuthenticationProvider 可能能够验证一个用户名/密码,而另一个可能能够验证一个 SAML 断言。这允许每个AuthenticationProvider做非常具体的认证类型,同时支持多种类型的认证,并且只暴露出一个AuthenticationManager bean。
ProviderManager还允许配置一个可选的父级AuthenticationManager,在没有AuthenticationProvider可以执行认证的情况下,可以咨询该父级。父级可以是任何类型的AuthenticationManager,但它通常是ProviderManager的一个实例。
事实上,多个ProviderManager实例可能共享同一个父级AuthenticationManager。这在有多个SecurityFilterChain实例的场景中常见,这些实例有一些共同的认证(共享的父认证管理器),但也有不同的认证机制(不同的ProviderManager实例)。
默认情况下,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可以对提交给它的任何认证请求进行认证。
- 当用户提交他们的证书时,AbstractAuthenticationProcessingFilter会从HttpServletRequest中创建一个要认证的认证。创建的认证的类型取决于AbstractAuthenticationProcessingFilter的子类。例如,UsernamePasswordAuthenticationFilter从HttpServletRequest中提交的用户名和密码创建一个UsernamePasswordAuthenticationToken。
- 接下来,Authentication被传入AuthenticationManager,以进行认证。
- 如果认证失败:
- 清空SecurityContextHolder
- 执行RememberMeServices.loginFail,如果配置了remember me的话
- 执行AuthenticationFailureHandler。
- 如果认证成功:
- SessionAuthenticationStrategy被通知有新的登录
- 一个包含完整信息的Authentication将被设置到SecurityContextHolder,之后将SecurityContextPersistenceFilter设置到HttpSession中
- 执行RememberMeServices.loginSuccess,如果配置了 remeber me的话
- ApplicationEventPublisher发布了一个InteractiveAuthenticationSuccessEvent
- 执行AuthenticationSuccessHandler