SpringSecurity流程补充
SpringSecurity 流程补充
注意:
- 基于 spring-boot-dependencies:2.7.7
- 首先需要了解 springboot2.7 升级
Changes to Auto-configuration以后使用autoconfigure进行自动注入 - 代码地址 io.github.poo0054
启动
我们每次添加 <artifactId>spring-boot-starter-security</artifactId>,启动的时候会有一条类似的日志:
Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430
This generated password is for development use only. Your springSecurity configuration must be updated before running your
application in production.
找到 UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager 类,它是 springboot 自动装配的。
下面这些都是 springboot 自动装配类,在 spring-boot-autoconfigure-2.7.7.jar > META-INF > spring > org.springframework.boot.autoconfigure.AutoConfiguration.imports 中。这些类就是 Spring Security 的全部了。
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
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.rsocket.RSocketSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration
..........
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
SecurityAutoConfiguration
|
|
@EnableConfigurationProperties(SecurityProperties.class)
这个是 springSecurity 的核心配置类SecurityProperties,里面能配置
filter: 过滤,user : 用户信息
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
这里导入了 2 个类 SpringBootWebSecurityConfiguration和SecurityDataConfiguration,SecurityDataConfiguration是 Spring
springSecurity 与 Spring 数据的集成,暂时不做讲解,重点是SpringBootWebSecurityConfiguration
SpringBootWebSecurityConfiguration
这个类就是一个 Configuration 类,条件必须为 @ConditionalOnWebApplication(type = Type.SERVLET) 才会注入
SecurityFilterChainConfiguration
其中第一个子类SecurityFilterChainConfiguration添加了@ConditionalOnDefaultWebSecurity,这个类有个注解
@Conditional(DefaultWebSecurityCondition.class),而DefaultWebSecurityCondition类继承了AllNestedConditions
所以下面代码就是判断该类是否生效,如果不存在SecurityFilterChain和WebSecurityConfigurerAdapter
的 bean,就生效。创建默认的SecurityFilterChain
|
|
SecurityFilterChain就是整个 springsecurity 的流程了,有俩个方法,一个是boolean matches(HttpServletRequest request);
,是否匹配这次请求,匹配成功就获取当前所有Filter进行处理
SecurityFilterChain类会放在最下面单独讲解
ErrorPageSecurityFilterConfiguration
这是第二个子类,主要就是通过FilterRegistrationBean注入了一个ErrorPageSecurityFilter。 用于拦截错误调度,以确保对错误页面的授权访问。
|
|
WebSecurityEnablerConfiguration
这个类主要就是添加了@EnableWebSecurity注解,这个注解也很重要,后面跟SecurityFilterChain一起讲解
DefaultAuthenticationEventPublisher
在类中还存在SecurityAutoConfigurationbean,这个是属于 spring 的发布订阅。改装一下,就是 springSecurity 的成功和失败事件,可以订阅失败后的一些处理,如日志打印等
|
|
UserDetailsServiceAutoConfiguration
注入条件
|
|
InMemoryUserDetailsManager
UserDetailsManager 的非持久化实现,支持内存映射。 主要用于测试和演示目的,其中不需要完整的持久系统
SecurityFilterAutoConfiguration
SpringSecurity 的过滤器
自动配置。与 SpringBootWebSecurityConfiguration 分开配置,以确保在存在用户提供的 WebSecurityConfiguration 时,过滤器的顺序仍然被配置。
DelegatingFilterProxyRegistrationBean
这个类是继承了AbstractFilterRegistrationBean,FilterRegistrationBean也是继承的AbstractFilterRegistrationBean
。但是DelegatingFilterProxyRegistrationBean是使用的一个targetBeanName
找到 bean。进行注入。其中private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
也就是springSecurityFilterChain
其中有俩个属性, Order 和 DispatcherTypes
-
Order 默认为-100
-
DispatcherType就是DispatcherType类private Set<DispatcherType> dispatcherTypes = new HashSet<>( Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
注意: 这里需要了解一下DelegatingFilterProxyRegistrationBean以及 spring 如何整合 filter 和 mvc 的。 springSecurity 核心就是 filter
image.png
DelegatingFilterProxyRegistrationBean和FilterRegistrationBean都是继承的RegistrationBean,而RegistrationBean
又是ServletContextInitializer的实现类。其中void onStartup(ServletContext servletContext)方法是关键。 在javax.servlet
中,存在这样一个类
|
|
springboot 中的TomcatStarter继承了这个类,而这个类又是 spring 启动的核心AbstractApplicationContext#refresh()
中的onRefresh();方法。
找到实现类ServletWebServerApplicationContext的onRefresh()方法。里面有createWebServer();方法。
在里面有创建webServer的方法。this.webServer = factory.getWebServer(getSelfInitializer());这个就是创建tomcat的工厂。
TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)
。里面就是创建 tomcat,并启动TomcatWebServer#initialize()(这就是 springboot 不用 tamcat 的原因)
而把 filter 注入 servlet 中的是TomcatServletWebServerFactory#prepareContext(Host host, ServletContextInitializer[] initializers)
中的TomcatServletWebServerFactory#configureContext(context, initializersToUse)
方法,在里面创建了一个TomcatStarter starter = new TomcatStarter(initializers);。而TomcatStarter
继承了ServletContainerInitializer类。调用ServletContainerInitializer#onStartup(ServletContext servletContext)
时候会进入到RegistrationBean中。
然后AbstractFilterRegistrationBean#addRegistration里面添加 filter
return servletContext.addFilter(getOrDeduceName(filter), filter);这样每次请求 servlet,tomcat 就会先使用 filter 过滤器进行拦截
简单来说就是TomcatStarter继承了ServletContainerInitializer。tomcat 会调用onStartup
方法,在这个方法里面会调用ServletContextInitializer#onStartup。在这个里面有filter和其余需要整合ServletContext的方法
比如springSecurityFilterChain使用的是DelegatingFilterProxyRegistrationBean,需要使用 bean 去获取getFilter
。而ErrorPageFilter使用的是FilterRegistrationBean。直接就可以注入
@EnableWebSecurity
这个就是 springSecurity 的核心注解
需要注意,@EnableWebMvcSecurity已经弃用,请使用@EnableWebSecurity
|
|
WebSecurityConfiguration
首先先看类注释(以后都默认翻译成简体中文.ali 翻译):
使用 WebSecurity 创建执行 Spring 安全 web 安全的 FilterChainProxy。然后导出必要的 bean。 可以通过实现 WebSecurityConfigurer WebSecurityConfigurer 并将其公开为 Configuration 或公开 WebSecurityCustomizer bean 来进行自定义。 使用 EnableWebSecurity 时会导入该配置。
setFilterChainProxySecurityConfigurer
逐行解释:
this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
ObjectPostProcessor也就是AutowireBeanFactoryObjectPostProcessor。在AuthenticationConfiguration
类上@Import(ObjectPostProcessorConfiguration.class).
AutowireBeanFactoryObjectPostProcessor类里面创建webSecurity
AutowireBeanFactoryObjectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
使用AutowireCapableBeanFactory创建出WebSecurity
AutowireBeanFactoryObjectPostProcessor把SmartInitializingSingleton和DisposableBean拿出来,使用自己的destroy()
和afterSingletonsInstantiated()执行
List<SecurityConfigurer<Filter, WebSecurity» webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory).getWebSecurityConfigurers();
AutowiredWebSecurityConfigurersIgnoreParents也就是获取所有的WebSecurityConfigurerAdapter
这里有几个类需要了解SecurityConfigurer和SecurityBuilder
先了解一下结构
使用WebSecurity
聚合了private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
也就是WebSecurityConfigurerAdapter(当然还有别的,这里主要讲WebSecurityConfigurerAdapter)
WebSecurityConfigurerAdapter也可以认为就是SecurityConfigurer
WebSecurity也就是SecurityBuilder
然后在SecurityBuilder的实现类AbstractConfiguredSecurityBuilder的doBuild()方法进行很多别的操作。
|
|
回到原来地方,返回的webSecurityConfigurers,里面的
|
|
然后就到了AbstractConfiguredSecurityBuilder#apply方法,里面调用了add(configurer); 也就是把SecurityConfigurer
放入了AbstractConfiguredSecurityBuilder#configurers的一个 map 中,这样就使用SecurityBuilder聚合了SecurityConfigurer。
在构建的时候可以做一些事情
也就是说使用WebSecurity聚合了SecurityConfigurer(包括WebSecurityConfigurerAdapter)
this.securityFilterChains = securityFilterChains;
获取所有的securityFilterChains
this.webSecurityCustomizers = webSecurityCustomizers;
获取所有webSecurityCustomizers
public Filter springSecurityFilterChain()
这个里面最关键的也就是这个了。
|
|
首先使用根据获取到的securityFilterChainsset 入WebSecurity#securityFilterChainBuilders的 List 属性
这里有个需要注意的地方,如果你继承了WebSecurityConfigurerAdapter。this.securityFilterChains 就是一个空的。
而且会由WebSecurityConfigurerAdapter#getHttp()进行创建WebSecurity
。这就跟 spring 的 order 有关了。 @Order(SecurityProperties.BASIC_AUTH_ORDER)
其中SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration有一个注解@ConditionalOnDefaultWebSecurity
|
|
这里会创建SecurityFilterChain。 还会有一个HttpSecurity的注入
继续回到上面,
|
|
FilterSecurityInterceptor也在这里进行处理,也就是SecurityMetadataSource元数据
然后自定义的WebSecurityCustomizer也在这里。可以自行改变webSecurity
|
|
接下来就是构建了,来到 AbstractConfiguredSecurityBuilder#doBuild()
|
|
init(); 如果继承了
WebSecurityConfigurerAdapter,就会在这里创建HttpSecurity
注意: 这里个buildState,用来控制当前状态的
beforeConfigure();
在当前是没有什么处理
configure();
这行代码就是我们每次继承WebSecurityConfigurerAdapter的处理了
O result = performBuild();
然后就到了WebSecurity#performBuild()。
- 首先排除忽略的
RequestMatcher - 添加入
securityFilterChain和requestMatcherPrivilegeEvaluatorsEntries - 创建出
FilterChainProxybean 的名称为springSecurityFilterChain(重点)
剩下的都是一些创建一些 bean 了。
SecurityExpressionHandler: 默认为DefaultWebSecurityExpressionHandler类(Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离)
WebInvocationPrivilegeEvaluator: 为 WebSecurity#performBuild()中创建的 requestMatcherPrivilegeEvaluatorsEntries
使用RequestMatcherDelegatingWebInvocationPrivilegeEvaluator包装。(允许用户确定他们是否具有给定 web URI 的特权。)
这俩个类都是很重要的。 一个是解析器,一个是判断 uri 是否合格的类。 后面单独讲
HttpSecurityConfiguration
接下来到了HttpSecurityConfiguration
根据上面WebSecurityConfiguration,可以得出。WebSecurityConfiguration创建WebSecurity,WebSecurity
创建了FilterChainProxy的 bean。
HttpSecurityConfiguration创建HttpSecurity。 而在SecurityFilterChainConfiguration类中,使用HttpSecurity
创建了SecurityFilterChain。这也就是我们使用了WebSecurityConfigurerAdapter。为什么会存在SecurityFilterChain
类的原因。是SecurityFilterChainConfiguration#defaultSecurityFilterChain创建了一个SecurityFilterChain。
得出结论,FilterChainProxy持有SecurityFilterChain。而DelegatingFilterProxyRegistrationBean又持有FilterChainProxy
DelegatingFilterProxyRegistrationBean->FilterChainProxy->SecurityFilterChain
其实到了这一步。后面的就是我们自己编写的代码了比如
|
|
或者继承 WebSecurityConfigurerAdapter 的类了。
这里我们用 springSecurity 默认的SpringBootWebSecurityConfiguration来举例
|
|
首先获取HttpSecurity http(这里的HttpSecurity是从HttpSecurityConfiguration
里面创建的,如果是自定义的SecurityFilterChain就是从自己写的里面来的)
我们来到
HttpSecurityConfiguration#httpSecurity()先创建一个默认的密码管理器,
接下来进入authenticationBuilder.parentAuthenticationManager(authenticationManager());
,这里就是AuthenticationConfiguration
里面的处理。这个类后面和 springaop 加载我们写的注释单独在@EnableGlobalAuthentication注解类说
接着创建 HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());,
.csrf(withDefaults()) 启用 CSRF 保护,创建
CsrfConfigurer类,并添加入添加入AbstractConfiguredSecurityBuilder#configurers
.addFilter(new WebAsyncManagerIntegrationFilter())
这个有一个很有意思的类FilterOrderRegistration。 前面问的根据 filter 是如何包装顺序的。就在这个类里面
|
|
springSecurity 事先使用这个类把预加载的类全部排序好,然后每次 add 一个新的 filter 就会使用这个里面的序号。如果我们有自定义的类,也要提前加载到里面去,不然就会
|
|
WebAsyncManagerIntegrationFilter: 与处理异步 web 请求相关的实用程序方法
.exceptionHandling(withDefaults())
这个是异常的处理
提示: 这个里面每次添加一个类,如果在HttpSecurity中调用getOrApply
。比如这个代码调用的是exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));。
打开ExceptionHandlingConfigurer类,发现是一个HttpSecurityBuilder, 这样只需要看configure方法大概就能明白这个是一个什么类。
这个就是在 filter 中添加了一个ExceptionTranslationFilterfilter.主要就是SecurityConfigurer
的俩个方法。先调用init(B builder),然后configure(B builder)
后面都是一样,就跳过了
applyDefaultConfigurers(http);
这里的这一句,就是从"META-INF/spring.factories" 中加载并实例化给定类型的工厂实现
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader)
然后调用http.apply(configurer); 添加到configurers里面
接下来回到SecurityFilterChainConfiguration类
http.authorizeRequests().anyRequest().authenticated();
首先添加了http.authorizeRequests()
然后调用 return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();。
先把ExpressionUrlAuthorizationConfigurer放入 config 中,返回一个调用了getRegistry()。
也就是ExpressionInterceptUrlRegistry类。
后面调用的.anyRequest(),也就是AbstractRequestMatcherRegistry#anyRequest()。先了解一下结构图
完整调用链就是AbstractRequestMatcherRegistry#anyRequest() -> AbstractRequestMatcherRegistry#requestMatchers(RequestMatcher... requestMatchers) -> AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers(List<RequestMatcher> requestMatchers) -> ExpressionUrlAuthorizationConfigurer#chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) -> return new AuthorizedUrl(requestMatchers);
http.authorizeRequests().anyRequest().authenticated();是需要所有请求登录后才能访问
authorizeRequests是创建了一个ExpressionUrlAuthorizationConfigurer并添加入 configurer 中。anyRequest是创建了一个new AuthorizedUrl(requestMatchers),其中requestMatchers是AnyRequestMatcher.INSTANCE;也就是AnyRequestMatcher对象。里面matches(HttpServletRequest request)直接返回 tureauthenticated()也就是授权,ExpressionInterceptUrlRegistry#addMapping。里面放入了一个UrlMapping,UrlMapping的俩个属性,一个是AnyRequestMatcher(所有请求),configAttrs表示SecurityConfig。SecurityConfig的值为private static final String authenticated = "authenticated"
http.formLogin();
创建了一个FormLoginConfigurer,也就是SecurityConfigurer。关注init和configure方法。后面统一讲解
http.httpBasic();
HttpBasicConfigurer类
http.build()
进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入AbstractConfiguredSecurityBuilder#doBuild()方法
beforeInit();: 还是没有什么
init(): 调用里面所有的configurers里面的init方法,后面HttpSecurity#doBuild统一讲解,先把流程捋一遍
接下来SecurityFilterChain就已经创建好了,看一下里面的方法
|
|
肯定是先匹配,如果成功了,就返回里面所有的 filter 进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求
SecurityAutoConfiguration类就已经大致讲完了,
@EnableGlobalAuthentication
当前注解在@EnableSecurity中会自动加上
@Import(AuthenticationConfiguration.class)
AuthenticationConfiguration上面@Import(ObjectPostProcessorConfiguration.class)。 以前使用的ObjectPostProcessor
就是在这里注入的,注入AutowireBeanFactoryObjectPostProcessor对象
AuthenticationManagerBuilder
|
|
这里面返回了一个AuthenticationManagerBuilder的 bean,也就是上面``
HttpSecurityConfiguration#httpSecurity()的时候需要的类,这个类也是一个SecurityBuilder`。
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
首先创建了一个LazyPasswordEncoder,就是PasswordEncoder,用来管理密码的
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
这个就是在SecurityAutoConfiguration中创建的 springSecurity 的发布订阅,用来订阅事件
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder( objectPostProcessor, defaultPasswordEncoder);
就是AuthenticationManagerBuilder的真正实现了。接下来回到getAuthenticationManager()方法
|
|
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
获取到DefaultPasswordEncoderAuthenticationManagerBuilder
|
|
需要注意的是,this.globalAuthConfigurers就是上面三个类,
|
|
调用了apply也就是add方法。添加到configurers中
然后调用build并返回。 又是到了 doBuild() 这里
beforeInit();
没有
init();
上面三个类的init方法
EnableGlobalAuthenticationAutowiredConfigurer#initInitializeUserDetailsBeanManagerConfigurer#init调用了auth.apply(new InitializeUserDetailsManagerConfigurer());这个类比上面类名字少了一个 bean,并且没有后 init 方法 只有configure方法。 里面创建的DaoAuthenticationProvider,里面默认有一个passwordEncoder,在无参构造方法里面。而UserDetailsService和DaoAuthenticationProvider是同一个,也就是在UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager这里创建的。里面继承了,所以是同一个InitializeAuthenticationProviderBeanManagerConfigurer#init跟 2 一样,apply 了一个InitializeAuthenticationProviderManagerConfigurer
beforeConfigure();
没有
configure();
调用里面的configure方法
2: InitializeUserDetailsManagerConfigurer#configure方法
|
|
获取所有UserDetailsService和PasswordEncoder和UserDetailsPasswordService,使用DaoAuthenticationProvider
进行管理,然后添加到AuthenticationManagerBuilder#authenticationProviders中
3: InitializeAuthenticationProviderManagerConfigurer#configure方法,把 spring 中的所有AuthenticationProvider
添加到AuthenticationManagerBuilder#authenticationProviders中
然后又到了熟悉的AuthenticationManagerBuilder#performBuild
|
|
首先使用ProviderManager管理authenticationProviders和parentAuthenticationManager,这里的eraseCredentials
和CredentialsContainer类有关,也就是敏感数据。接着的eventPublisher就是发布订阅了,默认会创建的
然后providerManager = postProcess(providerManager);就是注入 spring 容器中,接着返回
,这里返回的其实是ProviderManager对象了,接着就是到了HttpSecurity的创建了,后面HttpSecurity#doBuild()
时候再讲HttpSecurity的构建
这里面的LazyPasswordEncoder这个类也很有意思,手动制造一个懒加载类
@EnableGlobalMethodSecurity
这里有个很坑的地方,里面的prePostEnabled,securedEnabled这些属性,不是直接在GlobalMethodSecuritySelector
中进行处理的,放在了GlobalMethodSecuritySelector#methodSecurityMetadataSource
这个 bean 里面进行处理,然后开启prePostEnabled之后,就会加载PrePostAnnotationSecurityMetadataSource类。 这个我找了半天,后面无意中才发现
这个注解也添加了@EnableGlobalAuthentication注解
主要是看GlobalMethodSecuritySelector类
里面加载了AutoProxyRegistrar,这个就是 springaop 的类,创建代理对象的一个类。会创建InfrastructureAdvisorAutoProxyCreator
类来创建代理对象。关键是GlobalMethodSecurityConfiguration和MethodSecurityMetadataSourceAdvisorRegistrar这俩个类
MethodSecurityMetadataSourceAdvisorRegistrar类里面都用到了GlobalMethodSecurityConfiguration。我就放在一起了
MethodSecurityMetadataSourceAdvisorRegistrar
这个类里面就是往 spring 中注册了一个MethodSecurityMetadataSourceAdvisor对象
MethodSecurityMetadataSourceAdvisor
这个类就是PointcutAdvisor,使用AbstractAutoProxyCreator创建代理对象的是,会获取Pointcut
来判断是否需要代理对象,然后使用Advice来进行其余操作。这是 springaop 的内容就不过多讲解了
aop 首先获取pointcut,进行匹配,当前的为
|
|
也就是StaticMethodMatcherPointcut,ClassFilter
默认都是 true,方法匹配为MethodSecurityMetadataSourceAdvisor#attributeSource进行匹配。而methodSecurityMetadataSource
是在GlobalMethodSecurityConfiguration#methodSecurityMetadataSource里面进行创建的。这俩个类后面讲。只要匹配成功就和 aop 一样流程了
这里的Advice就是MethodSecurityInterceptor类。在GlobalMethodSecurityConfiguration#methodSecurityInterceptor中创建
MethodSecurityInterceptor
isPrePostEnabled
添加PrePostAnnotationSecurityMetadataSource类,主要关注getAttributes方法后面会讲。
这里面就是我们常用的注解了,然后构建成ConfigAttribute并返回。里面的构建主要用的是PrePostInvocationAttributeFactory
的实现,只有一个实现
isSecuredEnabled
这个就是@Secured注解的处理。 逻辑基本和上面一样
最后返回一个DelegatingMethodSecurityMetadataSource对象,就是MethodSecurityInterceptor中用到的对象
匹配成功的 aop 都会进入MethodSecurityInterceptor#invoke
|
|
这个一看就是标准 aop
super.beforeInvocation(mi)
这个里面就有授权了,Authorization和authentication不一样,一个是认证一个是授权。这个是授权,简单说就是角色
Collection
attributes = this.obtainSecurityMetadataSource().getAttributes(object);
接着来到了DelegatingMethodSecurityMetadataSource#getAttributes
|
|
this.methodSecurityMetadataSources里面的值,就是GlobalMethodSecurityConfiguration#methodSecurityMetadataSource
里面的sources.
构建出来返回attributes。
Authentication authenticated = authenticateIfRequired();
这个就是获取当前认证信息
attemptAuthorization(object, attributes, authenticated);
使用accessDecisionManager进行授权。放到MethodInterceptor中进行讲解
里面还有授权失败发布事件publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));
接着就是授权成功发送事件,接着就是返回一个InterceptorStatusToken对象
result = mi.proceed();
执行业务
super.finallyInvocation(token);
是否刷新InterceptorStatusToken,前面传参是 false
return super.afterInvocation(token, result);
后处理器,与前处理器基本一样。剩下的MethodInterceptor中进行讲解
MethodInterceptor
创建出MethodSecurityInterceptor对象给MethodInterceptor用也就是securityMetadataSource属性
this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor(): new MethodSecurityInterceptor();
一般都是MethodSecurityInterceptor
accessDecisionManager()
这个就是AbstractSecurityInterceptor#attemptAuthorization的授权方法
|
|
前提条件必须开启prePostEnabled
这里面返回的也是AffirmativeBased,有时候我们自定义也会使用这个,只要有一个AccessDecisionVoter通过就认为是有权限的,这里就不过多讲解了
里面的GrantedAuthorityDefaults对象,也可以是我们自定义的一个前缀,默认前缀为ROLE_
我们一般自定义的,会使用.accessDecisionManager(accessDecisionManager()),在HttpSecurity#doBuild()中进行讲解
afterInvocationManager
与上面前处理一样
methodSecurityMetadataSource
这个就是MethodSecurityMetadataSource对象了
总结一下这里,就是实现 springaop 的AbstractPointcutAdvisor对象MethodSecurityMetadataSourceAdvisor, 进行 aop 加载,处理
EnableMethodSecurity
这个注解没有@EnableGlobalMethodSecurity这么强大,代码基本跟@EnableGlobalMethodSecurity一样
HttpSecurity#doBuild()
接下来是最后一块内容了,主要是看里面初始化,构建了哪些类
又是熟悉的AbstractConfiguredSecurityBuilder#doBuild()
首先看看我们一开始创建HttpSecurity的时候添加了哪些类 HttpSecurityConfiguration#httpSecurity()
|
|
CsrfConfigurer,ExceptionHandlingConfigurer,HeadersConfigurer,SessionManagementConfigurer,SecurityContextConfigurer,RequestCacheConfigurer,AnonymousConfigurer,ServletApiConfigurer,DefaultLoginPageConfigurer,LogoutConfigurer
还有我们添加到META-INF/spring.factories中的AbstractHttpConfigurer.class类
接着回到这里, 我们是自定义了一个SecurityFilterChain,所以在这里面进行构建
首先 http.formLogin();添加了FormLoginConfigurer
http.authorizeRequests() 添加了ExpressionUrlAuthorizationConfigurer,这个只有configure 没有init
http.csrf() 添加了CsrfConfigurer
http.userDetailsService(userDetailsService()) 添加了一个自定义的UserDetailsService
FormLoginConfigurer
也是一个SecurityConfigurer
首先创建对象给属性赋值
|
|
接着来到init
|
|
updateAuthenticationDefaults();
|
|
loginProcessingUrl(this.loginPage);
- 设置登录页面
this.authFilter也就是UsernamePasswordAuthenticationFilter的RequestMatcher设置为new AntPathRequestMatcher(loginProcessingUrl, "POST")
failureUrl(this.loginPage + "?error");
- 设置失败页面
this.failureHandler设置为new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl)里面的authenticationFailureUrl是/login + "?error"
getBuilder().getConfigurer(LogoutConfigurer.class); 就是前面加入的那一堆Configurer中的一个.这个默认就是当前设置的值,不用理会
updateAccessDefaults(http);
里面默认为 false
registerDefaultAuthenticationEntryPoint(http);
获取上面Configurer里面的ExceptionHandlingConfigurer
在ExceptionHandlingConfigurer中有俩个属性, defaultEntryPointMappings和defaultDeniedHandlerMappings, 基本看注释就能知道是做什么的
,这个注释是 map 的 value 类上的注释
|
|
我们这里的是添加defaultDeniedHandlerMappings, key是RequestMatcher,是否匹配。 value是匹配成功就执行
这个类里面也是只有configure(),没有init(), 后面讲解
先说里面的value,就是当前类的this.authenticationEntryPoint 也就是创建类时候的LoginUrlAuthenticationEntryPoint
key就是AndRequestMatcher,但是里面聚合了俩个RequestMatcher, 一个是MediaTypeRequestMatcher
,还有一个是NegatedRequestMatcher
其实到了这一步,我们只需要了解其中一个,剩下的都大同小异了
举例: ExceptionHandlingConfigurer
ExceptionHandlingConfigurer这个类就是刚刚在FormLoginConfigurer
中处理的那个,往这个里面添加了defaultEntryPointMappings属性
然后我们找到ExceptionHandlingConfigurer中的configure(H http)方法, 里面就是创建了一个ExceptionTranslationFilter
过滤器,添加到了http中, 代码是这一段
|
|
接着我们打开ExceptionTranslationFilter,这就是一个Filter
,找到doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
方法,就是在处理catch (Exception ex) {
的时候,做的一些事情,接着继续打开handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception)
方法,
|
|
发现最后还是到了
|
|
里面就有this.authenticationEntryPoint.commence(request, response, reason);这段代码,
创建的是DelegatingAuthenticationEntryPoint
|
|
基本上流程就完成了,请求先走过滤器,然后走不同的 filter,报错就到了这一步,进行错误处理,其余都基本一致了
备注
剩下的一些处理基本和上面这个流程一致,还有几个注解需要注意下
@CsrfToken
@CurrentSecurityContext
@AuthenticationPrincipal
这三个注解是在WebMvcSecurityConfiguration类进行处理的,只要启动了@EnableWebSecurity注解,就会启动
|
|
使用方法
|
|
里面的SpringWebMvcImportSelector类注入了WebMvcSecurityConfiguration,这就是 springmvc 中HandlerMethodArgumentResolver
的处理,也就是参数的处理,比如我们添加的@PathVariable,@RequestBody等,都是HandlerMethodArgumentResolver
的实现类处理的,当然还有HandlerMethodReturnValueHandler,这些就是DispatcherServlet里面的处理了
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek/post/code/docs/SpringSecurity/SpringSecurity%E6%B5%81%E7%A8%8B%E8%A1%A5%E5%85%85/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com