我以为我懂了系列之SpringSecurity(上)

544 阅读7分钟
  • 权限设计

对于一个完善的系统,使用会有很多人使用:最起码也会包含用户端和后台管理端两个角色,当然随着业务不断复杂,肯定会产生更多的角色,这些角色被赋予不同的权限,只能操作系统中的部分功能,然后所有角色一起来操作各自的模块,整个系统才能处于一个正常的活跃状态。 对于一个系统的登录方式,从账号密码登录,邮箱密码登陆,到非常方便的第三方登录方式:比如掘金的第三方登录就使用了:微信、GitHub、微博。应该是可以更加方便的引流吧,去除繁琐的注册流程。 除此之外,登录系统的设计还包含分布式系统中的单点登录SSO,使用CAS进行处理,还有jwt等等这些都可以集成在SpringSecurity中,最近连续看了好几天的Security源码(之前粗略的看过一遍,基本没理解),发现好复杂啊,好绕,功能强大势必会导致复杂的配置,所以在有些场景下使用就会觉着,但是权限设计又是每一个系统都需要的,所以决定在这里仔细缕一缕。

  • Spring Security 源码访问认证流程分析

从listener-filter-servlet中的filter说起,security的入口其实是filter的FilterChain中的一个filter。

FilterChain: is an object provided by the servlet container to the developer giving a view into the invocation chain of a filtered request for a resource.Filters use the FilterChain to invoke the next filter in the chain, or if the calling filter is the last filter in the chain, to invoke the resource at the end of the chain.

ApplicationFilterChain: Implementation of FilterChain used to manage the execution of a set of filters for a particular request. When the set of defined filters has all beenexecuted, the next call to doFilter() will execute the servlet's service() method itself.

先来看一下一般都有哪些filter,如下图所示,其中有些是我引入Actuator监控模块加入的,先不要care,下次单独说一下他是干嘛的.ApplicationFilterConfig filterConfig = filters[pos++];最后的这个[pos++]感觉挺好

其中有个filter:DelegatingFilterProxy,这是一个代理,那么代理的是谁呢?内部持有的FilterChainProxy,它做该过滤器应该做的事情,那么这个过滤器干嘛了呢?我们需要了解一个FilterChainProxy这个类(👇),这个就是我们要security的入口,然后下面我们从这个入口开始一点一点的分析。

FilterChainProxy 介绍

1.SecurityFilterChain 介绍

首先,在上面介绍中说到filterChainProxy内部的List<SecurityFilterChain> filterChains,需要从这个过滤器链的集合中匹配出当前请求的那个过滤器链,有个关键:RequestMatcher(👇),然后从将匹配到的securityFilterChain中的filter集合拿出来,便开始了进行了过滤,如下图所示

2.RequestMatcher 介绍

通过下图看下这些默认过滤器的功能,没有详细看过,后续再谈:

我们着重分析一下:UsernamePasswordAuthenticationFilter这个类就是验证的关键, 第一步:调用父抽象类的doFilter方法:

第二步:调用子类实现的attemptAuthentication方法:

第三步:认证开始: 抽象接口:AuthenticationManager,方法:authenticate(Authentication authentication) 通常使用实现ProviderManager,它持有一个List providers,这些provider提供了不同的认证方式,可以自定义AuthenticationProvider进行认证

我们来看一下这些authenticationProvider都是干嘛的

DaoAuthenticationProvider:数据访问认证方式,也就是说可以从不同的数据源来进行认证, 从上图中也可以看到父类AbstractUserDetailsAuthenticationProvider,提供入口authenticate()方法, 然后定义一些抽象方法,子类去实现具体的逻辑,又是模板模式的体现,在DaoAuthenticationProvider中有个很重要的属性:UserDetailsService,则会个接口里面只有一个方法:loadUserByUsername,然后不同的服务可以采取不同的策略来实现,对,这里我感觉就是一个策略模式,

  • Spring Security 源码访问授权流程分析

首先需要看下AbstractSecurityInterceptor这个抽象类,已经定义出在调用受保护对象的的前后的处理逻辑,主要是以下内容:

  1. beforeInvocation()
    1.1 authenticateIfRequired()
    1.2 decide()
    1.3 buildRunAs() 涉及到 RunAsManager
    1.4 InterceptorStatusToken
  2. finallyInvocation()
  3. afterInvocation() 涉及到 AfterInvocationManager 一个它管理的一个真正处理逻辑的 AfterInvocationProvider集合;这个里面的provider其实也是一个代理:delegates to a PostInvocationAuthorizationAdvice;里面还涉及到其他还不少东西,一个后置处理的逻辑
    3.1 PostInvocationAttribute: interface for attributes which are created from combined @PostFilter and @PostAuthorize annotations
    3.2 PostInvocationAuthorizationAdvice: Performs filtering and authorization logic after a method is invoked
    3.3 MethodSecurityExpressionHandler
    3.4 EvaluationContext

FilterSecurityInterceptor 定义处理的流程,流程中实现在抽象基类中.需要看下那个投票到底是干嘛的,核心:invoke()方法

beforeInvocation()主要流程:

this.accessDecisionManager.decide(authenticated, object, attributes);
//主要是基于AccessDecisionVoter的一个投票过程
//在已经确保用户通过了认证,现在基于登录的当前用户信息,和目标资源的安全配置属性
//进行相应的权限检查,如果检查失败,则抛出相应的异常 AccessDeniedException

投票的的通过策略:

  • 1.AffirmativeBased-任意1个通过授权成功 Simple concrete implementation of AccessDecisionManager that grants access if any AccessDecisionVoter returns an affirmative response.
  • 2.ConsensusBased-部分通过授权成功 "Consensus" here means majority-rule (ignoring abstains) rather than unanimous agreement (ignoring abstains).
  • 3.UnanimousBased-全部通过授权成功 Simple concrete implementation of AccessDecisionManager that requires all voters to abstain or grant access. 扩展点:
    1. securityMetadataSource 获取安全的元数据(初始化的时候将我们的配置加载进去)
    1. AccessDecisionManager
    1. AccessDecisionVoter 涉及到各种expressionHandle进行match决定投出什么样的票
  • Spring Security 初始化分析(配置如何生效,如何配置)

对于SpringBoot项目进行分析,使用SpringBoot之后最强大的功能便是自动配置,引入security的stater之后,什么都不用干,访问我们的之前的controller接口,就发现已经被保护起来,原因就是这些自动引入的以及通过它们又间接引入的配置:

org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\

说一个主要的自动配置:

然后再上图最右边开始里面开始加载webSecurity等一系列的配置; 接下来我们先来分析一下security配置涉及的组件, 我们从SecurityBuilder说起,看到这个想到是一个建造者模式,这是一个高度抽象的接口:


/**
 * Interface for building an Object
 * @param <O> The type of the Object being built
 */
public interface SecurityBuilder<O> {
	/**
	 * Builds the object and returns it or null.
	 * @return the Object to be built or null if the implementation allows it.
	 * @throws Exception if an error occurred when building the Object
	 */
	O build() throws Exception;
}

继承实现:

build的统一模板流程:

再看一下 SecurityConfigurer:

/**
 * Allows for configuring a {@link SecurityBuilder}. All {@link SecurityConfigurer} first
 * have their {@link #init(SecurityBuilder)} method invoked. After all
 * {@link #init(SecurityBuilder)} methods have been invoked, each
 * {@link #configure(SecurityBuilder)} method is invoked. 
 * @param <O> The object being built by the {@link SecurityBuilder} 
 * @param <B> The {@link SecurityBuilder} that builds objects of type O. This is also the
 * {@link SecurityBuilder} that is being configured.
 */
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	/**
	 * Initialize the {@link SecurityBuilder}. Here only shared state should be created
	 * and modified, but not properties on the {@link SecurityBuilder} used for building
	 * the object. This ensures that the {@link #configure(SecurityBuilder)} method uses
	 * the correct shared objects when building.
	 * @param builder
	 * @throws Exception
	 */
	void init(B builder) throws Exception;
	/**
	 * Configure the {@link SecurityBuilder} by setting the necessary properties on the
	 * {@link SecurityBuilder}.
	 * @param builder
	 * @throws Exception
	 */
	void configure(B builder) throws Exception;
}

继承实现:

SecurityBuilder 是用来构建O这个类型的对象的; SecurityConfigurer 是用来对不同的build过程中的属性进行配置; 这里面包含有众多的类,我们说一些重要的:

  • HttpSecurity:创建的对象类型为DefaultSecurityFilterChain
  • WebSecurity:create the FilterChainProxy known as the Spring Security Filter Chain (springSecurityFilterChain).
  • WebSecurityConfigurer:Allows customization to the {@link WebSecurity}.
  • WebSecurityConfigurerAdapter:Provides a convenient base class for creating a WebSecurityConfigurer instance.
  • DefaultSecurityFilterChain:是SecurityFilterChain的默认实现,提供两个功能:url匹配filter集合处理,在springSecurityFilterChain中可以创建一个或者多个SecurityFilterChain,并且根据SecurityFilterChain提供的requestMatcher来决定使用哪个SecurityFilterChain中的filter来处理当前请求,相当于于一个代理; httpSecurity的构建过程:

当然,还差了很多内容:如何配置,自定义拓展,cas,jwt,第三方登录等,我觉着这些东西,第一是知道一些使用规范流程,比如:cas单点登录的流程,Oauth2.0的流程,然后加上对整个security是如何进行初始化我们的配置,如何进行认证和授权的流程的熟悉,将这些内容添加进来也就变得会容易一些,当然实际操作的过程中肯定会有各种问题,一个一个解决就完事了。

参考:

www.shangyang.me/categories/… www.jianshu.com/u/fb66b7412… zhuanlan.zhihu.com/c_111502114… docs.spring.io/spring-secu…