diff --git a/README.md b/README.md index 1361f35..628d5db 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ - [SpringSecurity 请求全过程解析](/docs/SpringSecurity/SpringSecurity请求全过程解析.md) - [SpringSecurity 自定义用户认证](/docs/SpringSecurity/SpringSecurity自定义用户认证.md) +- [SpringSecurity 流程补充](/docs/SpringSecurity/SpringSecurity流程补充.md) ## MyBatis diff --git a/docs/SpringSecurity/SpringSecurity流程补充.md b/docs/SpringSecurity/SpringSecurity流程补充.md new file mode 100644 index 0000000..d43bafa --- /dev/null +++ b/docs/SpringSecurity/SpringSecurity流程补充.md @@ -0,0 +1,1284 @@ +# 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) + `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) + +## 启动 + +我们每次添加 `spring-boot-starter-security`,启动的时候会有一条类似的日志: + +``` +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 的全部了。 + +```imports +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 + +```java + +/** + * {@code EnableAutoConfiguration} for Spring Security. + * + * @author Dave Syer + * @author Andy Wilkinson + * @author Madhura Bhave + * @since 1.0.0 + */ +@AutoConfiguration +@ConditionalOnClass(DefaultAuthenticationEventPublisher.class) +@EnableConfigurationProperties(SecurityProperties.class) +@Import({SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class}) +public class SecurityAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(AuthenticationEventPublisher.class) + public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { + return new DefaultAuthenticationEventPublisher(publisher); + } + +} +``` + +### @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` + +```java +/** + * {@link Condition} for + * {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}. + * + * @author Phillip Webb + */ +class DefaultWebSecurityCondition extends AllNestedConditions { + + DefaultWebSecurityCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnClass({SecurityFilterChain.class, HttpSecurity.class}) + static class Classes { + + } + + @ConditionalOnMissingBean({ + org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class, + SecurityFilterChain.class}) + @SuppressWarnings("deprecation") + static class Beans { + + } + +} +``` + +`SecurityFilterChain`就是整个 springsecurity 的流程了,有俩个方法,一个是`boolean matches(HttpServletRequest request);` +,是否匹配这次请求,匹配成功就获取当前所有`Filter`进行处理 + +`SecurityFilterChain`类会放在最下面单独讲解 + +##### ErrorPageSecurityFilterConfiguration + +这是第二个子类,主要就是通过`FilterRegistrationBean`注入了一个`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; + } + +} +``` + +##### WebSecurityEnablerConfiguration + +这个类主要就是添加了`@EnableWebSecurity`注解,这个注解也很重要,后面跟`SecurityFilterChain`一起讲解 + +### DefaultAuthenticationEventPublisher + +在类中还存在`SecurityAutoConfiguration`bean,这个是属于 spring 的发布订阅。改装一下,就是 springSecurity 的成功和失败事件,可以订阅失败后的一些处理,如日志打印等 + +```java +/** + * @author Luke Taylor + * @since 3.0 + */ +public interface AuthenticationEventPublisher { + + void publishAuthenticationSuccess(Authentication authentication); + + void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication); + +} +``` + +## UserDetailsServiceAutoConfiguration + +注入条件 + +```java + +@ConditionalOnBean(ObjectPostProcessor.class) +@ConditionalOnMissingBean( + value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, + AuthenticationManagerResolver.class}, + type = {"org.springframework.security.oauth2.jwt.JwtDecoder", + "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 的非持久化实现,支持内存映射。 +主要用于测试和演示目的,其中不需要完整的持久系统 + +## 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 dispatcherTypes = new HashSet<>( +Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));` + +注意: 这里需要了解一下`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` +中,存在这样一个类 + +```java +public interface ServletContainerInitializer { + + /** + * Receives notification during startup of a web application of the classes + * within the web application that matched the criteria defined via the + * {@link javax.servlet.annotation.HandlesTypes} annotation. + * + * @param c The (possibly null) set of classes that met the specified + * criteria + * @param ctx The ServletContext of the web application in which the + * classes were discovered + * + * @throws ServletException If an error occurs + */ + void onStartup(Set> c, ServletContext ctx) throws ServletException; +``` + +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` + +```java + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@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; + +} +``` + +### 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> webSecurityConfigurers = new +> AutowiredWebSecurityConfigurersIgnoreParents(beanFactory).getWebSecurityConfigurers(); + +`AutowiredWebSecurityConfigurersIgnoreParents`也就是获取所有的`WebSecurityConfigurerAdapter` + +这里有几个类需要了解`SecurityConfigurer`和`SecurityBuilder` + +先了解一下结构 +![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`) + +`WebSecurityConfigurerAdapter`也可以认为就是`SecurityConfigurer` + +`WebSecurity`也就是`SecurityBuilder` + +然后在`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; + } +} +``` + +回到原来地方,返回的`webSecurityConfigurers`,里面的 + +```java +for (SecurityConfigurer webSecurityConfigurer : webSecurityConfigurers) { + this.webSecurity.apply(webSecurityConfigurer); +} +``` + +然后就到了`AbstractConfiguredSecurityBuilder#apply`方法,里面调用了`add(configurer);` 也就是把`SecurityConfigurer` +放入了`AbstractConfiguredSecurityBuilder#configurers`的一个 map 中,这样就使用`SecurityBuilder`聚合了`SecurityConfigurer`。 +在构建的时候可以做一些事情 + +也就是说使用`WebSecurity`聚合了`SecurityConfigurer`(包括`WebSecurityConfigurerAdapter`) + +> this.securityFilterChains = securityFilterChains; + +获取所有的`securityFilterChains` + +> this.webSecurityCustomizers = webSecurityCustomizers; + +获取所有`webSecurityCustomizers` + +#### 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(); +``` + +首先使用根据获取到的`securityFilterChains`set 入`WebSecurity#securityFilterChainBuilders`的 List 属性 + +这里有个需要注意的地方,如果你继承了`WebSecurityConfigurerAdapter`。`this.securityFilterChains` 就是一个空的。 + +而且会由`WebSecurityConfigurerAdapter#getHttp()`进行创建`WebSecurity` +。这就跟 spring 的 order 有关了。 `@Order(SecurityProperties.BASIC_AUTH_ORDER)` + +其中`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(); + } + +} +``` + +这里会创建`SecurityFilterChain`。 还会有一个`HttpSecurity`的注入 + +继续回到上面, + +```java +if (filter instanceof FilterSecurityInterceptor) { + this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); + break; +} +``` + +`FilterSecurityInterceptor`也在这里进行处理,也就是`SecurityMetadataSource`元数据 + +然后自定义的`WebSecurityCustomizer`也在这里。可以自行改变`webSecurity` + +```java +for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { + customizer.customize(this.webSecurity); +} +``` + +接下来就是构建了,来到 `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; + } +} +``` + +> init(); +> 如果继承了`WebSecurityConfigurerAdapter`,就会在这里创建`HttpSecurity` + +注意: 这里个`buildState`,用来控制当前状态的 + +> beforeConfigure(); + +在当前是没有什么处理 + +> configure(); + +这行代码就是我们每次继承`WebSecurityConfigurerAdapter`的处理了 + +> O result = performBuild(); + +然后就到了`WebSecurity#performBuild()`。 + +1. 首先排除忽略的`RequestMatcher` +2. 添加入`securityFilterChain` 和`requestMatcherPrivilegeEvaluatorsEntries` +3. 创建出`FilterChainProxy`bean 的名称为`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` + +其实到了这一步。后面的就是我们自己编写的代码了比如 + +```java +@Bean +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(); + // .accessDecisionManager(accessDecisionManager()); + // 关闭csrf + http.csrf().disable(); + return http.build(); +} +``` + +或者继承 WebSecurityConfigurerAdapter 的类了。 + +这里我们用 springSecurity 默认的`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(); +} +``` + +首先获取`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 是如何包装顺序的。就在这个类里面 + +```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()); +} +``` + +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."); +``` + +`WebAsyncManagerIntegrationFilter`: 与处理异步 web 请求相关的实用程序方法 + +> .exceptionHandling(withDefaults()) + +这个是异常的处理 + +提示: 这个里面每次添加一个类,如果在`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" 中加载并实例化给定类型的工厂实现 +`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()`。先了解一下结构图 + +![img_3.png](../../images/SpringSecurity/img-2023-6-7-_3.png) + +完整调用链就是`AbstractRequestMatcherRegistry#anyRequest()` -> `AbstractRequestMatcherRegistry#requestMatchers(RequestMatcher... requestMatchers)` -> `AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers(List requestMatchers)` -> `ExpressionUrlAuthorizationConfigurer#chainRequestMatchersInternal(List requestMatchers)` -> `return new AuthorizedUrl(requestMatchers);` + +`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"` + +> http.formLogin(); + +创建了一个`FormLoginConfigurer`,也就是`SecurityConfigurer`。关注`init`和`configure`方法。后面统一讲解 + +> http.httpBasic(); + +`HttpBasicConfigurer`类 + +> http.build() + +进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入`AbstractConfiguredSecurityBuilder#doBuild()`方法 +`beforeInit();`: 还是没有什么 + +`init()`: 调用里面所有的`configurers`里面的`init方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 + +接下来`SecurityFilterChain`就已经创建好了,看一下里面的方法 + +```java +/** + * Defines a filter chain which is capable of being matched against an + * {@code HttpServletRequest}. in order to decide whether it applies to that request. + *

+ * Used to configure a {@code FilterChainProxy}. + * + * @author Luke Taylor + * @since 3.1 + */ +public interface SecurityFilterChain { + + boolean matches(HttpServletRequest request); + + List getFilters(); +} +``` + +肯定是先匹配,如果成功了,就返回里面所有的 filter 进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求 + +`SecurityAutoConfiguration`类就已经大致讲完了, + +### @EnableGlobalAuthentication + +当前注解在`@EnableSecurity`中会自动加上 + +#### @Import(AuthenticationConfiguration.class) + +`AuthenticationConfiguration`上面`@Import(ObjectPostProcessorConfiguration.class)`。 以前使用的`ObjectPostProcessor` +就是在这里注入的,注入`AutowireBeanFactoryObjectPostProcessor`对象 + +#### AuthenticationManagerBuilder + +```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; +} +``` + +这里面返回了一个`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()`方法 + +```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; +} +``` + +> AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); + +获取到`DefaultPasswordEncoderAuthenticationManagerBuilder` + +```java +for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { + authBuilder.apply(config); +} +``` + +需要注意的是,`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); +} +``` + +调用了`apply`也就是`add`方法。添加到`configurers`中 + +然后调用`build`并返回。 又是到了 `doBuild()` 这里 + +> beforeInit(); + +没有 + +> 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` + +> beforeConfigure(); + +没有 + +> configure(); + +调用里面的`configure`方法 + +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); +} +``` + +获取所有`UserDetailsService`和`PasswordEncoder`和`UserDetailsPasswordService`,使用`DaoAuthenticationProvider` +进行管理,然后添加到`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`管理`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`,进行匹配,当前的为 + +```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)); + } + +} +``` + +也就是`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` + +```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); +} +``` + +这个一看就是标准 aop + +###### 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; + } +} +``` + +`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`的授权方法 + +```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); +} +``` + +前提条件必须开启`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()` + +```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; +} +``` + +`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` + +首先创建对象给属性赋值 + +```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); +} +``` + +> 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"); + } +} +``` + +`loginProcessingUrl(this.loginPage);` + +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`中的一个.这个默认就是当前设置的值,不用理会 + +> updateAccessDefaults(http); + +里面默认为 false + +> registerDefaultAuthenticationEntryPoint(http); + +获取上面`Configurer`里面的`ExceptionHandlingConfigurer` + +在`ExceptionHandlingConfigurer`中有俩个属性, `defaultEntryPointMappings`和`defaultDeniedHandlerMappings`, 基本看注释就能知道是做什么的 +,这个注释是 map 的 value 类上的注释 + +```java +/** + * 开始一个身份验证方案。 + 在调用该方法之前, ExceptionTranslationFilter将使用请求的目标URL填充HttpSession属性abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 + 实现应根据需要修改ServletResponse上的标头,以开始身份验证过程。 + */ +private LinkedHashMap defaultEntryPointMappings=new LinkedHashMap<>(); + +/** + * 处理拒绝访问失败。 + */ +private LinkedHashMap defaultDeniedHandlerMappings=new LinkedHashMap<>(); +``` + +我们这里的是添加`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`中, 代码是这一段 + +```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); +} +``` + +接着我们打开`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); + } +} +``` + +发现最后还是到了 + +```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); +} +``` + +里面就有`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); +} +``` + +基本上流程就完成了,请求先走过滤器,然后走不同的 filter,报错就到了这一步,进行错误处理,其余都基本一致了 + +## 备注 + +剩下的一些处理基本和上面这个流程一致,还有几个注解需要注意下 + +`@CsrfToken` + +`@CurrentSecurityContext` + +`@AuthenticationPrincipal` + +这三个注解是在`WebMvcSecurityConfiguration`类进行处理的,只要启动了`@EnableWebSecurity`注解,就会启动 + +```java + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@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; + +} +``` + +使用方法 + +```java +@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); +} +``` + +里面的`SpringWebMvcImportSelector`类注入了`WebMvcSecurityConfiguration`,这就是 springmvc 中`HandlerMethodArgumentResolver` +的处理,也就是参数的处理,比如我们添加的`@PathVariable`,`@RequestBody`等,都是`HandlerMethodArgumentResolver` +的实现类处理的,当然还有`HandlerMethodReturnValueHandler`,这些就是`DispatcherServlet`里面的处理了 diff --git a/images/SpringSecurity/img-2023-6-7-_2.png b/images/SpringSecurity/img-2023-6-7-_2.png new file mode 100644 index 0000000..cdea10c Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7-_2.png differ diff --git a/images/SpringSecurity/img-2023-6-7-_3.png b/images/SpringSecurity/img-2023-6-7-_3.png new file mode 100644 index 0000000..d6001fd Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7-_3.png differ diff --git a/images/SpringSecurity/img-2023-6-7_0.png b/images/SpringSecurity/img-2023-6-7_0.png new file mode 100644 index 0000000..531a925 Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7_0.png differ diff --git a/images/SpringSecurity/img-2023-6-7_1.png b/images/SpringSecurity/img-2023-6-7_1.png new file mode 100644 index 0000000..df7ecd9 Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7_1.png differ