对于一个完善的系统,使用会有很多人使用:最起码也会包含用户端和后台管理端两个角色,当然随着业务不断复杂,肯定会产生更多的角色,这些角色被赋予不同的权限,只能操作系统中的某一部分功能,然后所有角色一起来操作各自的模块,整个系统才能处于一个正常的活跃状态。 对于一个系统的登录方式,从账号密码登录,邮箱密码登陆,到非常方便的第三方登录方式:比如掘金的第三方登录就使用了:微信、GitHub、微博。应该是可以更加方便的引流吧,去除繁琐的注册流程。 除此之外,登录系统的设计还包含在一个分布式系统中的单点登录SSO,使用CAS进行处理,还有jwt等等这些都可以集成在SpringSecurity中,最近连续看了好几天的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,下次单独说一下他是干嘛的
其中有个filter:DelegatingFilterProxy,这是一个代理,那么代理的是谁呢?内部持有的FilterChainProxy,它做该过滤器应该做的事情,那么这个过滤器干嘛了呢?我们需要了解一个FilterChainProxy这个类(:point_down:),这个就是我们要security的入口,然后下面我们从这个入口开始一点一点的分析。
FilterChainProxy 介绍
SecurityFilterChain 介绍
首先,在上面介绍中说到filterChainProxy内部的 List<SecurityFilterChain> filterChains
,需要从这个过滤器链的集合中匹配出当前请求的那个过滤器链,有个关键:RequestMatcher(:point_down:),然后从将匹配到的securityFilterChain中的filter集合拿出来,便开始了进行了过滤,如下图所示
RequestMatcher 介绍
通过下图看下这些默认过滤器的功能,没有详细看过,后续再谈:
我们着重分析一下:UsernamePasswordAuthenticationFilter
这个类就是验证的关键, 第一步:调用父抽象类的doFilter方法:
第二步:调用子类实现的attemptAuthentication方法:
第三步:认证开始: 抽象接口:AuthenticationManager,方法:authenticate(Authentication authentication) 通常使用实现ProviderManager
,它持有一个List providers,这些provider提供了不同的认证方式,可以自定义 AuthenticationProvider
进行认证
我们来看一下这些authenticationProvider都是干嘛的
DaoAuthenticationProvider
:数据访问认证方式,也就是说可以从不同的数据源来进行认证,
从上图中也可以看到父类 AbstractUserDetailsAuthenticationProvider
,提供入口authenticate()方法,
然后定义一些抽象方法,子类去实现具体的逻辑,又是模板模式的体现,在 DaoAuthenticationProvider
中有个很重要的属性: UserDetailsService
,则会个接口里面只有一个方法: loadUserByUsername
,然后不同的服务可以采取不同的策略来实现,对,这里我感觉就是一个策略模式,
FilterSecurityInterceptor 感觉也很绕,需要看下那个投票到底是干嘛的, 核心:invoke()方法
beforeInvocation()主要流程:
this.accessDecisionManager.decide(authenticated, object, attributes); //主要是基于AccessDecisionVoter的一个投票过程 //在已经确保用户通过了认证,现在基于登录的当前用户信息,和目标资源的安全配置属性 //进行相应的权限检查,如果检查失败,则抛出相应的异常 AccessDeniedException 复制代码
投票的的通过策略:
对于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过程中的属性进行配置; 这里面包含有众多的类,我们说一些重要的:
当然,还差了很多内容:如何配置,自定义拓展,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…