SpringSecurity------ Servlet Authentication Architecture (八)
- Servlet Authentication Architecture
-
- 1、SecurityContextHolder
- 2、SecurityContext
- 3、Authentication
- 4、GrantedAuthority
- 5、AuthenticationManager
- 6、ProviderManager
- 7、AuthenticationProvider
- 8、AuthenticationEntryPoint
- 9、AbstractAuthenticationProcessingFilter
Servlet Authentication Architecture
以下是Spring Security使用的主要组件:
-
SecurityContextHolder:Spring Security用其存储鉴权成功后的详细授权信息
-
SecurityContext:可以通过SecurityContextHolder获取,包含当前通过身份验证的用户的Authentication
-
Authentication:可以作为用户凭证存入AuthenticationManager用于后续的身份验证,也可以是SecurityContext中当前用户
-
GrantedAuthority:授予Authentication的权限
-
AuthenticationManager:定义Spring Security Filter如何执行身份验证的API
-
ProviderManager:最常用的AuthenticationManager实现类
-
AuthenticationProvider:ProviderManager使用AuthenticationProvider用于执行特定类型的身份验证
-
AuthenticationEntryPoint:用于从客户端请求凭据
-
AbstractAuthenticationProcessingFilter - 用于身份认证的基础过滤器,通过这个类可以了解身份验证的高级流程以及各个部分如何协同工作
1、SecurityContextHolder
Spring Security的构建authentication model的核心就是SecurityContextHolder,SecurityContextHolder包含了SecurityContext:
SecurityContextHolder是Spring Security存储身份验证详细信息的地方。Spring Security并不关心SecurityContextHolder是如何构建的,如果SecurityContextHolder中包含一个值,这个值就会作为当前被验证过的用户。标识用户已经通过身份验证的最直接的方法就是设置SecurityContextHolder,如下所示:
SecurityContext context = SecurityContextHolder.createEmptyContext(); //(1)Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); //(2)
context.setAuthentication(authentication);SecurityContextHolder.setContext(context); //(3)
(1)创建一个SecurityContext ,不使用SecurityContextHolder.getContext().setAuthentication(authentication), 避免多线程竞态条件。
(2)创建一个Authentication设置到SecurityContext中, 这里使用TestingAuthenticationToken实现。
(3)最后,将SecurityContext设置到SecurityContextHolder,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。Spring Security的FilterChainProxy可以保证当前主体的请求处理完成后清除SecurityContext ,所以使用ThreadLocal对象去保存验证主体的详细信息是安全可靠的。
我们可以配置SecurityContextHolder中SecurityContext的存储策略。对于一个独立的应用程序,你可以使用SecurityContextHolder.MODE_GLOBAL;有的应用程序可能希望由安全线程派生的线程也使用相同的安全标识,可以使用SecurityContextHolder.MODE_INHERITABLETHREADLOCAL。可以通过两种方式改变配置:设置系统属性或是调用SecurityContextHolder的静态方法。
2、SecurityContext
SecurityContext是从SecurityContextHolder中获得的,SecurityContext包含一个 Authentication对象。
3、Authentication
Spring Security中使用Authentication的两个主要目的是:
- 输入到AuthenticationManager中,给用户提供用于鉴权的凭证,在这种场景下,isAuthenticated()方法返回false。
- 代表当前已经鉴权的用户,Authentication对象可以从SecurityContext中获取。
Authentication包含以下信息:
- principal:用于识别用户的信息。当使用用户名/密码进行身份验证时,principal通常是一个UserDetails的实例。
- credentials:通常是一个密码。在许多情况下,在用户经过身份验证后被清除,以确保它不会泄漏。
- authorities:授予用户的高级权限。一般是角色信息或是作用域信息。
4、GrantedAuthority
GrantedAuthority是授予用户的高级权限。可以通过Authentication.getAuthorities() 方法获取GrantedAuthority列表。每一个GrantedAuthority都是授予已验证主体的一个权限。这些权限通常情况下都是“roles”,比如ROLE_ADMINISTRATOR 或是ROLE_HR_SUPERVISOR等等。这些“roles”稍后将配置为web授权、方法授权和域对象授权。Spring Security的其他部分能够解释和使用这些权限。当使用用户名/密码进行身份验证时,通常使用UserDetailsService加载GrantedAuthority。
通常GrantedAuthority对象是应用程序范围的权限,它们不是特定于给定域对象的。如此,你不可能仅仅使用一个GrantedAuthority对象去给一个用户提供许可,如果有成千上万个权限的话,会很快耗尽内存或者导致用户授权缓慢。当然,Spring Security是专门为处理这种常见需求而设计的,您可以使用项目的域对象安全功能来实现此目的。
5、AuthenticationManager
AuthenticationManager是定义Spring Security Filter如何执行身份验证的API。Spring Security Filter通过调用AuthenticationManager将Authentication设置到SecurityContextHolder中。如果没有使用Spring Security的过滤器,你可以直接设置SecurityContextHolder,并且不需要使用AuthenticationManager。
6、ProviderManager
ProviderManager是AuthenticationManager最常用的实现。
ProviderManager将其功能职责委托给一些AuthenticationProvider处理。每个AuthenticationProvider都有一次机会去校验身份认证是否成功,或者是表明自己不能做出决定并将身份认证交给后面的AuthenticationProvider处理。如果配置好的AuthenticationProvider中没有一个可以处理当前类型认证的,则会抛出ProviderNotFoundException 异常导致认证失败。ProviderNotFoundException 是一个特殊的AuthenticationException异常类型,用于说明ProviderManager没有配置支持指定类型的认证。
实际上,每个AuthenticationProvider都知道如何执行特定类型的身份验证。例如,一个AuthenticationProvider可能能够验证用户名/密码,而另一个可能能够验证SAML断言。我们只需要实现一个AuthenticationManager,并将我们所需要的 AuthenticationProvider配置到里面,这样就能保证我们的程序支持多种验证类型。
ProviderManager还允许配置一个可选的父级AuthenticationManager,当AuthenticationProvider无法执行身份验证时,可以咨询该父级AuthenticationManager。父级AuthenticationManager可以是任何类型的AuthenticationManager,但它通常是ProviderManager的一个实例。
实际上,多个ProviderManager实列可能拥有共同的父级AuthenticationManager。这在有多个SecurityFilterChain实例的场景中比较常见,这些SecurityFilterChain实例有一些共同的身份验证(共享的父级AuthenticationManager),但也有不同的身份验证机制(不同的ProviderManager)。
默认情况下,ProviderManager将尝试从成功的身份验证请求返回的Authentication对象中清除任何敏感凭据信息。这可以防止密码等信息在HttpSession中保留的时间超过所需的时间。
例如,在使用用户对象的缓存来提高无状态应用程序中的性能时,这可能会导致问题。如果Authentication中包含一个缓存中的对象(比如UserDetails 实例)引用,并且这个对象已经删除了它的凭据,这样就不可能根据缓存的值来进行身份验证。一个明显的解决方案是首先复制对象,要么在缓存实现中,要么在创建返回的身份验证对象的AuthenticationProvider中。或者,您可以禁用ProviderManager上的eraseCredentialsAfterAuthentication属性。
7、AuthenticationProvider
可以在ProviderManager中注入多个AuthenticationProvider。每个AuthenticationProvider执行特定类型的身份验证。例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,而JwtAuthenticationProvider支持对JWT令牌的身份验证。
8、AuthenticationEntryPoint
AuthenticationEntryPoint用于发送HTTP Response用于向客户端请求凭证。
有时,客户端会主动包含诸如用户名/密码之类的凭证来请求资源,在这些情况下,Spring Security不需要发送HTTP Response向客户端请求凭证。
有些时候,客户端可能会发送未经身份验证的请求去获取需要授权访问的资源,在这种情况下,AuthenticationEntryPoint的实现可能执行一次重定向引导用户到登陆页面,或是使用WWW-Authenticate头进行响应等。
9、AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter是对用户凭据进行身份验证的基类过滤器。在验证凭证之前,Spring Security通常使用AuthenticationEntryPoint请求凭证。
接下来,AbstractAuthenticationProcessingFilter可以对提交给它的任何身份验证请求进行身份验证。
(1)当用户提交凭证之后,AbstractAuthenticationProcessingFilter通过HttpServletRequest创建一个Authentication用于进行身份验证。创建的Authentication的具体类型依赖于AbstractAuthenticationProcessingFilter的子类。例如UsernamePasswordAuthenticationFilter从在HttpServletRequest中提交的用户名和密码创建一个UsernamePasswordAuthenticationToken。
(2)接着,Authentication对象将被传递到AuthenticationManager中进行身份认证。
(3)如果认证失败:
- SecurityContextHolder被清空。
- RememberMeServices.loginFail执行,如果没有配置remember-me,则不执行。
- AuthenticationFailureHandler执行。
(4)如果认证成功:
- 通知SessionAuthenticationStrategy有一个新的登录 。
- Authentication被设置到SecurityContextHolder,稍后SecurityContextPersistenceFilter将SecurityContext保存到HttpSession。
- RememberMeServices.loginSuccess执行,如果没有配置remember-me,则不执行。
- ApplicationEventPublisher 发布InteractiveAuthenticationSuccessEvent事件。
- AuthenticationSuccessHandler 执行。