SpringSecurity 流程补充

注意:

  1. 基于 spring-boot-dependencies:2.7.7
  2. 首先需要了解 springboot2.7 升级 Changes to Auto-configuration 以后使用 autoconfigure 进行自动注入
  3. 代码地址 io.github.poo0054

启动

我们每次添加 <artifactId>spring-boot-starter-security</artifactId>,启动的时候会有一条类似的日志:

Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430

This generated password is for development use only. Your springSecurity configuration must be updated before running your
application in production.

找到 UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager 类,它是 springboot 自动装配的。

下面这些都是 springboot 自动装配类,在 spring-boot-autoconfigure-2.7.7.jar > META-INF > spring > org.springframework.boot.autoconfigure.AutoConfiguration.imports 中。这些类就是 Spring Security 的全部了。

org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration
..........
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration

SecurityAutoConfiguration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

/**
 * {@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 个类 SpringBootWebSecurityConfigurationSecurityDataConfigurationSecurityDataConfiguration是 Spring springSecurity 与 Spring 数据的集成,暂时不做讲解,重点是SpringBootWebSecurityConfiguration

SpringBootWebSecurityConfiguration

这个类就是一个 Configuration 类,条件必须为 @ConditionalOnWebApplication(type = Type.SERVLET) 才会注入

SecurityFilterChainConfiguration

其中第一个子类SecurityFilterChainConfiguration添加了@ConditionalOnDefaultWebSecurity,这个类有个注解 @Conditional(DefaultWebSecurityCondition.class),而DefaultWebSecurityCondition类继承了AllNestedConditions

所以下面代码就是判断该类是否生效,如果不存在SecurityFilterChainWebSecurityConfigurerAdapter 的 bean,就生效。创建默认的SecurityFilterChain

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * {@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。 用于拦截错误调度,以确保对错误页面的授权访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
   /**
* 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

在类中还存在SecurityAutoConfigurationbean,这个是属于 spring 的发布订阅。改装一下,就是 springSecurity 的成功和失败事件,可以订阅失败后的一些处理,如日志打印等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
 * @author Luke Taylor
 * @since 3.0
 */
public interface AuthenticationEventPublisher {

    void publishAuthenticationSuccess(Authentication authentication);

    void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication);

}

UserDetailsServiceAutoConfiguration

注入条件

1
2
3
4
5
6
7
8
9

@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

这个类是继承了AbstractFilterRegistrationBeanFilterRegistrationBean也是继承的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 image.png DelegatingFilterProxyRegistrationBeanFilterRegistrationBean都是继承的RegistrationBean,而RegistrationBean 又是ServletContextInitializer的实现类。其中void onStartup(ServletContext servletContext)方法是关键。 在javax.servlet 中,存在这样一个类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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();方法。

找到实现类ServletWebServerApplicationContextonRefresh()方法。里面有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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

@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 AutowireBeanFactoryObjectPostProcessorSmartInitializingSingletonDisposableBean拿出来,使用自己的destroy()afterSingletonsInstantiated()执行

List<SecurityConfigurer<Filter, WebSecurity» webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory).getWebSecurityConfigurers();

AutowiredWebSecurityConfigurersIgnoreParents也就是获取所有的WebSecurityConfigurerAdapter

这里有几个类需要了解SecurityConfigurerSecurityBuilder

先了解一下结构 img_1.png

img_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的实现类AbstractConfiguredSecurityBuilderdoBuild()方法进行很多别的操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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,里面的

1
2
3
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()

这个里面最关键的也就是这个了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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();

首先使用根据获取到的securityFilterChainsset 入WebSecurity#securityFilterChainBuilders的 List 属性

这里有个需要注意的地方,如果你继承了WebSecurityConfigurerAdapterthis.securityFilterChains 就是一个空的。

而且会由WebSecurityConfigurerAdapter#getHttp()进行创建WebSecurity 。这就跟 spring 的 order 有关了。 @Order(SecurityProperties.BASIC_AUTH_ORDER)

其中SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration有一个注解@ConditionalOnDefaultWebSecurity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

@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的注入

继续回到上面,

1
2
3
4
if (filter instanceof FilterSecurityInterceptor) {
    this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
    break;
}

FilterSecurityInterceptor也在这里进行处理,也就是SecurityMetadataSource元数据

然后自定义的WebSecurityCustomizer也在这里。可以自行改变webSecurity

1
2
3
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
    customizer.customize(this.webSecurity);
}

接下来就是构建了,来到 AbstractConfiguredSecurityBuilder#doBuild()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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. 添加入securityFilterChainrequestMatcherPrivilegeEvaluatorsEntries
  3. 创建出FilterChainProxybean 的名称为springSecurityFilterChain (重点)

剩下的都是一些创建一些 bean 了。

SecurityExpressionHandler: 默认为DefaultWebSecurityExpressionHandler类(Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离)

WebInvocationPrivilegeEvaluator: 为 WebSecurity#performBuild()中创建的 requestMatcherPrivilegeEvaluatorsEntries 使用RequestMatcherDelegatingWebInvocationPrivilegeEvaluator包装。(允许用户确定他们是否具有给定 web URI 的特权。)

这俩个类都是很重要的。 一个是解析器,一个是判断 uri 是否合格的类。 后面单独讲

HttpSecurityConfiguration

接下来到了HttpSecurityConfiguration

根据上面WebSecurityConfiguration,可以得出。WebSecurityConfiguration创建WebSecurityWebSecurity 创建了FilterChainProxy的 bean。

HttpSecurityConfiguration创建HttpSecurity。 而在SecurityFilterChainConfiguration类中,使用HttpSecurity 创建了SecurityFilterChain。这也就是我们使用了WebSecurityConfigurerAdapter。为什么会存在SecurityFilterChain 类的原因。是SecurityFilterChainConfiguration#defaultSecurityFilterChain创建了一个SecurityFilterChain

得出结论,FilterChainProxy持有SecurityFilterChain。而DelegatingFilterProxyRegistrationBean又持有FilterChainProxy

DelegatingFilterProxyRegistrationBean->FilterChainProxy->SecurityFilterChain

其实到了这一步。后面的就是我们自己编写的代码了比如

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@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来举例

1
2
3
4
5
6
7
8
@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 是如何包装顺序的。就在这个类里面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 就会使用这个里面的序号。如果我们有自定义的类,也要提前加载到里面去,不然就会

1
2
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 中添加了一个ExceptionTranslationFilterfilter.主要就是SecurityConfigurer 的俩个方法。先调用init(B builder),然后configure(B builder)

后面都是一样,就跳过了

applyDefaultConfigurers(http);

这里的这一句,就是从"META-INF/spring.factories" 中加载并实例化给定类型的工厂实现 SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader) 然后调用http.apply(configurer); 添加到configurers里面

接下来回到SecurityFilterChainConfiguration

http.authorizeRequests().anyRequest().authenticated();

首先添加了http.authorizeRequests() 然后调用 return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();。 先把ExpressionUrlAuthorizationConfigurer放入 config 中,返回一个调用了getRegistry()。 也就是ExpressionInterceptUrlRegistry类。

后面调用的.anyRequest(),也就是AbstractRequestMatcherRegistry#anyRequest()。先了解一下结构图

img_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),其中 requestMatchersAnyRequestMatcher.INSTANCE;也就是AnyRequestMatcher对象。里面matches(HttpServletRequest request)直接返回 ture
  3. authenticated()也就是授权,ExpressionInterceptUrlRegistry#addMapping。里面放入了一个UrlMappingUrlMapping 的俩个属性,一个是AnyRequestMatcher(所有请求),configAttrs表示SecurityConfigSecurityConfig 的值为private static final String authenticated = "authenticated"

http.formLogin();

创建了一个FormLoginConfigurer,也就是SecurityConfigurer。关注initconfigure方法。后面统一讲解

http.httpBasic();

HttpBasicConfigurer

http.build()

进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入AbstractConfiguredSecurityBuilder#doBuild()方法 beforeInit();: 还是没有什么

init(): 调用里面所有的configurers里面的init方法,后面HttpSecurity#doBuild统一讲解,先把流程捋一遍

接下来SecurityFilterChain就已经创建好了,看一下里面的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@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()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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

1
2
3
for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
    authBuilder.apply(config);
}

需要注意的是,this.globalAuthConfigurers就是上面三个类,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@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,在无参构造方法里面。而UserDetailsServiceDaoAuthenticationProvider 是同一个,也就是在UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager这里创建的。里面继承了,所以是同一个
  3. InitializeAuthenticationProviderBeanManagerConfigurer#init 跟 2 一样,apply 了一个InitializeAuthenticationProviderManagerConfigurer

beforeConfigure();

没有

configure();

调用里面的configure方法

2: InitializeUserDetailsManagerConfigurer#configure方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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);
}

获取所有UserDetailsServicePasswordEncoderUserDetailsPasswordService,使用DaoAuthenticationProvider 进行管理,然后添加到AuthenticationManagerBuilder#authenticationProviders

3: InitializeAuthenticationProviderManagerConfigurer#configure方法,把 spring 中的所有AuthenticationProvider 添加到AuthenticationManagerBuilder#authenticationProviders

然后又到了熟悉的AuthenticationManagerBuilder#performBuild

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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管理authenticationProvidersparentAuthenticationManager,这里的eraseCredentialsCredentialsContainer类有关,也就是敏感数据。接着的eventPublisher就是发布订阅了,默认会创建的 然后providerManager = postProcess(providerManager);就是注入 spring 容器中,接着返回 ,这里返回的其实是ProviderManager对象了,接着就是到了HttpSecurity的创建了,后面HttpSecurity#doBuild() 时候再讲HttpSecurity的构建

这里面的LazyPasswordEncoder这个类也很有意思,手动制造一个懒加载类

@EnableGlobalMethodSecurity

这里有个很坑的地方,里面的prePostEnabledsecuredEnabled这些属性,不是直接在GlobalMethodSecuritySelector 中进行处理的,放在了GlobalMethodSecuritySelector#methodSecurityMetadataSource 这个 bean 里面进行处理,然后开启prePostEnabled之后,就会加载PrePostAnnotationSecurityMetadataSource类。 这个我找了半天,后面无意中才发现

这个注解也添加了@EnableGlobalAuthentication注解

主要是看GlobalMethodSecuritySelector类 里面加载了AutoProxyRegistrar,这个就是 springaop 的类,创建代理对象的一个类。会创建InfrastructureAdvisorAutoProxyCreator 类来创建代理对象。关键是GlobalMethodSecurityConfigurationMethodSecurityMetadataSourceAdvisorRegistrar这俩个类

MethodSecurityMetadataSourceAdvisorRegistrar类里面都用到了GlobalMethodSecurityConfiguration。我就放在一起了

MethodSecurityMetadataSourceAdvisorRegistrar

这个类里面就是往 spring 中注册了一个MethodSecurityMetadataSourceAdvisor对象

MethodSecurityMetadataSourceAdvisor

这个类就是PointcutAdvisor,使用AbstractAutoProxyCreator创建代理对象的是,会获取Pointcut 来判断是否需要代理对象,然后使用Advice来进行其余操作。这是 springaop 的内容就不过多讲解了

aop 首先获取pointcut,进行匹配,当前的为

1
2
3
4
5
6
7
8
9
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));
    }

}

也就是StaticMethodMatcherPointcutClassFilter 默认都是 true,方法匹配为MethodSecurityMetadataSourceAdvisor#attributeSource进行匹配。而methodSecurityMetadataSource 是在GlobalMethodSecurityConfiguration#methodSecurityMetadataSource里面进行创建的。这俩个类后面讲。只要匹配成功就和 aop 一样流程了

这里的Advice就是MethodSecurityInterceptor类。在GlobalMethodSecurityConfiguration#methodSecurityInterceptor中创建

MethodSecurityInterceptor

isPrePostEnabled

添加PrePostAnnotationSecurityMetadataSource类,主要关注getAttributes方法后面会讲。 这里面就是我们常用的注解了,然后构建成ConfigAttribute并返回。里面的构建主要用的是PrePostInvocationAttributeFactory 的实现,只有一个实现

isSecuredEnabled

这个就是@Secured注解的处理。 逻辑基本和上面一样

最后返回一个DelegatingMethodSecurityMetadataSource对象,就是MethodSecurityInterceptor中用到的对象

匹配成功的 aop 都会进入MethodSecurityInterceptor#invoke

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@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)

这个里面就有授权了,Authorizationauthentication不一样,一个是认证一个是授权。这个是授权,简单说就是角色

Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);

接着来到了DelegatingMethodSecurityMetadataSource#getAttributes

1
2
3
4
5
6
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的授权方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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;
}

CsrfConfigurerExceptionHandlingConfigurerHeadersConfigurerSessionManagementConfigurerSecurityContextConfigurerRequestCacheConfigurerAnonymousConfigurerServletApiConfigurerDefaultLoginPageConfigurerLogoutConfigurer 还有我们添加到META-INF/spring.factories中的AbstractHttpConfigurer.class

接着回到这里, 我们是自定义了一个SecurityFilterChain,所以在这里面进行构建

首先 http.formLogin();添加了FormLoginConfigurer

http.authorizeRequests() 添加了ExpressionUrlAuthorizationConfigurer,这个只有configure 没有init

http.csrf() 添加了CsrfConfigurer

http.userDetailsService(userDetailsService()) 添加了一个自定义的UserDetailsService

FormLoginConfigurer

也是一个SecurityConfigurer

首先创建对象给属性赋值

1
2
3
authFilter = UsernamePasswordAuthenticationFilter()
loginPage = "/login"
this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);

接着来到init

1
2
3
4
5
6
@Override
public void init(B http) throws Exception {
    updateAuthenticationDefaults();
    updateAccessDefaults(http);
    registerDefaultAuthenticationEntryPoint(http);
}

updateAuthenticationDefaults();

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/**
 * 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也就是UsernamePasswordAuthenticationFilterRequestMatcher 设置为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中有俩个属性, defaultEntryPointMappingsdefaultDeniedHandlerMappings, 基本看注释就能知道是做什么的 ,这个注释是 map 的 value 类上的注释

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
 * 开始一个身份验证方案。
 在调用该方法之前, ExceptionTranslationFilter将使用请求的目标URL填充HttpSession属性abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。
 实现应根据需要修改ServletResponse上的标头,以开始身份验证过程。
 */
private LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> defaultEntryPointMappings=new LinkedHashMap<>();

/**
 * 处理拒绝访问失败。
 */
private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings=new LinkedHashMap<>();

我们这里的是添加defaultDeniedHandlerMappingskeyRequestMatcher,是否匹配。 value是匹配成功就执行 这个类里面也是只有configure(),没有init(), 后面讲解

先说里面的value,就是当前类的this.authenticationEntryPoint 也就是创建类时候的LoginUrlAuthenticationEntryPoint

key就是AndRequestMatcher,但是里面聚合了俩个RequestMatcher, 一个是MediaTypeRequestMatcher ,还有一个是NegatedRequestMatcher

其实到了这一步,我们只需要了解其中一个,剩下的都大同小异了

举例: ExceptionHandlingConfigurer

ExceptionHandlingConfigurer这个类就是刚刚在FormLoginConfigurer 中处理的那个,往这个里面添加了defaultEntryPointMappings属性

然后我们找到ExceptionHandlingConfigurer中的configure(H http)方法, 里面就是创建了一个ExceptionTranslationFilter 过滤器,添加到了http中, 代码是这一段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@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) 方法,

1
2
3
4
5
6
7
8
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);
    }
}

发现最后还是到了

1
2
3
4
5
6
7
8
9
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@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注解,就会启动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

@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;

}

使用方法

1
2
3
4
5
@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里面的处理了