Architecture

①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖

当前记录了 SpringSecurity5.5.2 中的内容

1. 功能规划

  • 多租户 realm
  • 认证

2. Hello Srping Security

SpringBoot 官方给出的例子,从这些例子可以学到以下知识 。

项目名称简要说明知识点
hello没有权限认证
hello-security有权限认证只用引入 security 包就可以了
hello-security-explicit指定用户名与密码用户名和密码是写死的
使用 webSecurityConfig 文件
使用了 simul 自动化测试
authentication/username-password
jwt/login
oauth2
ldapldap 应用用到的不多
saml2-loginsarml 应用用到的

3. 工作原理

Spring 官方文档有详细说明

Spring 主要是通过Filter来实现各种安全认证的。

认证不通过与权限不足的错误处理图

4. 认证架构组件

  • SecurityContextHolder - 这SecurityContextHolder是 Spring Security 存储身份验证人员详细信息的地方。
  • SecurityContext - 通过SecurityContextHolder获取,包含了经过身份验证的用户的Authentication
  • Authentication- 可以通过AuthenticationManager进行身份验证后产生,或者直接从SecurityContext得到.
  • GrantedAuthority - 授予主体的权限Authentication(即角色、范围等)
  • AuthenticationManager - 定义 Spring Security 的过滤器如何执行身份验证的 API 。这是一个接口,只有一个函数authenticate,后来发现可能有多个确认者,所以就做了 ProviderManager 来替代,这个类可以管理多个 Provider
  • ProviderManager - 最常见的AuthenticationManager.
  • AuthenticationProvider - 用于ProviderManager执行特定类型的身份验证。这是一个接口,里面有一个authenticatesupports函数,Spring 中大部分认证的内置类都继承了这个接口。那么如何添加一个自定义的 provider 呢?在认证那个章节中写的有。
  • Request Credentials withAuthenticationEntryPoint - 用于从客户端请求凭据(即重定向到登录页面、发送WWW-Authenticate响应等)
  • AbstractAuthenticationProcessingFilter -Filter用于身份验证的基础。这也很好地了解了高级身份验证流程以及各个部分如何协同工作。

4.1 SecurityContext 权限容器

  • SecurityContextHolder
    • SecurityContext
      • Authentication
        • Principal:主角
        • Credentials:凭证
        • Authorities:权限

用户已通过身份验证的最简单方法是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)用来避免跨多个线程的竞争条件。

得到当前经过身份验证的用户

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

4.2 Authentication 验证数据

Authentication 在 Spring Security 中有两个主要目的:

  • 认证前:用于 AuthenticationManager 进行身份验证时的用户凭据。在这种情况下使用时,isAuthenticated() 返回 false。
  • 认证后:表示当前经过身份验证的用户。当前的 Authentication 可以从 SecurityContext 中获取。

其中Authentication包含:

  • principal- 识别用户。使用用户名/密码进行身份验证时,这通常是UserDetails.
  • credentials- 通常是密码。在许多情况下,这将在用户通过身份验证后被清除,以确保它不会泄露。
  • authorities- GrantedAuthority是授予用户的高级权限。一些示例是角色或范围。

这里还有连个附属变量:

  • isAuthenticated返回true,就表示认证通过。
  • detail,放一些用户的附加信息,例如 IP 地址、证书序列号等自定义信息

4.3 GrantedAuthority 以授权的权限

4.4 AuthenticationManager 认证管理器

AuthenticationManager决定 Spring Security 的 Filter 如何执行身份验证

  • 如果您不与Spring Security 的Filterss集成,则可以SecurityContextHolder直接设置并且不需要使用AuthenticationManager.
  • 虽然 的实现AuthenticationManager可以是任何东西,但最常见的实现是ProviderManager.

4.5 ProviderManager 提供者管理器

ProviderManager包含了很多的AuthenticationProviders,这些AuthenticationProviders负责验证身份是否成功、失败或者交给下一个来处理。

  • 没有配置AuthenticationProvider会抛出一个错误ProviderNotFoundException

这样做的好处,每一种身份验证类型,都可以指定相应的身份验证提供者。例如,一个人AuthenticationProvider可能能够验证用户名/密码,而另一个人可能能够验证 SAML 断言。这允许每个AuthenticationProvider人进行非常特定类型的身份验证,同时支持多种类型的身份验证并且只公开一个AuthenticationManagerbean。

ProviderManager还允许配置一个可选的父级AuthenticationManager,在AuthenticationProvider无法执行身份验证的情况下咨询该父级。父项可以是 的任何类型AuthenticationManager,但通常是 的实例ProviderManager

事实上,多个ProviderManager实例可能共享同一个 parent AuthenticationManager。这在多个SecurityFilterChain实例具有共同身份验证(共享父级AuthenticationManager)但也具有不同身份验证机制(不同ProviderManager实例)的情况下有些常见。

4.6 AuthenticationProvider 身份验证提供者

可以将多个AuthenticationProviders注入ProviderManager. 每个都AuthenticationProvider执行特定类型的身份验证。例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,同时JwtAuthenticationProvider支持对 JWT 令牌进行身份验证。

4.7 请求凭证与AuthenticationEntryPoint

AuthenticationEntryPoint 被用来向客户端发送一个 Http response,当客户端请求一个凭证的时候。

有时,客户端会主动包含凭据(例如用户名/密码)来请求资源。在这些情况下,Spring Security 就不会向客户端发送 Http response 了。

在有些情况,例如客户端将对他们无权访问的资源发出未经身份验证的请求。在这种情况下,该AuthenticationEntryPoint实现可能会执行重定向到登录页面,使用WWW-Authenticate头等进行响应。

参考HTTP 教程WWW-Authenticate详细说明,WWW-Authenticate报头与一个一起发送401 Unauthorized响应。

4.8 AbstractAuthenticationProcessingFilter 抽象认证处理过滤器

①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖

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

当用户提交登录凭证后,AbstractAuthenticationProcessingFilter会从 HttpServletRequest 创建一个Authentication,并且进行认证。到底创建一个什么样的鉴别数据Authentication,取决于AbstractAuthenticationProcessingFilter的具体实现类。 例如UsernamePasswordAuthenticationFilter,会根据HttpServletRequest中的 username 与 password 创建一个UsernamePasswordAuthenticationToken

下一步,Authentication会被传递给AuthenticationManager来进行认证。

如果认证失败

  • SecurityContextHolder 中被清除出去。
  • RememberMeServices.loginFail被调用。如果记住我没有配置,这是一个空操作。
  • AuthenticationFailureHandler 被调用。

如果身份验证成功

  • SessionAuthenticationStrategy 收到新登录通知。
  • Authentication被设置在SecurityContextHolder 中。稍后将SecurityContextPersistenceFilter保存SecurityContextHttpSession.
  • RememberMeServices.loginSuccess被调用。如果记住我没有配置,这是一个空操作。
  • ApplicationEventPublisher发布一个InteractiveAuthenticationSuccessEvent.
  • AuthenticationSuccessHandler 被调用。

4.9 构建过程

Spring-Security 安全处理是基于 Servlet Filter 来实现的。

这里通过添加一个代理 Filter(DelegatingFilterProxy),这个 Filter 的名称一般定义为springSecurityFilterChain,它的实现类就是FilterChainProxy

FilterChainProxy

FilterChainProxy 管理了所有的SecurityFilterChain 集合,这里SecurityFilterChain是一个接口,

默认的实现有DefaultSecurityFilterChain

FilterChainProxy 会根据请求的 URL 来判断使用哪一个 SecurityFilterChain 。这里注意,spring-security 对某一个请求,只会使用一个 SecurityFilterChain 来处理。

5. 汇总

授权部分 Authoriztion (Access Control)

AccessDecisionManager

public interface AccessDecisionManager {
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}

AccessDecisionVoter

boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
//authentication表示认证处理后,保存的认证信息
//object是需要判断是否能获取的资源
//ConfigAttribute 是用于access-control判断的信息,比如角色-url列表
int vote(Authentication authentication, S object,Collection<ConfigAttribute> attributes);

默认的处理类:只要返回积极响应即可

org.springframework.security.access.vote.AffirmativeBased

5.1 设计原理

AccessDecisionManager 是入口,然后具体是否有权限是调用 AccessDecisionVoter 进行判断。

为什么这样设计?
因为这里AccessDecisionManager有三个实现:
AffirmativeBased一种只要一个voter返回有权限即可。
ConsensusBased大多数voter返回有权限,就说明有权限
UnConsensusBased要求必须所有的voter都返回或者弃权才行。

具体调用过程分析

这里decide方法被
org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke ->
org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation调用。

Springboot 中 AccessDecisionManager 的创建过程

AbstractInterceptUrlConfigurercreate->
FilterSecurityInterceptor ->
getAccessDecisionManager ->
AffirmativeBased

源码展示

private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
FilterInvocationSecurityMetadataSource metadataSource,
AuthenticationManager authenticationManager) throws Exception {
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
securityInterceptor.setSecurityMetadataSource(metadataSource);
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
securityInterceptor.setAuthenticationManager(authenticationManager);
securityInterceptor.afterPropertiesSet();
return securityInterceptor;
}

设计原理

这是对整个 Spring-Security 框架的一个总体认识

对理解它的各组件功能、其工作流程和源码分析有很大帮助

  • 以 Filter 为发起,使用 FilterChainProxy 统领整个安全处理框架。

  • 在 FilterChainProxy 中根据请求路径,选择具体的 securityFilterChain 实现(即关于安全的 filter 实现),处理安全逻辑

  • 每个 security filter,作用不同。(需要关注这里 filter 的顺序)

    • 有的则进行一些通常安全的校验

      • 例如:CsrfFilter
    • 有的根据 RequestMatcher,判断本 filter 是否尝试去处理,如果是,则封装认证信息成具体 token 类型,然后调用authenticationManager 进行具体的认证处理;

      • 例如:UsernamePasswordAuthenticationFilter
    • 有的对请求是否认证或者是否成功,通过对异常的判断,来进行处理。比如:重定向到登入页面。

      例如:ExceptionTranslationFilter

      该 filter 通过对 AuthenticationException的判断,然后调用AuthenticationEntryPoint 接口的 commence 方法进行处理。

5.2 同时支持多种认证

如何提供多种认证机制的?

就是通过这里的AuthenticationProvider,因为ProviderManager他管理了多个AuthenticationProvider,每一种机制可以是一个AuthenticationProvider的实现。

是否需要考虑各个认证机制的顺序?

不需要考虑各个机制的顺序问题,因为只要一个认证通过就 ok 了。(代码实现是直接跳出循环)

具体实现原理分析

Parent AuthenticationManager

ProviderManager管理了一个父的AuthenticationManager,作为最后的兜底。就是说当所有的认证机制都无法判断的时候,即返回为空的时候,此时可以通过父类来处理。如果没有定义这个父类,是会抛出AuthenticationException。

5.3 spring-security 入口

DelegatingFilterProxy(Spring-Web) ->FilterChainProxy (Spring-Security)->

这里看出 Spring 是通过 DelegatingFilterProxy 来整合 Spring-Security 的

一般这个类在我们的web.xml配置中使用的,当我们定义的filter为DelegatingFilterProxy
此时呢,所有的请求filter,都会去容器中找一个叫springsecurityfilterchain的bean来处理。
这里是方便我们去处理web应用,当我们需要一个filter来处理,此时我们只需要在spring相关的配置文件中定义一个bean,id为springSecurityFilterChain ,而在web.xml中写成DelegatingFilterProxy。

特别提示

DelegatingFilterProxy不需要是一个 spring 的 bean,也就是说在不使用 spring 容器开发的应用中,可以是有该类。或者说在 spring 容器还没有初始化的时候,可以使用该类来处理。这也是为什么可以在 web.xml 中使用该类的原因。

DelegatingFilterProxy 的作用DelegatingFilterProxy 把处理的执行交给了 FilterChainProxy(注意这个类是 spring-security 中的类,然后会有很多这样的类去做具体的 filter 操作),这个 bean 一般使用 springSecurityFilterChain 这个 bean id 来做配置。

FilterChainProxy这里代理中包含很多的filter(关于spring-security安全认证和权限控制的filters),当然也可以是不同的filter chains,不过一个请求只会被一个filter chain处理,一般是根据请求路径来判断交给那个filter chain处理。
这里就是我们需要关注和定义的filters

SecurityFilterChain 接口

spring-security可以有很多SecurityFilterChain,但是最终只有一个会被匹配。
可以用于oauth整合、jwt整合,也可以用于处理不需要安全处理的路径。
也就是说所有的链都必须实现该类
默认的**DefaultSecurityFilterChain**

6. 参考文档