Spring Security的整体架构

130 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

Spring Security的整体架构

\qquad在学习 Spring Security 的时候会碰到很多与认证授权相关的概念,先介绍一下 Spring Security 中常见的概念, 以及认证、授权思路,了解 Spring Security 的整体架构,和涉及的所有组件。

1.认证和授权

1.1 认证

\qquad在 Spring Security 的架构设计中,认证(Authentication)和授权(Authorization)是分开 的,无论使用什么样的认证方式,都不会影响授权,这是两个独立的存在,这种独立带来的好处之一,就是 Spring Security 可以非常方便地整合一些外部的认证方案。
\qquad在 Spring Security 中,用户的认证信息主要由 Authentication 的实现类来保存,接口定义如下:

public interface Authentication extends Principal, Serializable {

    //getAuthorities 方法:用来获取用户的权限。 
   Collection<? extends GrantedAuthority> getAuthorities();
   
    //getCredentials 方法:用来获取用户凭证,一般来说就是密码。 
   Object getCredentials();
   
    //getDetails 方法:用来获取用户携带的详细信息,可能是当前请求乊类等。 
   Object getDetails();
   
    //getPrincipal 方法:用来获取当前用户,例如是一个用户名或者一个用户对象。 
   Object getPrincipal();
   
    //isAuthenticated:当前用户是否认证成功。
   boolean isAuthenticated();

   void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

}

当用户使用用户名/密码登录或使用 Remember-me 登录时,都会对应一个不同的 Authentication 实例。
\qquadAuthentication 是用来在保存用户的信息,那么获取的用户的信息是用SecurityContextHolder这个类来进行处理。Spring Security会将登录用户数据保存在Session中。当用户登录成功后,Spring Security会将登录成功的用户信息保存到SecurityContextHolder中。SecurityContextHolder 中的数据保存默认是通过ThreadLocal来实现的,使用ThreadLocal创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。
\qquad当登录请求处理完毕后,Spring Security会将SecurityContextHolder中的数据拿出来保存到Session中,同时将SecurityContexHolder中的数据清空。以后每当有请求到来时,Spring Security就会先从Session中取出用户登录数据,再保存到SecurityContextHolder中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据再次取出并保存到Session 中,然后再将SecuritySecurityContextHolder中的数据清空。这一策略使得在Controller/Service 层以及任何代码中都能获取当前登录用户数据。但是带来的另外一个问题就是,在子线程中想要获取用户登录数据就比较麻烦。Spring Security 对此也提供了相应的解决方案,如果开发者使用 @Async 注解来开启异步任务的话,那么只需要添加如下配置,使用 Spring Security 提供的异步任务代理,就可以在异步任务中从 Security ContextHolder 里边获取当前登录用户的信息:

@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport { 
    @Override 
    public Executor getAsyncExecutor() { 
        return new DelegatingSecurityContextExecutorService( 
                Executors.newFixedThreadPool(5)); 
    } 
}

\qquadSpring Security 中的认证工作主要由 AuthenticationManager 接口来负责,接口定义如下:

public interface AuthenticationManager {

   Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

\qquadAuthenticationManager 只有一个 authenticate 方法可以用来做认证,该方法有三个不同的返回值:

  • 返回 Authentication,表示认证成功。
  • 抛出 AuthenticationException 异常,表示用户输入了无效的凭证。
  • 返回 null,表示不能断定。

\qquadAuthenticationManager 最主要的实现类是 ProviderManager,ProviderManager 管理了众多 的 AuthenticationProvider 实例,AuthenticationProvider 有点类似于 AuthenticationManager,但 是它多了一个 supports 方法用来判断是否支持给定的 Authentication 类型。

public interface AuthenticationProvider {

   Authentication authenticate(Authentication authentication) throws AuthenticationException;

   boolean supports(Class<?> authentication);

}

由于 Authentication 拥有众多不同的实现类,这些不同的实现类又由不同的 AuthenticationProvider 来处理,所以 AuthenticationProvider 会有一个 supports 方法,用来判断 当前的 Authentication Provider 是否支持对应的 Authentication。
\qquad在一次完整的认证流程中,可能会同时存在多个 AuthenticationProvider(例如,项目同时支持 form 表单登录和短信验证码登录两套不同的认证流程),多个 AuthenticationProvider 统一由 ProviderManager 来管理。同时,ProviderManager 具有一个可选的 parent,如果所有的 AuthenticationProvider 都认证失败,那么就会调用 parent 进行认证。parent 相当于一个备用认证方式,即各个 AuthenticationProvider 都无法处理认证问题的时候,就由得交给 parent 处理。

1.2 授权

\qquad当完成认证后,接下来就是授权了。在 Spring Security 的授权体系中,有三个关键接口:

  • AccessDecisionManager
  • AccessDecisionVoter
  • ConfigAttribute

\qquadAccessDecisionVoter 是一个投票器,投票器会检查用户是否具备应有的角色,进而投出赞成、反对或者弃权票;AccessDecisionManager 则是一个决策器,它用来决定此次访问是否被允许。 ConfigAttribute则是用来保存授权时的角色信息;
\qquadAccessDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在 AccessDecisionManager 中会挨个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AccessDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager 的关系。
\qquad在 Spring Security 中,用户访问一个资源(一般是网络接口或者是 Java 方法)所需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute 方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个 ROLE_ 前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具备的角色和请求某个资源所需的ConfigAttribute 之间的关系。

2.整体结构

\qquad在 Spring Security 中,认证、授权等功能都是基于过滤器来完成的。Spring Security 里常见的过滤器大约有三十多种,注意这里说的是否默认加载是指引入 Spring Security 依赖之后,开发者不做任何配置时,就会自动加载的过滤器。比如说 WebAsyncManagerIntegrationFilter 用来将 WebAsyncManager 与 Spring Security 上下文进行集成,UsernamePasswordAuthenticationFilter 用来处理表单登录,DefaultLoginPageGeneratingFilter 用来配置默认登陆页面等等一系列过滤器。
\qquad我们所用到的 Spring Security 提供的功能,都是通过这些过滤器来实现的,这些过滤器按照既定的优先级排列,最终形成一个过滤器链。开发者也可以自定义过滤器,并通过@Order 注解去调整自定义过滤器在过滤器链中的位置。但是,默认过滤器并不是直接放在 Web 项目的原生过滤器链中,而是通过一个 FilterChainProxy 来统一管理。Spring Security 中的过滤器链通过 FilterChainProxy 嵌入到 Web 项目的原生过滤器链中,因为只有原生的Filter才具有拦截请求的功能,如图所示:

image.png

\qquad在 Spring Security 中,这样的过滤器链不仅仅只有一个,可能会有多个,如图所示。 当存在多个过滤器链时,多个过滤器链之间要指定优先级,当请求到达后,会从 FilterChainProxy 进行分发,先和哪个过滤器链匹配上,就用哪个过滤器链进行处理。当系统中存在多个不同的认证体系时,那么使用多个过滤器链就非常有效。

image.png

\qquadFilterChainProxy 作为一个顶层管理者,将统一管理 Security Filter。FilterChainProxy 本身 将通过 Spring 框架提供的 DelegatingFilterProxy 整合到原生过滤器链中,所以还可以做进一步的优化,如图所示。

image.png