链接:https://www.bilibili.com/video/BV1gU4y117qx
第一节
主要讲解了SpringSecurity的顶层脉络以及几个核心的类:
- SecurityBuilder —— 构建一个对象
- HttpSecurity —— 属于SecurityBuilder,用来构建SecurityFilterChain
- WebSecurity —— 属于SecurityBuilder,用来构建Filter(FilterChainProxy)
- SecurityFilterChain —— 维护了许多的Filter
- FilterChainProxy —— WebSecurity构建的Filter对象
SpringSecurity进入流程:
FilterChainProxy -> 根据请求决定一个 SecurityFilterChain -> 执行SecurityFilterChain中的一系列过滤器
第二节
主要讲解下面三个类:
- HttpSecurity 内幕 —— 属于SecurityBuilder,最终构建为SecurityFilterChain,维护了RequestMatcher请求匹配器来匹配请求,以及一组Filter,当请求匹配器匹配到请求后,由该组Filter处理这个请求
- SecurityConfigurer —— 用来配置SecurityBuilder,而一个SecurityBuilder中会维护一系列的SecurityConfigurer,它们再SecurityBuilder构建目标对象的过程中,完成对目标对象的配置
- AbstractConfiguredSecurityBuilder —— 维护了一堆的SecurityConfigurer配置器,sharedObjects共享对象(key为Class类型),和一个ObjectPostProcessor对象后置处理器(它除了SecurityBuilder本身可以调用这个后置处理器的后置处理方法;这个对象还会在apply(应用,即添加)具体的SecurityConfiguerr配置器时,被添加到SecurityConfigurerAdapter配置适配器中,由该配置适配器对对象作后置处理)
在实际的doBuild过程中,
(状态:默认BuildState.UNBUILT未构建)
——> beforeInit() ——> init() 回调SecurityConfigurer#init(),并且中途可以添加SecurityConfigurer,但不可二次添加 ——>
(状态:默认BuildState.CONFIGURING配置中)
——>beforeConfigure()——>configure() 回调SecurityConfigurer#configure()来往这个Builder中添加过滤器——>
(状态:默认BuildState.BUILDING构建中)
——>performBuild() 真正的build()要构建最终的目标对象了——>
(状态:默认BuildState.BUILT构建完成)
SecurityBuilder用来构建安全对象,安全对象包括:HttpSecurity、FilterChainProxy、AuthenticationManager
SecurityConfigurer用来配置安全对象构建器(SecurityBuilder),典型的有:FormLoginConfigurer、CsrfConfigurer等
第三节
主要讲解下面四个类:
- Authentication —— 认证主体对象,当发起认证请求时,传入Authentication对象(比如里面的credentials属性可以是密码组成,也可能是小程序的code码,是个抽象概念、比如里面的principal属性可以是用户名或者任何能代表当前认证请求对象的数据),交给认证管理器去对该对象做认证,如果认证通过,会返回一个填充好属性(比如授权信息、是否已认证)的认证主体对象回来
- AuthenticationManager —— 认证管理器,传入一个Authentication对象,对该认证对象认证成功后,返回一个Authentication对象,如果认证不成功,应当要抛出异常
- AuthenticationManagerBuilder —— 属于SecurityBuilder,和HttpSecurity、WebSecurity一样,都可以添加SecurityConfigurer配置器,最终是为了构建出ProviderManager对象(这个对象实现了AuthenticationManager接口)
- ProviderManager—— 它里面维护了一组的AuthenticationProvider和一个父AuthenticationManager(security中的默认实现也是一个ProviderManager),并且它是AuthenticationManagerBuilder最终要构建出来的对象
- AuthenticationProvider—— 能够判断是否支持当前的Authentication,来决定是否使用该认证提供器对当前的Authentication作认证,属性典型的策略模式,与SpringMvc的处理器适配器处理类似
Authentication存放认证信息,它需要被AuthenticationManager来认证,而AuthenticationManager管理着一堆的AuthenticationProvider
实际真正干活的就是AuthenticationProvider;ProviderManager是AuthenticationManager的主要实现。
第四节
主要讲解下面五个类:
- UserDetailsService
- 能根据用户名获取UserDetails(它里面包含用户名、密码、授权、账号禁用/锁定/过期等 属性),它本身并没有实现Authentication接口,但是它包含的东西又正是Authentication所需要的,所以它返回的UserDetails由它的调用者决定如何使用。如果根据用户名找不到用户,建议抛出UsernameNotFoundException。
- DaoAuthenticationProvider
- 作为上节中的AuthenticationProvider的一种实现,它委托给了UserDetailsService去实现返回UserDetails,而它就抽取返回的UserDetails中的授权、密码、用户名等封装成UsernamePasswordAuthenticationToken作为Authentication返回。同时这个类也是作为AbstractUserDetailsAuthenticationProvider抽象类的实现。小技巧:查看DaoAuthenticationProvider 的构造方法使用的地方(InitializeUserDetailsBeanManagerConfigurer中),同时在里面也可以看到UserDetailsService也被创建了,并设置到进去了
- AbstractUserDetailsAuthenticationProvider
- 仅支持UsernamePasswordAuthenticationToken类型的Authentication对象的认证,并且委托给子类根据用户名查询UserDetails,校验子类返回的UserDetails用户是否被锁定、禁用、过期,然后使用PasswordEncoder校验认证请求提交的密码是否正确,如果校验成功,则将此UserDetails封装成UsernamePasswordAuthenticationToken作为Authentication返回
- UsernamePasswordAuthenticationFilter
- 它继承自抽象AbstractAuthenticationProcessingFilter类,这个抽象类判断当前过滤器是否需要对当前请求进行认证,如果需要认证,则把认证的工作交给UsernamePasswordAuthenticationFilter去做,并且如果认证成功作一些工作(比如设置Authentication到SecurityContext上下文中),或者认证失败做一些工作。
- 获取认证请求提交的用户名和密码,将它们封装成UsernamePasswordAuthenticationToken对象,交给authenticationManager属性(其实就是ProviderManager)进行认证
- FormLoginConfigurer
- 用来配置UsernamePassowrdAuthenticationFilter,当HttpSecurity应用(apply)此配置器时,这个配置器在它的configure(B)方法被回调时,将会把配置好的这个过滤器添加给HttpSecurity,最终HttpSecurity构建SecurityFilterChain时,就会把这个过滤器添加到这个SecurityFilterChain中
- 在重写父类init方法时,判断sharedObjects中是否存在DefaultLoginPageGeneratingFilter过滤器,并且用户也没有设置自定义登陆页,则会开启默认的登陆页面,登录名为user,登录密码是UserDetailsServiceAutoConfiguration中日志生成的密码
用户名密码登录的真谛就是DaoAuthenticationProvider
第五节
- SecurityContext(被SecurityContextHolder持有)
- 可以设置Authentication
- 可以获取Authentication
- 具体实现是SecurityContextImpl,它持有一个Authentication
- SecurityContextHolder
- 清空 / 获取 / 设置 SecurityContext,都委托给了SecurityContextHolderStrategy
- 根据系统属性spring.security.strategy来加载具体的安全上下文Holder的策略实现,默认不设置的情况下,用的是ThreadLocalSecurityContextHolderStrategy,即:将SecurityContext绑定到当前线程
- SecurityContextHolderStrategy
- 默认使用的实现是ThreadLocalSecurityContextHolderStrategy
- SecurityContextPersistenceFilter
- 包含了一个SecurityContextRepository可以保存SecurityContext的组件
- 过滤器负责从请求信息中使用SecurityContextRepository组件,加载出SecurityContext,把它绑定到当前线程中,以便于后面的处理能直接拿到security上下文。当后续处理都完成后,重新从SecurityContextHolder获取SecurityContext保存到SecurityContextRepository中。
- SecurityContextHolderFilter
- 自5.7版本开始有的,继承了OncePerRequestFilter,保证在过滤器链中只执行一次
- 它的功能和SecurityContextPersistenceFilter类似,但是区别是需要我们自己主动保存已认证的SecurityContext到SecurityContextRepository中,保存的目的,就是为了方便下一次请求时,直接就能根据请求信息,能直接方便的拿到SecurityContext获取已认证过的用户信息
- SecurityContextRepository
- SecurityContext持久化的策略接口,用来保存 / 加载SecurityContext,security中提供了HttpSessionSecurityContextRepository,里面使用HttpSession去存储SecurityContext。
- 也可以自定义SecurityContextRepository,比如使用redis实现分布式环境下的SecurityContext共享(SecurityContext和Authentication都继承了序列化接口)
- SecurityContextConfigurer
- 使用requireExplicitSave开关,决定使用新的SecurityContextHolderFilter(须手动保存SecurityContext),还是旧的SecurityContextPersistenceFilter(这个过滤器会帮我们保存)
- 用来配置SecurityContextPersistenceFilter,默认使用HttpSessionSecurityContextRepository作为SecurityContext持久化的策略实现,获取会话配置器的sessionCreationPolicy属性决定是否强制先request.getSession()启用session会话,将此过滤器添加到httpSecurity中,httpSecurity在最终构建SecurityFilterChain时(在WebSecurity#performBuild中先触发,在HttpSecurity#performBuild中构建DefaultSecurityFilterChain),将所有配置器添加的Filter过滤器都添加到SecurityFilterChain中
- 一般来说,配置器在将filter添加到httpSecurity之前,都会调用ObjectPostProcessor#postProcess(obj)方法对filter进行自定义修改(这个可以注意一下)
第六节
-
RememberMeAuthenticationFilter
- 将请求和响应对象交给RememberMeServices(默认通过解析cookie)返回一个Authentication(默认实现是RememberMeAuthenticationToken类型的对象,因为security提供了RememberMeAuthenticationProvider专门对该类型进行认证),然后要将返回的Authentication对象交给AuthenticationManager(即ProviderManager)去做认证
-
RememberMeAuthenticationProvider
- 专门对 RememberMeAuthenticationToken类型的 Authentication进行认证,但内部仅仅校验了RememberMeAuthenticationToken的key与本身的key属性是否相同(实际记住我大部分的工作都是RememberMeServices中做的)
-
RememberMeAuthenticationToken
- 表示是通过记住我功能登录的Authentication
-
RememberMeConfigurer
- 用来向HttpSecurity中配置记住我功能的过滤器(RememberMeAuthenticationFilter)及该过滤器所使用的相关的组件(如AuthenticationManager,RememberMeServices)
-
RememberMeServices
- security提供了2个具体实现,TokenBasedRememberMeServices(用户名和密码和失效时间做md5,校验md5值是否一致) 和 PersistentTokenBasedRememberMeServices(通过将记住我的信息保存到PersistentTokenRepository用于下一次通过记住我进行认证校验),它们内部逻辑都是通过解析以及校验请求中携带的cookie,获取用户名,使用UserDetailsService加载出对应的用户,校验用户状态,封装RememberMeAuthenticationToken已认证对象。实际上在这里就已经完成了用户的记住我登录,后面的RememberMeAuthenticationProvider处理仅仅校验了个key。
-
SessionManagementConfigurer
- 用来配置会话管理,创建SessionManagementFilter,添加到HttpSecurity中
-
SessionManagementFilter
- 会话过滤器的实际使用排序是靠后的(由FilterComparator及HttpSecurity#performBuild可知),而SecurityContextPersistenceFilter靠前,它们2个用的都是从sharedObjects中获取的securityContextRepository。
- 如果securityContextRepository中还没有当前请求的SecurityContext,那么说明还没有走完SecurityContextPersistenceFilter的保存逻辑,那就说明这次请求之前的前面是还没有登录过的,如果现在到了会话过滤器,现在能从SecurityContextHolder中获取到认证对象,那就说明是刚刚才认证成功过的,那这个时候就通知SessionAuthenticationStrategy#onAuthentication做会话相关逻辑
-
SessionAuthenticationStrategy
- 策略实现有 RegisterSessionAuthenticationStrategy(当刚刚认证成功时,保存会话数据;内部的SessionRegistryImpl起作用需要配置HttpSessionEventPublisher,即它监听到session失效时,移除里面维护的会话数据)
- 策略实现有 ConcurrentSessionControlAuthenticationStrategy(当刚刚认证成功时,保存会话数据;内部的SessionRegistryImpl起作用同样需要配置HttpSessionEventPublisher,即它监听到session失效时,移除里面维护的会话数据),并且会获取当前用户所能允许的最大会话数,如果超过这个会话数量,根据配置决定是否抛出异常,或者按时间排序标记指定数量的最先创建的SessionInformation会话已失效,但是这里仅仅是作个标记,真正拦截已失效的会话是在ConcurrentSessionFilter中实现的,同时刷新会话信息的最后一次访问时间也是在这个过滤器中实现的。这样就可以实现踢人下线的功能。
- 策略实现有CompositeSessionAuthenticationStrategy,可以用来组合多个会话认证策略的实现,即组合多个功能
- 策略实现有SessionFixationProtectionStrategy,防止会话固定攻击(它通过修改会话id,内部有2种实现)
-
SessionInformation
- 会话信息,代表一个sessionId对应的会话信息对象,包含principal(用户),sessionId(会话id),lastRequest(最后一次的访问时间),expired(是否已失效)
-
SessionRegistry
- 会话注册器,提供的默认实现是SessionRegistryImpl,内部通过维护2个map(即principals [ 用户 -> 用户的所有会话id ] ,以及 sessionIds [ 会话id -> 会话信息 ]),这个组件除了在用户刚刚登录成功之后,注册新的会话,还需要监听会话销毁事件,来维护内部的这2个map。
- 如果有需要,可以自定义自己的会话注册器,比如存到redis种
-
SessionInformationExpiredStrategy
- 当会话信息失效时,提供的默认行为,默认的SimpleSessionInformationExpiredStrategy是让用户重定向到一个目标路径
RememberMe登录SpringSecurity也为我们考虑到了,同时也提供了现成的实现,可以说是拿来即用。
Session的相关管理SpringSecurity自然也不会落下,毕竟曾几何时,Session式的用户权限管理方案那是中小型企业入门最快、上手最容易的
方案
第七节
- DisabledEncodeUrlFilter
- 先使用装饰者模式,通过继承HttpServletResponseWrapper将response包装起来,然后将包装后的对象作为response走过滤器链的下面过程
- 继承的目的是重写response相关的encodeUrl相关的方法,原样返回url(取消将sessionId写到url)
- WebAsyncManagerIntegrationFilter
- 通过提前使用springmvc的WebAsyncUtils获取WebAsyncManager,来往里面注册SecurityContextCallableProcessingInterceptor拦截器,这个拦截器会在异步线程池提交任务前从SecurityContextHolder中获取SecurityContext,在callable任务执行call方法前,将提前获取的SecurityContext设置到当前线程中,保证在异步请求中也能拿到SecurityContext
-
HeaderWriterFilter
- 通过委托把将写响应头的工作交给维护的一堆HeaderWraiter去实现
-
CsrfFilter
- 默认只处理非"GET", “HEAD”, “TRACE”, "OPTIONS"的请求
- 里面使用CsrfTokenRepository保存csrfToken,默认实现是HttpSessionCsrfTokenRepository,将csrfToken存入HttpSession中
- 从请求头或者请求参数中获取到的csrfToken值与保存的csrfToken值必须相同,才会放行到下一个过滤器,来防止跨站请求伪造(就算拿了JESESSIONID,没有csrf的请求参数(_csrf)或头(X-XSRF-TOKEN)与后端不一致,也请求不了)
-
LogoutFilter
- 默认处理退出登录的路径是/logout(可以自定义),退出登录的具体逻辑委托给自身的LogoutHandler处理器(组合模式,可组合多个退出登录的处理器,比如销毁会话、清空SecurityContext、发布退出成功事件),退出成功后,由logoutSuccessHandler处理,然后直接return停止过滤器的执行。
-
RequestCacheAwareFilter
- 用户未经过认证访问某个url时,保存此请求对象,默认通过保存到HttpSession实现。
- 而当用户登录成功后,将之前保存的请求对象放入过滤器链中执行
-
SecurityContextHolderAwareRequestFilter
- 通过HttpServletRequestFactory将HttpServletRequest请求包装成SecurityContextHolderAwareRequestWrapper,它实现了HttpServletRequest,并进行了扩展,添加一些额外的方法,比如:getPrincipal()方法等。这样就可以那些需要Principal等参数的Controller就可以接收到对应参数了。除了这个地方的应用,在其他地方,也可以直接调用request#getUserPrincipal()获取对应信息。
-
AnonymousAuthenticationFilter
- 当SecurityContex中没有Authentication时,会创建出一个AnonymousAuthenticationToken的Authentication对象(principals属性默认为"anonymousUser",authorities属性默认为"ROLE_ANONYMOUS")
-
ExceptionTranslationFilter
- 位于FilterSecurityInterceptor过滤器之前,对后面filterChain.doFilter(…)进行了try-catch,并使用throwableAnalyzer分析抛出的异常,对security中的2种异常:AuthenticationException(认证异常,一般重定向到认证入口)、AccessDeniedException(访问拒绝-未完全认证或权限不够)做处理
这一整条链到底如何装配起来了,HttpSecurity到底做了什么?SpringSecurity顶层流程:
DelegatingFilterProxy -> FilterChainProxy -> SecurityFilterChain -> 具体的Filter
HttpSecurityConfiguration 配置了基础的 HttpSecurity 对象以供我们注入使用
WebSecurityConfiguration 注入了我们自己的 SecurityFilterChain Bean然后添加到 WebSecurity中
最终由 WebSecurity 构建出 FilterChainProxy 来执行SpringSecurity 的过滤逻辑
第八节
实战,构建SmsConfigurer配置短信验证码过滤器(略)
第九节
本节内容将先学习 AuthorizationFilter 这个用于授权的过滤器,与其相对应的还有 FilterSecurityInterceptor。
主要讲解如下类:
- AuthorizationFilter
- 授权过滤器,委托给授权管理器,检查用户是否有权限访问,如果没有授权结果或者授权被拒绝,将抛出访问拒绝异常
- 应用顺序内定在FilterSecurityInterceptor后面
- AuthorizationManager
- 授权管理器,检查(check)当前的用户(Authentication)是否有权限访问指定对象(request),返回AuthorizationDecision授权绝策的结果
- RequestMatcherDelegatingAuthorizationManager虽然也是AuthorizationManager,但是它里面是通过维护了一堆的(List<RequestMatcher>->AuthorizationManager)来实现接口的check方法的,它的key(RequestMatcher)用来匹配请求,当key成功匹配到请求时,它对应的value(AuthorizationManager)就用来检查是否允许授权,将结果作为返回值返回
- 具体的实现有:AuthenticationAuthorizationManager、AuthorityAuthorizationManager、Jsr250AuthorizationManager、SecuredAuthorizationManager等
- AuthorizationDecision
- 访问授权的结果,即是否授权允许访问,内部只有一个granted属性(boolean类型)。
- AuthorizeHttpRequestsConfigurer
- 用来配置AuthorizationFilter
- 创建了一个成员内部类对象AuthorizationManagerRequestMatcherRegistry,使用这个注册器创建一个RequestMatcherDelegatingAuthorizationManager(内部维护了一堆的(List<RequestMatcher>->AuthorizationManager)),这样就可以使用注册器往里面注册对应请求的授权管理器了(使用这个configurer中的add方法或者addMapping方法)
- RequestAuthorizationContext
- 用在RequestMatcherDelegatingAuthorizationManager中,回调注册的多个AuthorizationManager#check方法时,会构建此对象,用来封装request请求对象和模板路径变量。作为被check校验的对象。
- AuthorizationManagerRequestMatcherRegistry
- AuthorizeHttpRequestsConfigurer中的成员内部类,用来注册(List<RequestMatcher>->AuthorizationManager)这样的关系的注册器
- 借助AuthohrizedUrl实现链式调用,
- 该内部类的addMapping与外部类的addMapping居然打通了,用法真的是太灵活了
- AbstractRequestMatcherRegistry
- 是AuthorizationManagerRequestMatcherRegistry的父类
- 主要作用是创建List<RequestMatcher>(比如:使用anyRequest、antMatchers、regexMatchers),这些创建方法最终都会调用以模板方法的形式暴露的chainRequestMatchers方法(这个方法的返回值又是由子类去决定,而这里实现的子类AuthorizationManagerRequestMatcherRegistry返回的又是成员内部类中AuthorizedUrl,这个AuthorizedUrl由于是成员内部类对象,所以可以访问到外部类的this,也就可以往外部类对象的注册器中添加RequestMatcher->AuthorizationManager对应关系了,并且将外部类对象的注册器再次返回,来支持链式调用)
- AuthorizedUrl
- AuthorizeHttpRequestsConfigurer中的成员内部类,用来维护chainRequestMatchers方法调用所传入的List<RequestMatcher>,并且设置与之匹配的AuthorizationManager,将这样的关系添加到外部类AuthorizeHttpRequestsConfigurer对象中的AuthorizationManagerRequestMatcherRegistry注册器中。
- 这个类是连接requestMatcher和AuthorizationManager的关键的一个类
第十节
本节内容将攻克SpringSecurity中的终极怪兽,授权处理过滤器FilterSecurityInterceptor。
-
AbstractSecurityInterceptor
- 维护的属性:
- AccessDecisionManager(访问决策管理器)
- AfterInvocationManager(完成调用后管理器)
- AuthenticationManager(认证管理器)
- RunAsManager(RunAs管理器)
- 定义的方法:
- beforeInvocation:方法调用前的权限判断。由子类提供SecurityMetadataSource,获取 要访问受保护对象 所需要的 ConfigAttribute。将认证对象、受保护的对象、所须的ConfigAttribute交给授权管理器判断是否允许授权访问(授权管理器内部又委托给授权投票器投票,并根据策略处理授权),根据配置是否启用RunAsManager替换当前认证对象
- finallyInvocation:调用完成后的处理。恢复原来的认证对象(如果有设置的话)
- 维护的属性:
-
FilterSecurityInterceptor
- 维护FilterInvocationSecurityMetadataSource实例作为SecurityMetadataSource接口的实现
- doFilter调用invoke,在invoke方法中,传入组装的FilterInvocation实例
- 调用父类 beforeInvocation做授权校验,返回InterceptorStatusToken
- 调用过滤器链,执行目标方法(通过授权校验,即可调用目标方法)
- 调用父类 finallyInvocation(finally一定会执行,恢复原来的认证对象)
- afterInvocationManager对返回结果继续处理(如果有的话)
-
FilterInvocation
- 被保护的对象的类型,由AbstractSecurityInterceptor具体的子类确定类型
- 包含filterChain、request、response这3个属性
- 里面的这个filterChain并不是servlet容器里面最原始的那个filterChain,而是VirtualFilterChain(它内部持有了原始的filterChain)。VirtualFilterChain利用责任链模式应用完security配置的所有filter之后,才会调用servlet容器里面最原始的那个filterChain的doFilter方法,这时候才会把执行权返回,进而调用servlet.service方法,再走到springMvc的流程处理过程,然后沿着原来的调用链路层层返回。
-
SecurityMetadataSource
- 获取 访问受保护的对象securedObject 需要哪些configAttribute(权限)
- 有两大类
- FilterInvocationSecurityMetadataSource(受保护对象FilterInvocation)
- MethodSecurityMetadataSource(受保护对象MethodInvocation)
-
ConfigAttribute
- 简单的理解为权限,即访问某个安全对象所需要的权限
- 仅一个方法String getAttribute();
-
SecurityConfig
- ConfigAttribute的简单实现,仅维护了一个String attrib属性,代表权限
-
WebExpressionConfigAttribute
- el表达式授权封装,投票器能够根据提供的EvalueContext计算el表达式结果
-
SecurityExpressionHandler
- 能够获取计算el表达式所需要的EvaluationContext上下文,及一些有用的属性,比如BeanResolver,PermissionEvaluator
- 能够获取解析el表达式的ExpressionParser解析器
-
AbstractInterceptUrlConfigurer
- 继承自AbstractHttpConfigurer,用来创建FilterSecurityInterceptor,并设置以下等属性,添加到httpSecurity中
- 提供createMetadataSource模板方法,让子类提供FilterInvocationSecurityMetadataSource具体实现
- 默认使用AffirmativeBased类型的授权管理器,让子类提供具体的投票器
-
AbstractInterceptUrlRegistry
- AbstractInterceptUrlConfigurer中的成员内部类,用来配置外部类的属性,以及注册RequestMatcher—>Collection<ConfigAttribute>的对应关系
-
UrlAuthorizationConfigurer
- 它继承自AbstractInterceptUrlConfigurer,实现了父类获取SecurityMetadataSource(通过注册器的方法注册RequestMatcher—>Collection<ConfigAttribute>对应关系,DefaultFilterInvocationSecurityMetadataSource)、获取AccessDecisionVoter的模板方法(RoleVoter、AuthenticationVoter)
- 用来配置FilterSecurityInterceptor这个过滤器。如果要用这个配置器,需要主动应用该过滤器:http.apply(new UrlAuthorizationConfigurer())
-
ExpressionUrlAuthorizationConfigurer
- 继承自AbstractInterceptUrlConfigurer,实现基于SpEL表达式的授权配置
- 实现了父类获取SecurityMetadataSource(创建ExpressionBasedFilterInvocationSecurityMetadataSource,里面含有RequestMatcher—>Collection<WebExpressionConfigAttribute>通过注册器注册的对应关系,和SecurityExpressionHandler用于解析SpEL表达式的处理器)
- 实现了父类获取AccessDecisionVoter(WebExpressionVoter,它与SecurityMetadataSource共用同一个SecurityExpressionHandler)
- 其中使用的DefaultWebSecurityExpressionHandler,它计算SpEl表达式的值时,使用的rootObject对象是WebSecurityExpressionRoot,这里面有很多的方法和属性可以在spel中使用,还可以引用模板变量,以及自定义的PermissionEvaluator的bean也会被用来支持hasPermission表达式
-
AccessDecisionManager
- 访问决策管理器
- 内部维护了一堆访问决策投票器,具体的决策交给这些投票器 进行投票,以投票结果为依据进行决策,由具体的访问决策管理器实现。如:AffirmativeBased(有一个同意就允许访问,作为默认实现)、ConsensusBased(少数服从多数)、UnanimousBased(有一个拒绝就拒绝访问)
-
AffirmativeBased
- 访问决策管理器的一种具体策略
- 只要有任何1个AccessDecisionVoter 返回肯定的结果,就授予访问权限
-
AccessDecisionVoter
- 访问决策投票器,可以对当前Authentication是否可以访问受保护的对象securedObject所需要一堆configAttribute,进行投票,可以“同意”,“反对”,“弃权”
第十一节
从源码开始精通SpringSecurity 第十一节
本节内容将讲解SpringSecurity的实战,实现基Token的访问模式
- 基于Redis的有状态的Token
- 基于JWT的无状态Token
第十二节
从源码开始精通SpringSecurity 第十二节
本节内容将讲解SpringSecurity最后一块内容,基于方法级别的权限控制及其实现原理
- @EnableGlobalMethodSecurity
- 用于开启基于方法注解的权限控制,同时该注解已被@Configuration、@EnableGlobalAuthentication注解所配置
- 该注解引入了GlobalMethodSecuritySelector
- 默认情况下导入了GlobalMethodSecurityConfiguration配置类,
- 该注解引入了MethodSecurityMetadataSourceAdvisorRegistrar
- 向容器中注入MethodSecurityMetadataSourceAdvisor切面,因为要使用到AOP,必须要有切面(切点+增强),切点决定切入哪些方法,增强决定如何修改目标方法。
- 其中,切点的查找,切面又委托给了 MethodSecurityMetadataSource接口的具体实现,可以在GlobalMethodSecurityConfiguration#methodSecurityMetadataSource查看到支持的查找方式,比如PrePostAnnotationSecurityMetadataSource(处理@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter),SecuredAnnotationSecurityMetadataSource(处理@Secured),Jsr250MethodSecurityMetadataSource(处理@RolesAllowed、@PermitAll、@DenyAll)
- 启用了AutoProxyRegistrar注册表,这个注册表会往容器中添加AnnotationAwareAspectJAutoProxyCreator自动代理创建器,这个组件会扫描容器中所有的切面,也就是上面的切面将会被检测到,也就是切面会生效了
- 向容器中注入MethodSecurityMetadataSourceAdvisor切面,因为要使用到AOP,必须要有切面(切点+增强),切点决定切入哪些方法,增强决定如何修改目标方法。
- MethodSecurityInterceptor
- 实现了MethodInterceptor接口,也继承自AbstractSecurityInterceptor(与FilterSecurityInterceptor一样)
- 维护MethodSecurityMetadataSource实例作为SecurityMetadataSource接口的实现
- invoke触发(作为AOP中的MethodInterceptor接口的invoke方法实现)
- 调用父类 beforeInvocation做授权校验,返回InterceptorStatusToken
- 调用MethodInvocation的proceed方法,将执行权返还给调用链(只要通过前面的授权校验,即可调用目标方法)
- 调用父类 finallyInvocation(finally一定会执行,恢复原来的认证对象)
- afterInvocationManager对返回结果继续处理(如果有的话)
- 这个bean在MethodSecurityInterceptor#methodSecurityInterceptor方法中,以@Bean的方式声明了,并填充了AccessDecisionManager、AfterInvocationManager、SecurityMetadataSource、RunAsManager属性
- MethodInterceptor
- 在security中的MethodSecurityInterceptor作为Aop体系中的MethodInterceptor拦截器接口的实现,这里面是应用了责任链模式,在责任链上的每个节点(拦截器)都有权决定是否继续走下面的节点,当所有的节点都调用完了(也就是所有的节点都允许走下面的节点),才会执行目标方法,MethodSecurityInterceptor正是其中的一个节点,当它经过过权限判断,允许通过后,才能执行目标方法
- PrePostAnnotationSecurityMetadataSource
- 通过解析@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter这4个注解,获取访问被修饰的方法所需要的权限
- security使用了组合模式(DelegatingMethodSecurityMetadataSource)结合多个SecurityMetadataSource实现去解析方法上不同注解
oauth2
- WebSecurityConfigurerAdapter
- 子类
- 自定义WebSecurityConfigurerAdapter security基本应用
- AuthorizationServerSecurityConfiguration 授权服务
- ResourceServerConfiguration 资源服务
- OAuth2SsoDefaultConfiguration 单点登录
- 作用
- setApplicationContext(由@Autowierd触发)
- 设置authenticationBuilder属性(创建DefaultPasswordEncoderAuthenticationManagerBuilder)
- 设置localConfigureAuthenticationBldr属性(创建DefaultPasswordEncoderAuthenticationManagerBuilder)
- setAuthenticationConfiguration(由@Autowierd触发)
- 设置authenticationConfiguration属性
- init
- 获取HttpSecurity
- 确定 authenticationManager 是由 localConfigureAuthenticationBldr 创建,还是从 authenticationConfiguration 获取
- 这个authenticationConfiguration表示从全局获取authenticationManager,获取的逻辑是从容器中获取(即全局)
(留意下InitializeUserDetailsManagerConfigurer类里面获取UserDetailsService,表示从容器中拿,所以叫全局)
- 将得到的 authenticationManager 设置到 authenticationBuilder 的 parentAuthenticationManager 属性中
- 获取 sharedObjects
- localConfigureAuthenticationBldr.getSharedObjects()
- UserDetailsService.class —> userDetailsService()返回的UserDetailsServiceDelegator
- 优先获取localConfigureAuthenticationBldr的defaultUserDetailsService属性,如果为null,走其次
- 其次从容器中获取 AuthenticationManagerBuilder 的bean的defaultUserDetailsService属性
- 该方法可被子类重写
- ApplicationContext.class —> applicationContext上下文
- 从容器中获取的 ContentNegotiationStrategy
- 从容器中获取的 AuthenticationTrustResolver
- 创建httpSecurity
- 传入objectPostProcessor,authenticationBuilder,sharedObjects,并将 authenticationBuilder 也放入 sharedObjects
- 对httpSecurity做默认配置(可关闭)
- 给子类一个机会去继续配置httpSecurity(configure(httpSecurity))
- 补充细节: HttpSecurity#beforeConfigure()将从sharedObjects中获取AuthenticationManagerBuilder类型,
构建出AuthenticationManager,设置到sharedObjects中
- 将HttpSecurity作为SecurityBuilder(用来创建SecurityFilterChain),添加到WebSecurity的securityFilterChainBuilders属性中
- configure
- 默认空实现
- WebSecurityConfiguration(由@EnableWebSecurity触发)
- setFilterChainProxySecurityConfigurer()(由@Autowierd触发)
- 使用AutowiredWebSecurityConfigurersIgnoreParents注入容器中所有的WebSecurityConfigurer,并对它们排序
- new了一个WebSecurity,应用(即添加)这些WebSecurityConfigurer
- springSecurityFilterChain()(由@Bean触发)
- 如果容器中一个WebSecurityConfigurer都没有,就会创建一个WebSecurityConfigurer,来保证至少有一个
- 触发WebSecurity#build(),来构建FilterChainProxy
- AuthorizationServerSecurityConfiguration(继承自WebSecurityConfigurerAdapter,由@EnableAuthorizationServer触发,order是0)
- configure(AuthenticationManagerBuilder)
- disableLocalConfigureAuthenticationBldr 仍然为false,禁用从全局获取AuthenticationManager
- configure(HttpSecurity)
- 创建一个AuthorizationServerSecurityConfigurer授权服务器安全配置器对象,专门用来配置httpSecurity
- 使用注入的AuthorizationServerConfigurer授权服务器配置器,来配置上面这个AuthorizationServerSecurityConfigurer授权服务器安全配置器对象
- httpSecurity设置"/oauth/token"、"/oauth/token_key"、"/oauth/check_token"访问权限,并且requestMatchers也只匹配这3个请求
- 自动注入ClientDetailsService
- 这里注入的实质上是个代理,将会从ClientDetailsServiceConfiguration#ClientDetailsServiceConfigurer构建返回(懒加载,仅1次)
- 将clientDetailsService设置到http#sharedObjects中
- 注入容器中所有的 AuthorizationServerConfigurer授权服务器配置器(由@Autowired注入属性触发)
- 用来配置自动注入的方法参数ClientDetailsServiceConfigurer(与上面提到的自动注入ClientDetailsService相关)
- ClientDetailsServiceConfiguration(由@Import导入,并用@Autowried注入,用来配置客户端详细信息服务)
- AuthorizationServerEndpointsConfiguration (由@Import导入,并用@Autowried注入,用来配置授权服务端点)
- AuthorizationServerConfigurer(授权服务器配置器)
- 注入容器中所有的 AuthorizationServerConfigurer授权服务器配置器,用来配置 授权服务端点配置器
- TokenKeyEndpointRegistrar(使用@Import引入了该注册表)
- 该注册表用于,当容器中定义了JwtAccessTokenConverter时,向容器中注册TokenKeyEndpoint,处理"/oauth/token_key"的请求
- AuthorizationServerEndpointsConfigurer(授权服务端点配置器,包含大量的属性)
- AuthorizationServerTokenServices(资源服务器令牌服务)
- 默认创建DefaultTokenServices
- AuthorizationCodeServices(授权码服务)
- 默认创建InMemoryAuthorizationCodeServices
- ResourceServerTokenServices(资源服务器令牌服务)
- 默认创建DefaultTokenServices(与资源服务器令牌服务共用)
- TokenStore(令牌仓库)
- 默认创建InMemoryTokenStore
- AccessTokenConverter(访问令牌转换器)
- 默认创建DefaultAccessTokenConverter
- TokenEnhancer(令牌增强器)
- 默认是null
- 如果是accessTokenConverter是JwtAccessTokenConverter,那么直接将它强转
- TokenGranter(令牌授权器)
- AuthorizationCodeTokenGranter(授权码授权)
- RefreshTokenGranter(刷新令牌)
- ImplicitTokenGranter(隐式授权)
- ClientCredentialsTokenGranter(客户端授权)
- ResourceOwnerPasswordTokenGranter(密码授权,必须先配置authenticationManager才生效)
- OAuth2RequestFactory(OAuth2请求工厂)
- 默认创建DefaultOAuth2RequestFactory
- UserApprovalHandler(用户授权处理器)
- 根据配置创建ApprovalStoreUserApprovalHandler或TokenStoreUserApprovalHandler
- AuthenticationManager(认证管理器)
- 须手动配置
- ClientDetailsService(客户端详细信息服务)
- 默认创建InMemoryClientDetailsService
- UserDetailsService(用户详细信息服务)
- 须手动配置
- ClientDetailsService(由@Autowired自动注入)
- 客户端信息服务
- FrameworkEndpointHandlerMapping(由@Bean触发)
- 创建过程在 授权服务端点配置器 frameworkEndpointHandlerMapping()方法中
- AuthorizationEndpoint
- 授权端点,处理 /oauth/authorize
- 获取 授权服务端点配置器 中的属性,并设置
- 设置自动注入的ClientDetailsService
- TokenEndpoint
- 令牌端点,处理 /oauth/token
- 获取 授权服务端点配置器 中的属性,并设置
- 设置自动注入的ClientDetailsService
- CheckTokenEndpoint
- 令牌校验端点,处理 /oauth/check_token
- 获取 授权服务端点配置器 中的属性,并设置
- WhitelabelApprovalEndpoint
- 授权页端点,处理 /oauth/confirm_access
- WhitelabelErrorEndpoint
- 授权错误端点,处理 /oauth/error
- ResourceServerConfiguration(继承自WebSecurityConfigurerAdapter,由@EnableResourceServer触发,order是3)
- 注入(false) TokenStore
- 注入(false) AuthenticationEventPublisher
- 注入(false) 多个ResourceServerTokenServices 资源服务器令牌服务
- 注入(false) 多个ResourceServerConfigurer 资源服务器配置器
- 注入(false) AuthorizationServerEndpointsConfiguration
- configure(HttpSecurity)
- 创建一个 ResourceServerSecurityConfigurer 资源服务器安全配置器对象,专门用来配置httpSecurity
- 如果 注入的资源服务器令牌服务 不为null ,则将 该 资源服务器令牌服务 设置给 资源服务器安全配置器对象,
否则 将自动注入的 tokenStore设置给 资源服务器安全配置器对象
- 将注入的 AuthenticationEventPublisher 设置给 资源服务器安全配置器对象
- 默认配置 AnonymousAuthenticationProvider
- 默认配置 异常处理
- 使用 资源服务器安全配置器对象的 accessDeniedHandler
- 配置会话管理,会话创建策略为stateless
- 关闭csrf
- httpSecurity 添加 前面创建的 资源服务器安全配置器
- 定义NotOAuthRequestMatcher,排除授权服务的请求路径"/oauth/token"、"/oauth/token_key"、"/oauth/check_token"
- 将注入的多个ResourceServerConfigurer,用来配置 资源服务器安全配置器对象(重点)
- 如果ResourceServerConfigurer一个都没有,那就设置任何请求都需要认证