You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
source-code-hunter/docs/SpringSecurity/SpringSecurity流程补充.md

1285 lines
52 KiB

# 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)
## 启动
我们每次添加 `<artifactId>spring-boot-starter-security</artifactId>`,启动的时候会有一条类似的日志:
```
Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430
This generated password is for development use only. Your springSecurity configuration must be updated before running your
application in production.
```
找到 `UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager` 类,它是 springboot 自动装配的。
下面这些都是 springboot 自动装配类,在 `spring-boot-autoconfigure-2.7.7.jar` > META-INF > spring > org.springframework.boot.autoconfigure.AutoConfiguration.imports 中。这些类就是 Spring Security 的全部了。
```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> errorPageSecurityFilter(ApplicationContext context) {
FilterRegistrationBean<ErrorPageSecurityFilter> 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<DispatcherType> 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<Class<?>> 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);`这样每次请求 servlettomcat 就会先使用 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<SecurityConfigurer<Filter, WebSecurity>> 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<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> 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<Filter, WebSecurity> 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<RequestMatcher> requestMatchers)` -> `ExpressionUrlAuthorizationConfigurer#chainRequestMatchersInternal(List<RequestMatcher> 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.
* <p>
* Used to configure a {@code FilterChainProxy}.
*
* @author Luke Taylor
* @since 3.1
*/
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
```
肯定是先匹配,如果成功了,就返回里面所有的 filter 进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求
`SecurityAutoConfiguration`类就已经大致讲完了,
### @EnableGlobalAuthentication
当前注解在`@EnableSecurity`中会自动加上
#### @Import(AuthenticationConfiguration.class)
`AuthenticationConfiguration`上面`@Import(ObjectPostProcessorConfiguration.class)`。 以前使用的`ObjectPostProcessor`
就是在这里注入的,注入`AutowireBeanFactoryObjectPostProcessor`对象
#### AuthenticationManagerBuilder
```java
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> 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<ConfigAttribute> 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<AccessDecisionVoter<?>>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<B> 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<RequestMatcher, AuthenticationEntryPoint> defaultEntryPointMappings=new LinkedHashMap<>();
/**
* 处理拒绝访问失败。
*/
private LinkedHashMap<RequestMatcher, AccessDeniedHandler> 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`里面的处理了