From ec936747c39834adf229872fa564e5a7f6d6f12c Mon Sep 17 00:00:00 2001 From: poo0054 Date: Thu, 8 Jun 2023 14:59:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=8F=8A=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7=E5=92=8CspringSecurity=E5=90=8D=E7=A7=B0=E5=AE=8C?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpringSecurity流程补充.md | 1191 +++++++++-------- 1 file changed, 640 insertions(+), 551 deletions(-) diff --git a/docs/SpringSecurity/SpringSecurity流程补充.md b/docs/SpringSecurity/SpringSecurity流程补充.md index 4744f7b..849b943 100644 --- a/docs/SpringSecurity/SpringSecurity流程补充.md +++ b/docs/SpringSecurity/SpringSecurity流程补充.md @@ -1,24 +1,27 @@ -# springSecurity流程补充 +# springSecurity 流程补充 注意: -1. 基于spring-boot-dependencies:2.7.7 -2. 首先需要了解[springboot2.7升级](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes) +1. 基于 spring-boot-dependencies:2.7.7 +2. 首先需要了解[springboot2.7 升级](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes) `Changes to Auto-configuration` 以后使用`autoconfigure`进行自动注入 -3. 代码地址[io.github.poo0054](https://github.com/poo0054/security-study/blob/master/starter/src/main/java/io/github/poo0054/security/StarterApplication.java) +3. + +代码地址[io.github.poo0054](https://github.com/poo0054/security-study/blob/master/starter/src/main/java/io/github/poo0054/security/StarterApplication.java) + ## 启动 我们每次添加` spring-boot-starter-security` -,启动的时候启动日志会有一条类似 -`Using generated security password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430 +,启动的时候启动日志会有一条类似 +`Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430 -This generated password is for development use only. Your security configuration must be updated before running your +This generated password is for development use only. Your springSecurity configuration must be updated before running your application in production.` -的日志.找到`UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager`类,它是springboot自动装配的. +的日志。找到`UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager`类,它是 springboot 自动装配的。 -下面这些都是springboot自动装配类,在`spring-boot-autoconfigure-2.7.7.jar`>META-INF>spring> -org.springframework.boot.autoconfigure.AutoConfiguration.imports中. 这些类就是security的全部了. +下面这些都是 springboot 自动装配类,在`spring-boot-autoconfigure-2.7.7.jar`>META-INF>spring> +org.springframework.boot.autoconfigure.AutoConfiguration.imports 中. 这些类就是 springSecurity 的全部了. ```imports org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration @@ -64,27 +67,27 @@ public class SecurityAutoConfiguration { ### @EnableConfigurationProperties(SecurityProperties.class) -这个是security的核心配置类`SecurityProperties`,里面能配置 -`filter`: 过滤,`user` : 用户信息 +这个是 springSecurity 的核心配置类`SecurityProperties`,里面能配置 +`filter`: 过滤,`user` : 用户信息 -`这个有个问题,filter是属于tomcat的,security中使用什么方式让filter变的有序的` +`这个有个问题,filter是属于tomcat的,springSecurity 中使用什么方式让filter变的有序的` ### @Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class }) -这里导入了2个类 `SpringBootWebSecurityConfiguration`和`SecurityDataConfiguration`,`SecurityDataConfiguration`是Spring -Security与Spring数据的集成,暂时不做讲解,重点是`SpringBootWebSecurityConfiguration` +这里导入了 2 个类 `SpringBootWebSecurityConfiguration`和`SecurityDataConfiguration`,`SecurityDataConfiguration`是 Spring +springSecurity 与 Spring 数据的集成,暂时不做讲解,重点是`SpringBootWebSecurityConfiguration` #### SpringBootWebSecurityConfiguration -这个类就是一个`Configuration`类,条件必须为`@ConditionalOnWebApplication(type = Type.SERVLET)`才会注入 +这个类就是一个`Configuration`类,条件必须为`@ConditionalOnWebApplication(type = Type.SERVLET)`才会注入 ##### SecurityFilterChainConfiguration -其中第一个子类`SecurityFilterChainConfiguration`添加了`@ConditionalOnDefaultWebSecurity`,这个类有个注解 -`@Conditional(DefaultWebSecurityCondition.class)`,而`DefaultWebSecurityCondition`类继承了`AllNestedConditions` +其中第一个子类`SecurityFilterChainConfiguration`添加了`@ConditionalOnDefaultWebSecurity`,这个类有个注解 +`@Conditional(DefaultWebSecurityCondition.class)`,而`DefaultWebSecurityCondition`类继承了`AllNestedConditions` -所以下面代码就是判断该类是否生效,如果不存在`SecurityFilterChain`和`WebSecurityConfigurerAdapter` -的bean,就生效.创建默认的`SecurityFilterChain` +所以下面代码就是判断该类是否生效,如果不存在`SecurityFilterChain`和`WebSecurityConfigurerAdapter` +的 bean,就生效。创建默认的`SecurityFilterChain` ```java /** @@ -115,42 +118,42 @@ class DefaultWebSecurityCondition extends AllNestedConditions { } ``` -`SecurityFilterChain`就是整个springsecurity的流程了,有俩个方法,一个是`boolean matches(HttpServletRequest request);` -,是否匹配这次请求,匹配成功就获取当前所有`Filter`进行处理 +`SecurityFilterChain`就是整个 springsecurity 的流程了,有俩个方法,一个是`boolean matches(HttpServletRequest request);` +,是否匹配这次请求,匹配成功就获取当前所有`Filter`进行处理 `SecurityFilterChain`类会放在最下面单独讲解 ##### ErrorPageSecurityFilterConfiguration -这是第二个子类,主要就是通过`FilterRegistrationBean`注入了一个`ErrorPageSecurityFilter`. 用于拦截错误调度,以确保对错误页面的授权访问。 +这是第二个子类,主要就是通过`FilterRegistrationBean`注入了一个`ErrorPageSecurityFilter`。 用于拦截错误调度,以确保对错误页面的授权访问。 - ```java - /** - * Configures the {@link ErrorPageSecurityFilter}. - */ +```java + /** +* Configures the {@link ErrorPageSecurityFilter} +*/ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebInvocationPrivilegeEvaluator.class) @ConditionalOnBean(WebInvocationPrivilegeEvaluator.class) static class ErrorPageSecurityFilterConfiguration { - @Bean - FilterRegistrationBean errorPageSecurityFilter(ApplicationContext context) { - FilterRegistrationBean registration = new FilterRegistrationBean<>( - new ErrorPageSecurityFilter(context)); - registration.setDispatcherTypes(DispatcherType.ERROR); - return registration; - } + @Bean + FilterRegistrationBean errorPageSecurityFilter(ApplicationContext context) { + FilterRegistrationBean registration = new FilterRegistrationBean<>( + new ErrorPageSecurityFilter(context)); + registration.setDispatcherTypes(DispatcherType.ERROR); + return registration; + } } - ``` +``` ##### WebSecurityEnablerConfiguration -这个类主要就是添加了`@EnableWebSecurity`注解,这个注解也很重要,后面跟`SecurityFilterChain`一起讲解 +这个类主要就是添加了`@EnableWebSecurity`注解,这个注解也很重要,后面跟`SecurityFilterChain`一起讲解 ### DefaultAuthenticationEventPublisher -在类中还存在`SecurityAutoConfiguration`bean,这个是属于spring的发布订阅.改装一下,就是security的成功和失败事件,可以订阅失败后的一些处理,如日志打印等 +在类中还存在`SecurityAutoConfiguration`bean,这个是属于 spring 的发布订阅。改装一下,就是 springSecurity 的成功和失败事件,可以订阅失败后的一些处理,如日志打印等 ```java /** @@ -180,40 +183,40 @@ public interface AuthenticationEventPublisher { "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"}) - ``` +``` ### InMemoryUserDetailsManager -UserDetailsManager的非持久化实现,支持内存映射。 +UserDetailsManager 的非持久化实现,支持内存映射。 主要用于测试和演示目的,其中不需要完整的持久系统 ## SecurityFilterAutoConfiguration -Spring Security的过滤器 -自动配置。与SpringBootWebSecurityConfiguration分开配置,以确保在存在用户提供的WebSecurityConfiguration时,过滤器的顺序仍然被配置。 +SpringSecurity 的过滤器 +自动配置。与 SpringBootWebSecurityConfiguration 分开配置,以确保在存在用户提供的 WebSecurityConfiguration 时,过滤器的顺序仍然被配置。 ### DelegatingFilterProxyRegistrationBean -这个类是继承了`AbstractFilterRegistrationBean`,`FilterRegistrationBean`也是继承的`AbstractFilterRegistrationBean` -.但是`DelegatingFilterProxyRegistrationBean`是使用的一个`targetBeanName` -找到bean.进行注入.其中`private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;` +这个类是继承了`AbstractFilterRegistrationBean`,`FilterRegistrationBean`也是继承的`AbstractFilterRegistrationBean` +。但是`DelegatingFilterProxyRegistrationBean`是使用的一个`targetBeanName` +找到 bean。进行注入。其中`private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;` 也就是`springSecurityFilterChain` -其中有俩个属性, Order和DispatcherTypes +其中有俩个属性, Order 和 DispatcherTypes -* Order默认为-100 +- Order 默认为-100 -* `DispatcherType`就是`DispatcherType`类 +- `DispatcherType`就是`DispatcherType`类 `private Set dispatcherTypes = new HashSet<>( - Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));` +Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));` -注意: 这里需要了解一下`DelegatingFilterProxyRegistrationBean`以及spring如何整合filter和mvc的.security核心就是filter +注意: 这里需要了解一下`DelegatingFilterProxyRegistrationBean`以及 spring 如何整合 filter 和 mvc 的。 springSecurity 核心就是 filter ![img.png](../../images/SpringSecurity/img-2023-6-7_0.png)image.png -`DelegatingFilterProxyRegistrationBean`和`FilterRegistrationBean`都是继承的`RegistrationBean`,而`RegistrationBean` -又是`ServletContextInitializer`的实现类.其中`void onStartup(ServletContext servletContext)`方法是关键. 在`javax.servlet` -中,存在这样一个类 +`DelegatingFilterProxyRegistrationBean`和`FilterRegistrationBean`都是继承的`RegistrationBean`,而`RegistrationBean` +又是`ServletContextInitializer`的实现类。其中`void onStartup(ServletContext servletContext)`方法是关键。 在`javax.servlet` +中,存在这样一个类 ```java public interface ServletContainerInitializer { @@ -233,32 +236,32 @@ public interface ServletContainerInitializer { void onStartup(Set> c, ServletContext ctx) throws ServletException; ``` -springboot中的`TomcatStarter`继承了这个类,而这个类又是spring启动的核心`AbstractApplicationContext#refresh()` -中的`onRefresh();`方法. +springboot 中的`TomcatStarter`继承了这个类,而这个类又是 spring 启动的核心`AbstractApplicationContext#refresh()` +中的`onRefresh();`方法。 -找到实现类`ServletWebServerApplicationContext`的`onRefresh()`方法.里面有`createWebServer();`方法. -在里面有创建`webServer`的方法.`this.webServer = factory.getWebServer(getSelfInitializer());`这个就是创建`tomcat`的工厂. +找到实现类`ServletWebServerApplicationContext`的`onRefresh()`方法。里面有`createWebServer();`方法。 +在里面有创建`webServer`的方法。`this.webServer = factory.getWebServer(getSelfInitializer());`这个就是创建`tomcat`的工厂。 `TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)` -.里面就是创建tomcat,并启动`TomcatWebServer#initialize()`(这就是springboot不用tamcat的原因) +。里面就是创建 tomcat,并启动`TomcatWebServer#initialize()`(这就是 springboot 不用 tamcat 的原因) -而把filter注入servlet中的是`TomcatServletWebServerFactory#prepareContext(Host host, ServletContextInitializer[] initializers)` +而把 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 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`的方法 +简单来说就是`TomcatStarter`继承了`ServletContainerInitializer`。tomcat 会调用`onStartup` +方法,在这个方法里面会调用`ServletContextInitializer#onStartup`。在这个里面有`filter`和其余需要整合`ServletContext`的方法 -比如`springSecurityFilterChain`使用的是`DelegatingFilterProxyRegistrationBean`,需要使用bean去获取`getFilter` -.而`ErrorPageFilter`使用的是`FilterRegistrationBean`.直接就可以注入 +比如`springSecurityFilterChain`使用的是`DelegatingFilterProxyRegistrationBean`,需要使用 bean 去获取`getFilter` +。而`ErrorPageFilter`使用的是`FilterRegistrationBean`。直接就可以注入 ## @EnableWebSecurity -这个就是security的核心注解 -需要注意,`@EnableWebMvcSecurity`已经弃用,请使用`@EnableWebSecurity` +这个就是 springSecurity 的核心注解 +需要注意,`@EnableWebMvcSecurity`已经弃用,请使用`@EnableWebSecurity` ```java @@ -282,24 +285,25 @@ public @interface EnableWebSecurity { ### WebSecurityConfiguration -首先先看类注释(以后都默认翻译成简体中文.ali翻译): +首先先看类注释(以后都默认翻译成简体中文.ali 翻译): -使用WebSecurity创建执行Spring安全web安全的FilterChainProxy。然后导出必要的bean。 -可以通过实现WebSecurityConfigurer WebSecurityConfigurer并将其公开为Configuration或公开WebSecurityCustomizer bean来进行自定义。 -使用EnableWebSecurity时会导入该配置。 +使用 WebSecurity 创建执行 Spring 安全 web 安全的 FilterChainProxy。然后导出必要的 bean。 +可以通过实现 WebSecurityConfigurer WebSecurityConfigurer 并将其公开为 Configuration 或公开 WebSecurityCustomizer bean 来进行自定义。 +使用 EnableWebSecurity 时会导入该配置。 #### setFilterChainProxySecurityConfigurer 逐行解释: + > this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor)); -`ObjectPostProcessor`也就是`AutowireBeanFactoryObjectPostProcessor`.在`AuthenticationConfiguration` +`ObjectPostProcessor`也就是`AutowireBeanFactoryObjectPostProcessor`。在`AuthenticationConfiguration` 类上`@Import(ObjectPostProcessorConfiguration.class)`. `AutowireBeanFactoryObjectPostProcessor`类里面创建`webSecurity` `AutowireBeanFactoryObjectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));` 使用`AutowireCapableBeanFactory`创建出`WebSecurity` -`AutowireBeanFactoryObjectPostProcessor`把`SmartInitializingSingleton`和`DisposableBean`拿出来,使用自己的`destroy()` +`AutowireBeanFactoryObjectPostProcessor`把`SmartInitializingSingleton`和`DisposableBean`拿出来,使用自己的`destroy()` 和`afterSingletonsInstantiated()`执行 > List> webSecurityConfigurers = new @@ -312,42 +316,46 @@ public @interface EnableWebSecurity { 先了解一下结构 ![img_1.png](../../images/SpringSecurity/img-2023-6-7_1.png) - - ![img_2.png](../../images/SpringSecurity/img-2023-6-7-_2.png) - -使用`WebSecurity`聚合了`private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();` 也就是`WebSecurityConfigurerAdapter`(当然还有别的,这里主要讲`WebSecurityConfigurerAdapter`) +使用`WebSecurity` +聚合了`private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();` +也就是`WebSecurityConfigurerAdapter`(当然还有别的,这里主要讲`WebSecurityConfigurerAdapter`) `WebSecurityConfigurerAdapter`也可以认为就是`SecurityConfigurer` `WebSecurity`也就是`SecurityBuilder` -然后在`SecurityBuilder`的实现类`AbstractConfiguredSecurityBuilder`的`doBuild()`方法进行很多别的操作. +然后在`SecurityBuilder`的实现类`AbstractConfiguredSecurityBuilder`的`doBuild()`方法进行很多别的操作。 + ```java -protected final O doBuild() throws Exception { - synchronized (this.configurers) { - this.buildState = BuildState.INITIALIZING; - beforeInit(); - init(); - this.buildState = BuildState.CONFIGURING; - beforeConfigure(); - configure(); - this.buildState = BuildState.BUILDING; - O result = performBuild(); - this.buildState = BuildState.BUILT; - return result; - } - } +protected final O doBuild()throws Exception{ +synchronized (this.configurers){ + this.buildState=BuildState.INITIALIZING; + beforeInit(); + init(); + this.buildState=BuildState.CONFIGURING; + beforeConfigure(); + configure(); + this.buildState=BuildState.BUILDING; + O result=performBuild(); + this.buildState=BuildState.BUILT; + return result; + } + } ``` -回到原来地方,返回的`webSecurityConfigurers`,里面的 +回到原来地方,返回的`webSecurityConfigurers`,里面的 + ```java -for (SecurityConfigurer webSecurityConfigurer : webSecurityConfigurers) { - this.webSecurity.apply(webSecurityConfigurer); - } +for(SecurityConfigurer webSecurityConfigurer:webSecurityConfigurers){ + this.webSecurity.apply(webSecurityConfigurer); + } ``` -然后就到了`AbstractConfiguredSecurityBuilder#apply`方法,里面调用了`add(configurer);` 也就是把`SecurityConfigurer`放入了`AbstractConfiguredSecurityBuilder#configurers`的一个map中,这样就使用`SecurityBuilder`聚合了`SecurityConfigurer`. 在构建的时候可以做一些事情 + +然后就到了`AbstractConfiguredSecurityBuilder#apply`方法,里面调用了`add(configurer);` 也就是把`SecurityConfigurer` +放入了`AbstractConfiguredSecurityBuilder#configurers`的一个 map 中,这样就使用`SecurityBuilder`聚合了`SecurityConfigurer`。 +在构建的时候可以做一些事情 也就是说使用`WebSecurity`聚合了`SecurityConfigurer`(包括`WebSecurityConfigurerAdapter`) @@ -358,88 +366,98 @@ for (SecurityConfigurer webSecurityConfigurer : webSecurity > this.webSecurityCustomizers = webSecurityCustomizers; 获取所有`webSecurityCustomizers` - -#### public Filter springSecurityFilterChain() -这个里面最关键的也就是这个了. +#### public Filter springSecurityFilterChain() + +这个里面最关键的也就是这个了。 + ```java - for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { - this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain); - for (Filter filter : securityFilterChain.getFilters()) { - if (filter instanceof FilterSecurityInterceptor) { - this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); - break; - } - } - } - for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { - customizer.customize(this.webSecurity); - } - return this.webSecurity.build(); + for(SecurityFilterChain securityFilterChain:this.securityFilterChains){ + this.webSecurity.addSecurityFilterChainBuilder(()->securityFilterChain); + for(Filter filter:securityFilterChain.getFilters()){ + if(filter instanceof FilterSecurityInterceptor){ + this.webSecurity.securityInterceptor((FilterSecurityInterceptor)filter); + break; + } + } + } + for(WebSecurityCustomizer customizer:this.webSecurityCustomizers){ + customizer.customize(this.webSecurity); + } + return this.webSecurity.build(); ``` -首先使用根据获取到的`securityFilterChains`set入`WebSecurity#securityFilterChainBuilders`的List属性 +首先使用根据获取到的`securityFilterChains`set 入`WebSecurity#securityFilterChainBuilders`的 List 属性 + +这里有个需要注意的地方,如果你继承了`WebSecurityConfigurerAdapter`。`this.securityFilterChains` 就是一个空的。 -这里有个需要注意的地方,如果你继承了`WebSecurityConfigurerAdapter`.`this.securityFilterChains` 就是一个空的. +而且会由`WebSecurityConfigurerAdapter#getHttp()`进行创建`WebSecurity` +。这就跟 spring 的 order 有关了。 `@Order(SecurityProperties.BASIC_AUTH_ORDER)` -而且会由`WebSecurityConfigurerAdapter#getHttp()`进行创建`WebSecurity`.这就跟spring的order有关了. `@Order(SecurityProperties.BASIC_AUTH_ORDER)` +其中`SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration`有一个注解`@ConditionalOnDefaultWebSecurity` -其中`SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration`有一个注解`@ConditionalOnDefaultWebSecurity` ```java + @Configuration(proxyBeanMethods = false) - @ConditionalOnDefaultWebSecurity - static class SecurityFilterChainConfiguration { - - @Bean - @Order(SecurityProperties.BASIC_AUTH_ORDER) - SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); - http.formLogin(); - http.httpBasic(); - return http.build(); - } - - } +@ConditionalOnDefaultWebSecurity +static class SecurityFilterChainConfiguration { + + @Bean + @Order(SecurityProperties.BASIC_AUTH_ORDER) + SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().authenticated(); + http.formLogin(); + http.httpBasic(); + return http.build(); + } + +} ``` -这里会创建`SecurityFilterChain`. 还会有一个`HttpSecurity`的注入 -继续回到上面, +这里会创建`SecurityFilterChain`。 还会有一个`HttpSecurity`的注入 + +继续回到上面, + ```java -if (filter instanceof FilterSecurityInterceptor) { - this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); - break; - } +if(filter instanceof FilterSecurityInterceptor){ + this.webSecurity.securityInterceptor((FilterSecurityInterceptor)filter); + break; + } ``` -`FilterSecurityInterceptor`也在这里进行处理,也就是`SecurityMetadataSource`元数据 -然后自定义的`WebSecurityCustomizer`也在这里.可以自行改变`webSecurity` -```java -for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { - customizer.customize(this.webSecurity); - } +`FilterSecurityInterceptor`也在这里进行处理,也就是`SecurityMetadataSource`元数据 + +然后自定义的`WebSecurityCustomizer`也在这里。可以自行改变`webSecurity` + +```java +for(WebSecurityCustomizer customizer:this.webSecurityCustomizers){ + customizer.customize(this.webSecurity); + } ``` -接下来就是构建了,来到`AbstractConfiguredSecurityBuilder#doBuild()` +接下来就是构建了,来到`AbstractConfiguredSecurityBuilder#doBuild()` + ```java -protected final O doBuild() throws Exception { - synchronized (this.configurers) { - this.buildState = BuildState.INITIALIZING; - beforeInit(); - init(); - this.buildState = BuildState.CONFIGURING; - beforeConfigure(); - configure(); - this.buildState = BuildState.BUILDING; - O result = performBuild(); - this.buildState = BuildState.BUILT; - return result; - } - } +protected final O doBuild()throws Exception{ +synchronized (this.configurers){ + this.buildState=BuildState.INITIALIZING; + beforeInit(); + init(); + this.buildState=BuildState.CONFIGURING; + beforeConfigure(); + configure(); + this.buildState=BuildState.BUILDING; + O result=performBuild(); + this.buildState=BuildState.BUILT; + return result; + } + } ``` + > init(); -如果继承了`WebSecurityConfigurerAdapter`,就会在这里创建`HttpSecurity` +> 如果继承了`WebSecurityConfigurerAdapter`,就会在这里创建`HttpSecurity` -注意: 这里个`buildState`,用来控制当前状态的 +注意: 这里个`buildState`,用来控制当前状态的 > beforeConfigure(); @@ -451,130 +469,148 @@ protected final O doBuild() throws Exception { > O result = performBuild(); -然后就到了`WebSecurity#performBuild()`. +然后就到了`WebSecurity#performBuild()`。 1. 首先排除忽略的`RequestMatcher` 2. 添加入`securityFilterChain` 和`requestMatcherPrivilegeEvaluatorsEntries` -3. 创建出`FilterChainProxy`bean的名称为`springSecurityFilterChain` (重点) +3. 创建出`FilterChainProxy`bean 的名称为`springSecurityFilterChain` (重点) -剩下的都是一些创建一些bean了. +剩下的都是一些创建一些 bean 了。 -`SecurityExpressionHandler`: 默认为`DefaultWebSecurityExpressionHandler`类(Facade将Spring Security评估安全表达式的要求与基础表达式对象的实现隔离) - -`WebInvocationPrivilegeEvaluator`: 为 `WebSecurity#performBuild()`中创建的 `requestMatcherPrivilegeEvaluatorsEntries` 使用`RequestMatcherDelegatingWebInvocationPrivilegeEvaluator`包装.(允许用户确定他们是否具有给定web URI的特权。) +`SecurityExpressionHandler`: 默认为`DefaultWebSecurityExpressionHandler`类(Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离) -这俩个类都是很重要的. 一个是解析器,一个是判断uri是否合格的类. 后面单独讲 +`WebInvocationPrivilegeEvaluator`: 为 `WebSecurity#performBuild()`中创建的 `requestMatcherPrivilegeEvaluatorsEntries` +使用`RequestMatcherDelegatingWebInvocationPrivilegeEvaluator`包装。(允许用户确定他们是否具有给定 web URI 的特权。) +这俩个类都是很重要的。 一个是解析器,一个是判断 uri 是否合格的类。 后面单独讲 ### HttpSecurityConfiguration -接下来到了`HttpSecurityConfiguration` -根据上面`WebSecurityConfiguration`,可以得出.`WebSecurityConfiguration`创建`WebSecurity`,`WebSecurity`创建了`FilterChainProxy`的bean. +接下来到了`HttpSecurityConfiguration` + +根据上面`WebSecurityConfiguration`,可以得出。`WebSecurityConfiguration`创建`WebSecurity`,`WebSecurity` +创建了`FilterChainProxy`的 bean。 -`HttpSecurityConfiguration`创建`HttpSecurity`. 而在`SecurityFilterChainConfiguration`类中,使用`HttpSecurity`创建了`SecurityFilterChain`.这也就是我们使用了`WebSecurityConfigurerAdapter`.为什么会存在`SecurityFilterChain`类的原因.是`SecurityFilterChainConfiguration#defaultSecurityFilterChain`创建了一个`SecurityFilterChain`. +`HttpSecurityConfiguration`创建`HttpSecurity`。 而在`SecurityFilterChainConfiguration`类中,使用`HttpSecurity` +创建了`SecurityFilterChain`。这也就是我们使用了`WebSecurityConfigurerAdapter`。为什么会存在`SecurityFilterChain` +类的原因。是`SecurityFilterChainConfiguration#defaultSecurityFilterChain`创建了一个`SecurityFilterChain`。 -得出结论,`FilterChainProxy`持有`SecurityFilterChain`.而`DelegatingFilterProxyRegistrationBean`又持有`FilterChainProxy` +得出结论,`FilterChainProxy`持有`SecurityFilterChain`。而`DelegatingFilterProxyRegistrationBean`又持有`FilterChainProxy` `DelegatingFilterProxyRegistrationBean`->`FilterChainProxy`->`SecurityFilterChain` +其实到了这一步。后面的就是我们自己编写的代码了比如 -其实到了这一步.后面的就是我们自己编写的代码了比如 ```java @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { +public SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception{ // 配置认证 http.formLogin(); // 设置URL的授权问题 // 多个条件取交集 http.authorizeRequests() - // 匹配 / 控制器 permitAll() 不需要被认证就可以访问 - .antMatchers("/login").permitAll() - .antMatchers("/error").permitAll() - .antMatchers("/fail").permitAll() - // anyRequest() 所有请求 authenticated() 必须被认证 - .anyRequest().authenticated(); + // 匹配 / 控制器 permitAll() 不需要被认证就可以访问 + .antMatchers("/login").permitAll() + .antMatchers("/error").permitAll() + .antMatchers("/fail").permitAll() + // anyRequest() 所有请求 authenticated() 必须被认证 + .anyRequest().authenticated(); // .accessDecisionManager(accessDecisionManager()); // 关闭csrf http.csrf().disable(); return http.build(); - } + } ``` -或者继承WebSecurityConfigurerAdapter的类了. +或者继承 WebSecurityConfigurerAdapter 的类了。 + +这里我们用 springSecurity 默认的`SpringBootWebSecurityConfiguration`来举例 -这里我们用security默认的`SpringBootWebSecurityConfiguration`来举例 ```java @Bean - @Order(SecurityProperties.BASIC_AUTH_ORDER) - SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); - http.formLogin(); - http.httpBasic(); - return http.build(); - } +@Order(SecurityProperties.BASIC_AUTH_ORDER) + SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)throws Exception{ + http.authorizeRequests().anyRequest().authenticated(); + http.formLogin(); + http.httpBasic(); + return http.build(); + } ``` -首先获取`HttpSecurity http`(这里的`HttpSecurity`是从`HttpSecurityConfiguration`里面创建的,如果是自定义的`SecurityFilterChain`就是从自己写的里面来的) +首先获取`HttpSecurity http`(这里的`HttpSecurity`是从`HttpSecurityConfiguration` +里面创建的,如果是自定义的`SecurityFilterChain`就是从自己写的里面来的) > 我们来到`HttpSecurityConfiguration#httpSecurity()` -先创建一个默认的密码管理器, +> 先创建一个默认的密码管理器, -接下来进入`authenticationBuilder.parentAuthenticationManager(authenticationManager());`,这里就是`AuthenticationConfiguration`里面的处理.这个类后面和springaop加载我们写的注释单独在`@EnableGlobalAuthentication`注解类说 +接下来进入`authenticationBuilder.parentAuthenticationManager(authenticationManager());` +,这里就是`AuthenticationConfiguration` +里面的处理。这个类后面和 springaop 加载我们写的注释单独在`@EnableGlobalAuthentication`注解类说 -接着创建` HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());`, +接着创建` HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());`, > .csrf(withDefaults()) -启用CSRF保护,创建`CsrfConfigurer`类,并添加入添加入`AbstractConfiguredSecurityBuilder#configurers`. +> 启用 CSRF 保护,创建`CsrfConfigurer`类,并添加入添加入`AbstractConfiguredSecurityBuilder#configurers` > .addFilter(new WebAsyncManagerIntegrationFilter()) -这个有一个很有意思的类`FilterOrderRegistration`. 前面问的根据filter是如何包装顺序的.就在这个类里面 +这个有一个很有意思的类`FilterOrderRegistration`。 前面问的根据 filter 是如何包装顺序的。就在这个类里面 + ```java -FilterOrderRegistration() { - Step order = new Step(INITIAL_ORDER, ORDER_STEP); - put(DisableEncodeUrlFilter.class, order.next()); - put(ForceEagerSessionCreationFilter.class, order.next()); - put(ChannelProcessingFilter.class, order.next()); - order.next(); // gh-8105 - put(WebAsyncManagerIntegrationFilter.class, order.next()); - put(SecurityContextHolderFilter.class, order.next()); - put(SecurityContextPersistenceFilter.class, order.next()); - put(HeaderWriterFilter.class, order.next()); - put(CorsFilter.class, order.next()); - put(CsrfFilter.class, order.next()); - put(LogoutFilter.class, order.next()); - // ..... +FilterOrderRegistration(){ + Step order=new Step(INITIAL_ORDER,ORDER_STEP); + put(DisableEncodeUrlFilter.class,order.next()); + put(ForceEagerSessionCreationFilter.class,order.next()); + put(ChannelProcessingFilter.class,order.next()); + order.next(); // gh-8105 + put(WebAsyncManagerIntegrationFilter.class,order.next()); + put(SecurityContextHolderFilter.class,order.next()); + put(SecurityContextPersistenceFilter.class,order.next()); + put(HeaderWriterFilter.class,order.next()); + put(CorsFilter.class,order.next()); + put(CsrfFilter.class,order.next()); + put(LogoutFilter.class,order.next()); +// ..... ``` -security事先使用这个类把预加载的类全部排序好,然后每次add一个新的filter就会使用这个里面的序号.如果我们有自定义的类,也要提前加载到里面去,不然就会 + +springSecurity 事先使用这个类把预加载的类全部排序好,然后每次 add 一个新的 filter 就会使用这个里面的序号。如果我们有自定义的类,也要提前加载到里面去,不然就会 + ```java - throw new IllegalArgumentException("The Filter class " + filter.getClass().getName() - + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); + throw new IllegalArgumentException("The Filter class "+filter.getClass().getName() + +" does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); ``` -`WebAsyncManagerIntegrationFilter`: 与处理异步web请求相关的实用程序方法 +`WebAsyncManagerIntegrationFilter`: 与处理异步 web 请求相关的实用程序方法 > .exceptionHandling(withDefaults()) 这个是异常的处理 -提示: 这个里面每次添加一个类,如果在`HttpSecurity`中调用`getOrApply`.比如这个代码调用的是`exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));`. 打开`ExceptionHandlingConfigurer`类,发现是一个`HttpSecurityBuilder`, 这样只需要看`configure`方法大概就能明白这个是一个什么类. 这个就是在filter中添加了一个`ExceptionTranslationFilter`filter.主要就是`SecurityConfigurer`的俩个方法.先调用`init(B builder)`,然后`configure(B builder)` +提示: 这个里面每次添加一个类,如果在`HttpSecurity`中调用`getOrApply` +。比如这个代码调用的是`exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));`。 +打开`ExceptionHandlingConfigurer`类,发现是一个`HttpSecurityBuilder`, 这样只需要看`configure`方法大概就能明白这个是一个什么类。 +这个就是在 filter 中添加了一个`ExceptionTranslationFilter`filter.主要就是`SecurityConfigurer` +的俩个方法。先调用`init(B builder)`,然后`configure(B builder)` + +后面都是一样,就跳过了 -后面都是一样,就跳过了 - > applyDefaultConfigurers(http); -这里的这一句,就是从"META-INF/spring.factories" 中加载并实例化给定类型的工厂实现 +这里的这一句,就是从"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`类. +> http.authorizeRequests().anyRequest().authenticated(); -后面调用的`.anyRequest()`,也就是`AbstractRequestMatcherRegistry#anyRequest()`.先了解一下结构图 +首先添加了`http.authorizeRequests()` +然后调用 `return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();`。 +先把`ExpressionUrlAuthorizationConfigurer`放入 config 中,返回一个调用了`getRegistry()`。 +也就是`ExpressionInterceptUrlRegistry`类。 + +后面调用的`.anyRequest()`,也就是`AbstractRequestMatcherRegistry#anyRequest()`。先了解一下结构图 ![img_3.png](../../images/SpringSecurity/img-2023-6-7-_3.png) @@ -582,13 +618,16 @@ security事先使用这个类把预加载的类全部排序好,然后每次add `http.authorizeRequests().anyRequest().authenticated();`是需要所有请求登录后才能访问 -1. `authorizeRequests`是创建了一个`ExpressionUrlAuthorizationConfigurer`并添加入configurer中. -2. `anyRequest`是创建了一个`new AuthorizedUrl(requestMatchers)`,其中 `requestMatchers`是`AnyRequestMatcher.INSTANCE;`也就是`AnyRequestMatcher`对象.里面`matches(HttpServletRequest request)`直接返回ture -3. `authenticated()`也就是授权,`ExpressionInterceptUrlRegistry#addMapping`.里面放入了一个`UrlMapping`,`UrlMapping`的俩个属性,一个是`AnyRequestMatcher`(所有请求),`configAttrs`表示`SecurityConfig`.`SecurityConfig`的值为`private static final String authenticated = "authenticated"` +1. `authorizeRequests`是创建了一个`ExpressionUrlAuthorizationConfigurer`并添加入 configurer 中。 +2. `anyRequest`是创建了一个`new AuthorizedUrl(requestMatchers)`,其中 `requestMatchers` + 是`AnyRequestMatcher.INSTANCE;`也就是`AnyRequestMatcher`对象。里面`matches(HttpServletRequest request)`直接返回 ture +3. `authenticated()`也就是授权,`ExpressionInterceptUrlRegistry#addMapping`。里面放入了一个`UrlMapping`,`UrlMapping` + 的俩个属性,一个是`AnyRequestMatcher`(所有请求),`configAttrs`表示`SecurityConfig`。`SecurityConfig` + 的值为`private static final String authenticated = "authenticated"` > http.formLogin(); -创建了一个`FormLoginConfigurer`,也就是`SecurityConfigurer`.关注`init`和`configure`方法.后面统一讲解 +创建了一个`FormLoginConfigurer`,也就是`SecurityConfigurer`。关注`init`和`configure`方法。后面统一讲解 > http.httpBasic(); @@ -596,12 +635,13 @@ security事先使用这个类把预加载的类全部排序好,然后每次add > http.build() -进行构建,这个就是非常重要的一个方法,build对象,老规矩.进入`AbstractConfiguredSecurityBuilder#doBuild()`方法 +进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入`AbstractConfiguredSecurityBuilder#doBuild()`方法 `beforeInit();`: 还是没有什么 -`init()`: 调用里面所有的`configurers`里面的`init方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 +`init()`: 调用里面所有的`configurers`里面的`init方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 + +接下来`SecurityFilterChain`就已经创建好了,看一下里面的方法 -接下来`SecurityFilterChain`就已经创建好了,看一下里面的方法 ```java /** * Defines a filter chain which is capable of being matched against an @@ -614,102 +654,114 @@ security事先使用这个类把预加载的类全部排序好,然后每次add */ public interface SecurityFilterChain { - boolean matches(HttpServletRequest request); + boolean matches(HttpServletRequest request); - List getFilters(); + List getFilters(); } ``` -肯定是先匹配,如果成功了,就返回里面所有的filter进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求 -`SecurityAutoConfiguration`类就已经大致讲完了, +肯定是先匹配,如果成功了,就返回里面所有的 filter 进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求 + +`SecurityAutoConfiguration`类就已经大致讲完了, ### @EnableGlobalAuthentication + 当前注解在`@EnableSecurity`中会自动加上 #### @Import(AuthenticationConfiguration.class) -`AuthenticationConfiguration`上面`@Import(ObjectPostProcessorConfiguration.class)`. 以前使用的`ObjectPostProcessor`就是在这里注入的,注入`AutowireBeanFactoryObjectPostProcessor`对象 +`AuthenticationConfiguration`上面`@Import(ObjectPostProcessorConfiguration.class)`。 以前使用的`ObjectPostProcessor` +就是在这里注入的,注入`AutowireBeanFactoryObjectPostProcessor`对象 #### AuthenticationManagerBuilder -```java + +```java @Bean - public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor objectPostProcessor, - ApplicationContext context) { - LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); - AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); - DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder( - objectPostProcessor, defaultPasswordEncoder); - if (authenticationEventPublisher != null) { - result.authenticationEventPublisher(authenticationEventPublisher); - } - return result; - } +public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor objectPostProcessor, + ApplicationContext context){ + LazyPasswordEncoder defaultPasswordEncoder=new LazyPasswordEncoder(context); + AuthenticationEventPublisher authenticationEventPublisher=getAuthenticationEventPublisher(context); + DefaultPasswordEncoderAuthenticationManagerBuilder result=new DefaultPasswordEncoderAuthenticationManagerBuilder( + objectPostProcessor,defaultPasswordEncoder); + if(authenticationEventPublisher!=null){ + result.authenticationEventPublisher(authenticationEventPublisher); + } + return result; + } ``` -这里面返回了一个`AuthenticationManagerBuilder`的bean,也就是上面``HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是一个`SecurityBuilder`. + +这里面返回了一个`AuthenticationManagerBuilder`的 bean,也就是上面`` +HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是一个`SecurityBuilder`。 > LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); -首先创建了一个`LazyPasswordEncoder`,就是`PasswordEncoder`,用来管理密码的 +首先创建了一个`LazyPasswordEncoder`,就是`PasswordEncoder`,用来管理密码的 > AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); -这个就是在`SecurityAutoConfiguration`中创建的security的发布订阅,用来订阅事件 +这个就是在`SecurityAutoConfiguration`中创建的 springSecurity 的发布订阅,用来订阅事件 + +> DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder( +> objectPostProcessor, defaultPasswordEncoder); -> DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); +就是`AuthenticationManagerBuilder`的真正实现了。接下来回到`getAuthenticationManager()`方法 -就是`AuthenticationManagerBuilder`的真正实现了.接下来回到`getAuthenticationManager()`方法 ```java -public AuthenticationManager getAuthenticationManager() throws Exception { - if (this.authenticationManagerInitialized) { - return this.authenticationManager; - } - AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); - if (this.buildingAuthenticationManager.getAndSet(true)) { - return new AuthenticationManagerDelegator(authBuilder); - } - for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { - authBuilder.apply(config); - } - this.authenticationManager = authBuilder.build(); - if (this.authenticationManager == null) { - this.authenticationManager = getAuthenticationManagerBean(); - } - this.authenticationManagerInitialized = true; - return this.authenticationManager; -} +public AuthenticationManager getAuthenticationManager()throws Exception{ + if(this.authenticationManagerInitialized){ + return this.authenticationManager; + } + AuthenticationManagerBuilder authBuilder=this.applicationContext.getBean(AuthenticationManagerBuilder.class); + if(this.buildingAuthenticationManager.getAndSet(true)){ + return new AuthenticationManagerDelegator(authBuilder); + } + for(GlobalAuthenticationConfigurerAdapter config:this.globalAuthConfigurers){ + authBuilder.apply(config); + } + this.authenticationManager=authBuilder.build(); + if(this.authenticationManager==null){ + this.authenticationManager=getAuthenticationManagerBean(); + } + this.authenticationManagerInitialized=true; + return this.authenticationManager; + } ``` + > AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); -获取到`DefaultPasswordEncoderAuthenticationManagerBuilder`. +获取到`DefaultPasswordEncoderAuthenticationManagerBuilder` ```java - for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { - authBuilder.apply(config); - } + for(GlobalAuthenticationConfigurerAdapter config:this.globalAuthConfigurers){ + authBuilder.apply(config); + } ``` -需要注意的是,`this.globalAuthConfigurers`就是上面三个类, + +需要注意的是,`this.globalAuthConfigurers`就是上面三个类, + ```java @Bean - public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( - ApplicationContext context) { - return new EnableGlobalAuthenticationAutowiredConfigurer(context); - } - - @Bean - public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer( - ApplicationContext context) { - return new InitializeUserDetailsBeanManagerConfigurer(context); - } - - @Bean - public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer( - ApplicationContext context) { - return new InitializeAuthenticationProviderBeanManagerConfigurer(context); - } +public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( + ApplicationContext context){ + return new EnableGlobalAuthenticationAutowiredConfigurer(context); + } + +@Bean +public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer( + ApplicationContext context){ + return new InitializeUserDetailsBeanManagerConfigurer(context); + } + +@Bean +public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer( + ApplicationContext context){ + return new InitializeAuthenticationProviderBeanManagerConfigurer(context); + } ``` -调用了`apply`也就是`add`方法.添加到`configurers`中 -然后调用`build`并返回. 又是到了 `doBuild()` 这里 +调用了`apply`也就是`add`方法。添加到`configurers`中 + +然后调用`build`并返回。 又是到了 `doBuild()` 这里 > beforeInit(); @@ -718,9 +770,14 @@ public AuthenticationManager getAuthenticationManager() throws Exception { > init(); 上面三个类的`init`方法 + 1. `EnableGlobalAuthenticationAutowiredConfigurer#init` -2. `InitializeUserDetailsBeanManagerConfigurer#init` 调用了`auth.apply(new InitializeUserDetailsManagerConfigurer());` 这个类比上面类名字少了一个bean,并且没有后init方法 只有`configure`方法. 里面创建的`DaoAuthenticationProvider`,里面默认有一个`passwordEncoder`,在无参构造方法里面.而`UserDetailsService`和`DaoAuthenticationProvider`是同一个,也就是在`UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager`这里创建的.里面继承了,所以是同一个 -3. `InitializeAuthenticationProviderBeanManagerConfigurer#init`跟2一样,apply了一个`InitializeAuthenticationProviderManagerConfigurer` +2. `InitializeUserDetailsBeanManagerConfigurer#init` 调用了`auth.apply(new InitializeUserDetailsManagerConfigurer());` + 这个类比上面类名字少了一个 bean,并且没有后 init 方法 只有`configure`方法。 里面创建的`DaoAuthenticationProvider` + ,里面默认有一个`passwordEncoder`,在无参构造方法里面。而`UserDetailsService`和`DaoAuthenticationProvider` + 是同一个,也就是在`UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager`这里创建的。里面继承了,所以是同一个 +3. `InitializeAuthenticationProviderBeanManagerConfigurer#init` + 跟 2 一样,apply 了一个`InitializeAuthenticationProviderManagerConfigurer` > beforeConfigure(); @@ -733,143 +790,155 @@ public AuthenticationManager getAuthenticationManager() throws Exception { 2: `InitializeUserDetailsManagerConfigurer#configure`方法 ```java - @Override - public void configure(AuthenticationManagerBuilder auth) throws Exception { - if (auth.isConfigured()) { - return; - } - UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class); - if (userDetailsService == null) { - return; - } - PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); - UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(userDetailsService); - if (passwordEncoder != null) { - provider.setPasswordEncoder(passwordEncoder); - } - if (passwordManager != null) { - provider.setUserDetailsPasswordService(passwordManager); - } - provider.afterPropertiesSet(); - auth.authenticationProvider(provider); - } + @Override +public void configure(AuthenticationManagerBuilder auth)throws Exception{ + if(auth.isConfigured()){ + return; + } + UserDetailsService userDetailsService=getBeanOrNull(UserDetailsService.class); + if(userDetailsService==null){ + return; + } + PasswordEncoder passwordEncoder=getBeanOrNull(PasswordEncoder.class); + UserDetailsPasswordService passwordManager=getBeanOrNull(UserDetailsPasswordService.class); + DaoAuthenticationProvider provider=new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); + if(passwordEncoder!=null){ + provider.setPasswordEncoder(passwordEncoder); + } + if(passwordManager!=null){ + provider.setUserDetailsPasswordService(passwordManager); + } + provider.afterPropertiesSet(); + auth.authenticationProvider(provider); + } ``` -获取所有`UserDetailsService`和`PasswordEncoder`和`UserDetailsPasswordService`,使用`DaoAuthenticationProvider`进行管理,然后添加到`AuthenticationManagerBuilder#authenticationProviders`中 +获取所有`UserDetailsService`和`PasswordEncoder`和`UserDetailsPasswordService`,使用`DaoAuthenticationProvider` +进行管理,然后添加到`AuthenticationManagerBuilder#authenticationProviders`中 -3: `InitializeAuthenticationProviderManagerConfigurer#configure`方法,把spring中的所有`AuthenticationProvider`添加到`AuthenticationManagerBuilder#authenticationProviders`中 +3: `InitializeAuthenticationProviderManagerConfigurer#configure`方法,把 spring 中的所有`AuthenticationProvider` +添加到`AuthenticationManagerBuilder#authenticationProviders`中 然后又到了熟悉的`AuthenticationManagerBuilder#performBuild` ```java -ProviderManager providerManager = new ProviderManager(this.authenticationProviders, - this.parentAuthenticationManager); - if (this.eraseCredentials != null) { - providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials); - } - if (this.eventPublisher != null) { - providerManager.setAuthenticationEventPublisher(this.eventPublisher); - } - providerManager = postProcess(providerManager); - return providerManager; +ProviderManager providerManager=new ProviderManager(this.authenticationProviders, + this.parentAuthenticationManager); + if(this.eraseCredentials!=null){ + providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials); + } + if(this.eventPublisher!=null){ + providerManager.setAuthenticationEventPublisher(this.eventPublisher); + } + providerManager=postProcess(providerManager); + return providerManager; ``` -首先使用`ProviderManager`管理`authenticationProviders`和`parentAuthenticationManager`,这里的`eraseCredentials`和`CredentialsContainer`类有关,也就是敏感数据.接着的`eventPublisher`就是发布订阅了,默认会创建的 -然后`providerManager = postProcess(providerManager);`就是注入spring容器中,接着返回 -,这里返回的其实是`ProviderManager`对象了,接着就是到了`HttpSecurity`的创建了,后面`HttpSecurity#doBuild()`时候再讲`HttpSecurity`的构建 - -这里面的`LazyPasswordEncoder`这个类也很有意思,手动制造一个懒加载类 - - - - - +首先使用`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`类. 这个我找了半天,后面无意中才发现 +这里有个很坑的地方,里面的`prePostEnabled`,`securedEnabled`这些属性,不是直接在`GlobalMethodSecuritySelector` +中进行处理的,放在了`GlobalMethodSecuritySelector#methodSecurityMetadataSource` +这个 bean 里面进行处理,然后开启`prePostEnabled`之后,就会加载`PrePostAnnotationSecurityMetadataSource`类。 这个我找了半天,后面无意中才发现 这个注解也添加了`@EnableGlobalAuthentication`注解 主要是看`GlobalMethodSecuritySelector`类 -里面加载了`AutoProxyRegistrar`,这个就是springaop的类,创建代理对象的一个类.会创建`InfrastructureAdvisorAutoProxyCreator`类来创建代理对象.关键是`GlobalMethodSecurityConfiguration`和`MethodSecurityMetadataSourceAdvisorRegistrar`这俩个类 +里面加载了`AutoProxyRegistrar`,这个就是 springaop 的类,创建代理对象的一个类。会创建`InfrastructureAdvisorAutoProxyCreator` +类来创建代理对象。关键是`GlobalMethodSecurityConfiguration`和`MethodSecurityMetadataSourceAdvisorRegistrar`这俩个类 -`MethodSecurityMetadataSourceAdvisorRegistrar`类里面都用到了`GlobalMethodSecurityConfiguration`.我就放在一起了 +`MethodSecurityMetadataSourceAdvisorRegistrar`类里面都用到了`GlobalMethodSecurityConfiguration`。我就放在一起了 ### MethodSecurityMetadataSourceAdvisorRegistrar -这个类里面就是往spring中注册了一个`MethodSecurityMetadataSourceAdvisor`对象 +这个类里面就是往 spring 中注册了一个`MethodSecurityMetadataSourceAdvisor`对象 #### MethodSecurityMetadataSourceAdvisor -这个类就是`PointcutAdvisor`,使用`AbstractAutoProxyCreator`创建代理对象的是,会获取`Pointcut`来判断是否需要代理对象,然后使用`Advice`来进行其余操作.这是springaop的内容就不过多讲解了 +这个类就是`PointcutAdvisor`,使用`AbstractAutoProxyCreator`创建代理对象的是,会获取`Pointcut` +来判断是否需要代理对象,然后使用`Advice`来进行其余操作。这是 springaop 的内容就不过多讲解了 + +aop 首先获取`pointcut`,进行匹配,当前的为 -aop首先获取`pointcut`,进行匹配,当前的为 ```java class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { - @Override - public boolean matches(Method m, Class targetClass) { - MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource; - return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass)); - } + @Override + public boolean matches(Method m, Class targetClass) { + MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource; + return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass)); + } - } +} ``` -也就是`StaticMethodMatcherPointcut`,`ClassFilter`默认都是true,方法匹配为`MethodSecurityMetadataSourceAdvisor#attributeSource`进行匹配.而`methodSecurityMetadataSource`是在`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource`里面进行创建的.这俩个类后面讲.只要匹配成功就和aop一样流程了 -这里的`Advice`就是`MethodSecurityInterceptor`类.在`GlobalMethodSecurityConfiguration#methodSecurityInterceptor`中创建 +也就是`StaticMethodMatcherPointcut`,`ClassFilter` +默认都是 true,方法匹配为`MethodSecurityMetadataSourceAdvisor#attributeSource`进行匹配。而`methodSecurityMetadataSource` +是在`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource`里面进行创建的。这俩个类后面讲。只要匹配成功就和 aop 一样流程了 + +这里的`Advice`就是`MethodSecurityInterceptor`类。在`GlobalMethodSecurityConfiguration#methodSecurityInterceptor`中创建 ##### MethodSecurityInterceptor > isPrePostEnabled -添加`PrePostAnnotationSecurityMetadataSource`类,主要关注`getAttributes`方法.后面会讲. 这里面就是我们常用的注解了,然后构建成`ConfigAttribute`并返回.里面的构建主要用的是`PrePostInvocationAttributeFactory`的实现,只有一个实现 +添加`PrePostAnnotationSecurityMetadataSource`类,主要关注`getAttributes`方法后面会讲。 +这里面就是我们常用的注解了,然后构建成`ConfigAttribute`并返回。里面的构建主要用的是`PrePostInvocationAttributeFactory` +的实现,只有一个实现 > isSecuredEnabled -这个就是`@Secured`注解的处理. 逻辑基本和上面一样 +这个就是`@Secured`注解的处理。 逻辑基本和上面一样 -最后返回一个`DelegatingMethodSecurityMetadataSource`对象,就是`MethodSecurityInterceptor`中用到的对象 +最后返回一个`DelegatingMethodSecurityMetadataSource`对象,就是`MethodSecurityInterceptor`中用到的对象 +匹配成功的 aop 都会进入`MethodSecurityInterceptor#invoke` -匹配成功的aop都会进入`MethodSecurityInterceptor#invoke` ```java @Override - public Object invoke(MethodInvocation mi) throws Throwable { - InterceptorStatusToken token = super.beforeInvocation(mi); - Object result; - try { - result = mi.proceed(); - } - finally { - super.finallyInvocation(token); - } - return super.afterInvocation(token, result); - } +public Object invoke(MethodInvocation mi)throws Throwable{ + InterceptorStatusToken token=super.beforeInvocation(mi); + Object result; + try{ + result=mi.proceed(); + } + finally{ + super.finallyInvocation(token); + } + return super.afterInvocation(token,result); + } ``` -这个一看就是标准aop -###### super.beforeInvocation(mi) +这个一看就是标准 aop -这个里面就有授权了,`Authorization`和`authentication`不一样,一个是认证一个是授权.这个是授权,简单说就是角色 +###### super.beforeInvocation(mi) + +这个里面就有授权了,`Authorization`和`authentication`不一样,一个是认证一个是授权。这个是授权,简单说就是角色 > Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); 接着来到了`DelegatingMethodSecurityMetadataSource#getAttributes` + ```java - for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { - attributes = s.getAttributes(method, targetClass); - if (attributes != null && !attributes.isEmpty()) { - break; - } - } + for(MethodSecurityMetadataSource s:this.methodSecurityMetadataSources){ + attributes=s.getAttributes(method,targetClass); + if(attributes!=null&&!attributes.isEmpty()){ + break; + } + } ``` -`this.methodSecurityMetadataSources`里面的值,就是`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource`里面的`sources`. -构建出来返回`attributes`. + +`this.methodSecurityMetadataSources`里面的值,就是`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource` +里面的`sources`. +构建出来返回`attributes`。 > Authentication authenticated = authenticateIfRequired(); @@ -877,23 +946,22 @@ class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut i > attemptAuthorization(object, attributes, authenticated); -使用`accessDecisionManager`进行授权.放到`MethodInterceptor`中进行讲解 +使用`accessDecisionManager`进行授权。放到`MethodInterceptor`中进行讲解 里面还有授权失败发布事件`publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));` -接着就是授权成功发送事件,接着就是返回一个`InterceptorStatusToken`对象 +接着就是授权成功发送事件,接着就是返回一个`InterceptorStatusToken`对象 -###### result = mi.proceed(); +###### result = mi.proceed(); 执行业务 ###### super.finallyInvocation(token); -是否刷新`InterceptorStatusToken`,前面传参是false +是否刷新`InterceptorStatusToken`,前面传参是 false ###### return super.afterInvocation(token, result); -后处理器,与前处理器基本一样.剩下的`MethodInterceptor`中进行讲解 - +后处理器,与前处理器基本一样。剩下的`MethodInterceptor`中进行讲解 #### MethodInterceptor @@ -905,36 +973,37 @@ class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut i ##### accessDecisionManager() -这个就是`AbstractSecurityInterceptor#attemptAuthorization`的授权方法. +这个就是`AbstractSecurityInterceptor#attemptAuthorization`的授权方法 ```java - protected AccessDecisionManager accessDecisionManager() { - List> decisionVoters = new ArrayList<>(); - if (prePostEnabled()) { - ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); - expressionAdvice.setExpressionHandler(getExpressionHandler()); - decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); - } - if (jsr250Enabled()) { - decisionVoters.add(new Jsr250Voter()); - } - RoleVoter roleVoter = new RoleVoter(); - GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class); - if (grantedAuthorityDefaults != null) { - roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()); - } - decisionVoters.add(roleVoter); - decisionVoters.add(new AuthenticatedVoter()); - return new AffirmativeBased(decisionVoters); - } + protected AccessDecisionManager accessDecisionManager(){ + List>decisionVoters=new ArrayList<>(); + if(prePostEnabled()){ + ExpressionBasedPreInvocationAdvice expressionAdvice=new ExpressionBasedPreInvocationAdvice(); + expressionAdvice.setExpressionHandler(getExpressionHandler()); + decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); + } + if(jsr250Enabled()){ + decisionVoters.add(new Jsr250Voter()); + } + RoleVoter roleVoter=new RoleVoter(); + GrantedAuthorityDefaults grantedAuthorityDefaults=getSingleBeanOrNull(GrantedAuthorityDefaults.class); + if(grantedAuthorityDefaults!=null){ + roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()); + } + decisionVoters.add(roleVoter); + decisionVoters.add(new AuthenticatedVoter()); + return new AffirmativeBased(decisionVoters); + } ``` + 前提条件必须开启`prePostEnabled` -这里面返回的也是`AffirmativeBased`.有时候我们自定义也会使用这个.只要有一个`AccessDecisionVoter`通过就认为是有权限的.这里就不过多讲解了 +这里面返回的也是`AffirmativeBased`,有时候我们自定义也会使用这个,只要有一个`AccessDecisionVoter`通过就认为是有权限的,这里就不过多讲解了 -里面的`GrantedAuthorityDefaults`对象,也可以是我们自定义的一个前缀.默认前缀为`ROLE_` +里面的`GrantedAuthorityDefaults`对象,也可以是我们自定义的一个前缀,默认前缀为`ROLE_` -我们一般自定义的,会使用`.accessDecisionManager(accessDecisionManager())`.在`HttpSecurity#doBuild()`中进行讲解 +我们一般自定义的,会使用`.accessDecisionManager(accessDecisionManager())`,在`HttpSecurity#doBuild()`中进行讲解 ##### afterInvocationManager @@ -944,220 +1013,236 @@ class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut i 这个就是`MethodSecurityMetadataSource`对象了 - -总结一下这里,就是实现springaop的`AbstractPointcutAdvisor`对象`MethodSecurityMetadataSourceAdvisor`. 进行aop加载,处理 +总结一下这里,就是实现 springaop 的`AbstractPointcutAdvisor`对象`MethodSecurityMetadataSourceAdvisor`, 进行 aop 加载,处理 ## EnableMethodSecurity -这个注解没有`@EnableGlobalMethodSecurity`这么强大.代码基本跟`@EnableGlobalMethodSecurity`一样 +这个注解没有`@EnableGlobalMethodSecurity`这么强大,代码基本跟`@EnableGlobalMethodSecurity`一样 ## HttpSecurity#doBuild() -接下来是最后一块内容了,主要是看里面初始化,构建了哪些类 +接下来是最后一块内容了,主要是看里面初始化,构建了哪些类 又是熟悉的`AbstractConfiguredSecurityBuilder#doBuild()` -首先看看我们一开始创建`HttpSecurity`的时候添加了哪些类. `HttpSecurityConfiguration#httpSecurity()` +首先看看我们一开始创建`HttpSecurity`的时候添加了哪些类 `HttpSecurityConfiguration#httpSecurity()` ```java - @Bean(HTTPSECURITY_BEAN_NAME) - @Scope("prototype") - HttpSecurity httpSecurity() throws Exception { - WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder( - this.context); - AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder( - this.objectPostProcessor, passwordEncoder); - authenticationBuilder.parentAuthenticationManager(authenticationManager()); - authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher()); - HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects()); - // @formatter:off - http - .csrf(withDefaults()) - .addFilter(new WebAsyncManagerIntegrationFilter()) - .exceptionHandling(withDefaults()) - .headers(withDefaults()) - .sessionManagement(withDefaults()) - .securityContext(withDefaults()) - .requestCache(withDefaults()) - .anonymous(withDefaults()) - .servletApi(withDefaults()) - .apply(new DefaultLoginPageConfigurer<>()); - http.logout(withDefaults()); - // @formatter:on - applyDefaultConfigurers(http); - return http; - } + @Bean(HTTPSECURITY_BEAN_NAME) +@Scope("prototype") + HttpSecurity httpSecurity()throws Exception{ + WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder=new WebSecurityConfigurerAdapter.LazyPasswordEncoder( + this.context); + AuthenticationManagerBuilder authenticationBuilder=new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder( + this.objectPostProcessor,passwordEncoder); + authenticationBuilder.parentAuthenticationManager(authenticationManager()); + authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher()); + HttpSecurity http=new HttpSecurity(this.objectPostProcessor,authenticationBuilder,createSharedObjects()); + // @formatter:off + http + .csrf(withDefaults()) + .addFilter(new WebAsyncManagerIntegrationFilter()) + .exceptionHandling(withDefaults()) + .headers(withDefaults()) + .sessionManagement(withDefaults()) + .securityContext(withDefaults()) + .requestCache(withDefaults()) + .anonymous(withDefaults()) + .servletApi(withDefaults()) + .apply(new DefaultLoginPageConfigurer<>()); + http.logout(withDefaults()); + // @formatter:on + applyDefaultConfigurers(http); + return http; + } ``` -`CsrfConfigurer`,`ExceptionHandlingConfigurer`,`HeadersConfigurer`,`SessionManagementConfigurer`,`SecurityContextConfigurer`,`RequestCacheConfigurer`,`AnonymousConfigurer`,`ServletApiConfigurer`,`DefaultLoginPageConfigurer`,`LogoutConfigurer` +`CsrfConfigurer`,`ExceptionHandlingConfigurer`,`HeadersConfigurer`,`SessionManagementConfigurer`,`SecurityContextConfigurer`,`RequestCacheConfigurer`,`AnonymousConfigurer`,`ServletApiConfigurer`,`DefaultLoginPageConfigurer`,`LogoutConfigurer` 还有我们添加到`META-INF/spring.factories`中的`AbstractHttpConfigurer.class`类 - -接着回到这里. 我们是自定义了一个`SecurityFilterChain`.所以在这里面进行构建 +接着回到这里, 我们是自定义了一个`SecurityFilterChain`,所以在这里面进行构建 首先` http.formLogin();`添加了`FormLoginConfigurer` -`http.authorizeRequests()` 添加了`ExpressionUrlAuthorizationConfigurer`.这个只有`configure` 没有`init` +`http.authorizeRequests()` 添加了`ExpressionUrlAuthorizationConfigurer`,这个只有`configure` 没有`init` ` http.csrf()` 添加了`CsrfConfigurer` `http.userDetailsService(userDetailsService())` 添加了一个自定义的`UserDetailsService` - ### FormLoginConfigurer 也是一个`SecurityConfigurer` 首先创建对象给属性赋值 -```java -authFilter = UsernamePasswordAuthenticationFilter() -loginPage = "/login" -this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage); +```java +authFilter=UsernamePasswordAuthenticationFilter() + loginPage="/login" + this.authenticationEntryPoint=new LoginUrlAuthenticationEntryPoint(loginPage); ``` 接着来到`init` + ```java - @Override - public void init(B http) throws Exception { - updateAuthenticationDefaults(); - updateAccessDefaults(http); - registerDefaultAuthenticationEntryPoint(http); - } + @Override +public void init(B http)throws Exception{ + updateAuthenticationDefaults(); + updateAccessDefaults(http); + registerDefaultAuthenticationEntryPoint(http); + } ``` > updateAuthenticationDefaults(); ```java - /** - * Updates the default values for authentication. - * @throws Exception - */ - protected final void updateAuthenticationDefaults() { - if (this.loginProcessingUrl == null) { - loginProcessingUrl(this.loginPage); - } - if (this.failureHandler == null) { - failureUrl(this.loginPage + "?error"); - } - LogoutConfigurer logoutConfigurer = getBuilder().getConfigurer(LogoutConfigurer.class); - if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) { - logoutConfigurer.logoutSuccessUrl(this.loginPage + "?logout"); - } - } + /** + * Updates the default values for authentication. + * @throws Exception + */ +protected final void updateAuthenticationDefaults(){ + if(this.loginProcessingUrl==null){ + loginProcessingUrl(this.loginPage); + } + if(this.failureHandler==null){ + failureUrl(this.loginPage+"?error"); + } + LogoutConfigurer logoutConfigurer=getBuilder().getConfigurer(LogoutConfigurer.class); + if(logoutConfigurer!=null&&!logoutConfigurer.isCustomLogoutSuccess()){ + logoutConfigurer.logoutSuccessUrl(this.loginPage+"?logout"); + } + } ``` `loginProcessingUrl(this.loginPage);` -1. 设置登录页面 -2. `this.authFilter`也就是`UsernamePasswordAuthenticationFilter`的`RequestMatcher`设置为`new AntPathRequestMatcher(loginProcessingUrl, "POST")` - +1. 设置登录页面 +2. `this.authFilter`也就是`UsernamePasswordAuthenticationFilter`的`RequestMatcher` + 设置为`new AntPathRequestMatcher(loginProcessingUrl, "POST")` `failureUrl(this.loginPage + "?error");` -1. 设置失败页面 -2. `this.failureHandler`设置为`new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl)`里面的`authenticationFailureUrl`是`/login + "?error"` -`getBuilder().getConfigurer(LogoutConfigurer.class);` 就是前面加入的那一堆`Configurer`中的一个.这个默认就是当前设置的值,不用理会 +1. 设置失败页面 +2. `this.failureHandler`设置为`new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl)` + 里面的`authenticationFailureUrl`是`/login + "?error"` +`getBuilder().getConfigurer(LogoutConfigurer.class);` 就是前面加入的那一堆`Configurer`中的一个.这个默认就是当前设置的值,不用理会 > updateAccessDefaults(http); -里面默认为false. +里面默认为 false > registerDefaultAuthenticationEntryPoint(http); -获取上面`Configurer`里面的`ExceptionHandlingConfigurer`. +获取上面`Configurer`里面的`ExceptionHandlingConfigurer` -在`ExceptionHandlingConfigurer`中有俩个属性. `defaultEntryPointMappings`和`defaultDeniedHandlerMappings`. 基本看注释就能知道是做什么的 .这个注释是map的value类上的注释 +在`ExceptionHandlingConfigurer`中有俩个属性, `defaultEntryPointMappings`和`defaultDeniedHandlerMappings`, 基本看注释就能知道是做什么的 +,这个注释是 map 的 value 类上的注释 ```java /** * 开始一个身份验证方案。 -在调用该方法之前, ExceptionTranslationFilter将使用请求的目标URL填充HttpSession属性abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 -实现应根据需要修改ServletResponse上的标头,以开始身份验证过程。 + 在调用该方法之前, ExceptionTranslationFilter将使用请求的目标URL填充HttpSession属性abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 + 实现应根据需要修改ServletResponse上的标头,以开始身份验证过程。 */ - private LinkedHashMap defaultEntryPointMappings = new LinkedHashMap<>(); +private LinkedHashMap defaultEntryPointMappings=new LinkedHashMap<>(); /** * 处理拒绝访问失败。 */ - private LinkedHashMap defaultDeniedHandlerMappings = new LinkedHashMap<>(); +private LinkedHashMap defaultDeniedHandlerMappings=new LinkedHashMap<>(); ``` -我们这里的是添加`defaultDeniedHandlerMappings`. `key`是`RequestMatcher`.是否匹配.`value`是匹配成功就执行 这个类里面也是只有`configure()`,没有`init()`. 后面讲解 +我们这里的是添加`defaultDeniedHandlerMappings`, `key`是`RequestMatcher`,是否匹配。 `value`是匹配成功就执行 +这个类里面也是只有`configure()`,没有`init()`, 后面讲解 -先说里面的`value`,就是当前类的`this.authenticationEntryPoint`.也就是创建类时候的`LoginUrlAuthenticationEntryPoint` +先说里面的`value`,就是当前类的`this.authenticationEntryPoint` 也就是创建类时候的`LoginUrlAuthenticationEntryPoint` -`key`就是`AndRequestMatcher`.但是里面聚合了俩个`RequestMatcher`. 一个是`MediaTypeRequestMatcher`,还有一个是`NegatedRequestMatcher` +`key`就是`AndRequestMatcher`,但是里面聚合了俩个`RequestMatcher`, 一个是`MediaTypeRequestMatcher` +,还有一个是`NegatedRequestMatcher` -其实到了这一步,我们只需要了解其中一个,剩下的都大同小异了. +其实到了这一步,我们只需要了解其中一个,剩下的都大同小异了 -举例: `ExceptionHandlingConfigurer` +举例: `ExceptionHandlingConfigurer` -`ExceptionHandlingConfigurer`这个类就是刚刚在`FormLoginConfigurer`中处理的那个.往这个里面添加了`defaultEntryPointMappings`属性. -然后我们找到`ExceptionHandlingConfigurer`中的` configure(H http) `方法, 里面就是创建了一个`ExceptionTranslationFilter`过滤器,添加到了`http`中. 代码是这一段 +`ExceptionHandlingConfigurer`这个类就是刚刚在`FormLoginConfigurer` +中处理的那个,往这个里面添加了`defaultEntryPointMappings`属性 + +然后我们找到`ExceptionHandlingConfigurer`中的`configure(H http)`方法, 里面就是创建了一个`ExceptionTranslationFilter` +过滤器,添加到了`http`中, 代码是这一段 ```java - @Override - public void configure(H http) { - AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); - ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, - getRequestCache(http)); - AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); - exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); - exceptionTranslationFilter = postProcess(exceptionTranslationFilter); - http.addFilter(exceptionTranslationFilter); - } + @Override +public void configure(H http){ + AuthenticationEntryPoint entryPoint=getAuthenticationEntryPoint(http); + ExceptionTranslationFilter exceptionTranslationFilter=new ExceptionTranslationFilter(entryPoint, + getRequestCache(http)); + AccessDeniedHandler deniedHandler=getAccessDeniedHandler(http); + exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); + exceptionTranslationFilter=postProcess(exceptionTranslationFilter); + http.addFilter(exceptionTranslationFilter); + } ``` -接着我们打开`ExceptionTranslationFilter`,这就是一个`Filter`,找到`doFilter(ServletRequest request, ServletResponse response, FilterChain chain)`方法,就是在处理`catch (Exception ex) {`的时候,做的一些事情.接着继续打开`handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception)`方法, + +接着我们打开`ExceptionTranslationFilter`,这就是一个`Filter` +,找到`doFilter(ServletRequest request, ServletResponse response, FilterChain chain)` +方法,就是在处理`catch (Exception ex) {` +的时候,做的一些事情,接着继续打开`handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception)` +方法, + ```java - private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, RuntimeException exception) throws IOException, ServletException { - if (exception instanceof AuthenticationException) { - handleAuthenticationException(request, response, chain, (AuthenticationException) exception); - } - else if (exception instanceof AccessDeniedException) { - handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception); - } - } + private void handleSpringSecurityException(HttpServletRequest request,HttpServletResponse response, + FilterChain chain,RuntimeException exception)throws IOException,ServletException{ + if(exception instanceof AuthenticationException){ + handleAuthenticationException(request,response,chain,(AuthenticationException)exception); + } + else if(exception instanceof AccessDeniedException){ + handleAccessDeniedException(request,response,chain,(AccessDeniedException)exception); + } + } ``` + 发现最后还是到了 + ```java - protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, - AuthenticationException reason) throws ServletException, IOException { - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContext context = SecurityContextHolder.createEmptyContext(); - SecurityContextHolder.setContext(context); - this.requestCache.saveRequest(request, response); - this.authenticationEntryPoint.commence(request, response, reason); - } + protected void sendStartAuthentication(HttpServletRequest request,HttpServletResponse response,FilterChain chain, + AuthenticationException reason)throws ServletException,IOException{ + // SEC-112: Clear the SecurityContextHolder's Authentication, as the + // existing Authentication is no longer considered valid + SecurityContext context=SecurityContextHolder.createEmptyContext(); + SecurityContextHolder.setContext(context); + this.requestCache.saveRequest(request,response); + this.authenticationEntryPoint.commence(request,response,reason); + } ``` -里面就有`this.authenticationEntryPoint.commence(request, response, reason);`这段代码. 创建的是`DelegatingAuthenticationEntryPoint`. + +里面就有`this.authenticationEntryPoint.commence(request, response, reason);`这段代码, +创建的是`DelegatingAuthenticationEntryPoint` + ```java @Override - public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { - for (RequestMatcher requestMatcher : this.entryPoints.keySet()) { - logger.debug(LogMessage.format("Trying to match using %s", requestMatcher)); - if (requestMatcher.matches(request)) { - AuthenticationEntryPoint entryPoint = this.entryPoints.get(requestMatcher); - logger.debug(LogMessage.format("Match found! Executing %s", entryPoint)); - entryPoint.commence(request, response, authException); - return; - } - } - logger.debug(LogMessage.format("No match found. Using default entry point %s", this.defaultEntryPoint)); - // No EntryPoint matched, use defaultEntryPoint - this.defaultEntryPoint.commence(request, response, authException); - } +public void commence(HttpServletRequest request,HttpServletResponse response, + AuthenticationException authException)throws IOException,ServletException{ + for(RequestMatcher requestMatcher:this.entryPoints.keySet()){ + logger.debug(LogMessage.format("Trying to match using %s",requestMatcher)); + if(requestMatcher.matches(request)){ + AuthenticationEntryPoint entryPoint=this.entryPoints.get(requestMatcher); + logger.debug(LogMessage.format("Match found! Executing %s",entryPoint)); + entryPoint.commence(request,response,authException); + return; + } + } + logger.debug(LogMessage.format("No match found. Using default entry point %s",this.defaultEntryPoint)); + // No EntryPoint matched, use defaultEntryPoint + this.defaultEntryPoint.commence(request,response,authException); + } ``` -基本上流程就完成了,请求先走过滤器.然后走不同的filter,报错就到了这一步.进行错误处理.其余都基本一致了. +基本上流程就完成了,请求先走过滤器,然后走不同的 filter,报错就到了这一步,进行错误处理,其余都基本一致了 ## 备注 -剩下的一些处理基本和上面这个流程一致,还有几个注解需要注意下 +剩下的一些处理基本和上面这个流程一致,还有几个注解需要注意下 `@CsrfToken` @@ -1165,34 +1250,38 @@ this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage); `@AuthenticationPrincipal` -这三个注解是在`WebMvcSecurityConfiguration`类进行处理的,只要启动了`@EnableWebSecurity`注解,就会启动 +这三个注解是在`WebMvcSecurityConfiguration`类进行处理的,只要启动了`@EnableWebSecurity`注解,就会启动 ```java + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented -@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, - HttpSecurityConfiguration.class }) +@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, + HttpSecurityConfiguration.class}) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { - /** - * Controls debugging support for Spring Security. Default is false. - * @return if true, enables debug support with Spring Security - */ - boolean debug() default false; + /** + * Controls debugging support for Spring Security. Default is false. + * @return if true, enables debug support with Spring Security + */ + boolean debug() default false; } ``` + 使用方法 + ```java - @GetMapping("/get") + @GetMapping("/get") // public Users getUser(String username,@CsrfToken CsrfToken token, @AuthenticationPrincipal Users customUser, @CurrentSecurityContext Authentication authentication) { - public Users getUser(String username, @AuthenticationPrincipal Users customUser, @CurrentSecurityContext SecurityContext securityContext) { - return userInfoService.getUsers(username); - } +public Users getUser(String username,@AuthenticationPrincipal Users customUser,@CurrentSecurityContext SecurityContext securityContext){ + return userInfoService.getUsers(username); + } ``` - -里面的`SpringWebMvcImportSelector`类注入了`WebMvcSecurityConfiguration`.这就是springmvc中`HandlerMethodArgumentResolver`的处理,也就是参数的处理,比如我们添加的`@PathVariable`,`@RequestBody`等,都是`HandlerMethodArgumentResolver`的实现类处理的,当然还有`HandlerMethodReturnValueHandler`.这些就是`DispatcherServlet`里面的处理了 +里面的`SpringWebMvcImportSelector`类注入了`WebMvcSecurityConfiguration`,这就是 springmvc 中`HandlerMethodArgumentResolver` +的处理,也就是参数的处理,比如我们添加的`@PathVariable`,`@RequestBody`等,都是`HandlerMethodArgumentResolver` +的实现类处理的,当然还有`HandlerMethodReturnValueHandler`,这些就是`DispatcherServlet`里面的处理了