我们自定义的SecurityConfig都是继承自WebSecurityConfigurerAdapter,今天我们就来看看WebSecurityConfigurerAdapter内部的工作原理以及配置原理。
WebSecurityConfigurerAdapter的继承关系图:
深入理解WebSecurityConfigurerAdapter(源码篇) - 图1
在这层继承关系图中,有两个非常重要的类:SecurityBuilderSecurityConfigurer
这两个类在之前的深入理解Spring Security配置与构建(源码篇)文章中已经介绍过了,所以关于这两个类就不再赘述,直接从WebSecurityConfigurer类开始。

1、WebSecurityConfigurer

WebSecurityConfigurer其实是一个空接口,只是它里面约束了一些泛型,如下:

  1. public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
  2. SecurityConfigurer<Filter, T> {
  3. }

它里面的泛型很关键,这关乎到WebSecurityConfigurer的目的是啥!

  1. SecurityBuilder中的泛型是Filter,表示SecurityBuilder最终的目的是为了构建一个Filter对象出来。
  2. SecurityConfigurer中两个泛型,第一个表示的含义也是SecurityBuilder最终构建的对象。

同时,这里还定义一个新的泛型TT需要继承自SecurityBuilder,根据WebSecurityConfigurerAdapter中的定义,可以知道T就是SecurityBuilder的子类WebSecurity
所以,WebSecurityConfigurer的目的可以理解为就是为了配置WebSecurity

2、WebSecurity

WebSecurity定义如下:

  1. public final class WebSecurity extends
  2. AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
  3. SecurityBuilder<Filter>, ApplicationContextAware {
  4. }

WebSecurity继承自AbstractConfiguredSecurityBuilder<Filter, WebSecurity>类同时实现了SecurityBuilder接口。
其实在前面的源码介绍中就已经介绍过了,我们再来看看。

2.1、AbstractConfiguredSecurityBuilder

首先,AbstractConfiguredSecurityBuilder中定义了一个枚举类,将整个构建过程分为5种状态,也可以理解为构建过程生命周期的五个阶段,如下:

  1. private enum BuildState {
  2. UNBUILT(0),
  3. INITIALIZING(1),
  4. CONFIGURING(2),
  5. BUILDING(3),
  6. BUILT(4);
  7. private final int order;
  8. BuildState(int order) {
  9. this.order = order;
  10. }
  11. public boolean isInitializing() {
  12. return INITIALIZING.order == order;
  13. }
  14. public boolean isConfigured() {
  15. return order >= CONFIGURING.order;
  16. }
  17. }

五种状态分别是UNBUILT``INITIALIZING``CONFIGURING``BUILDING以及BUILT。另外还提供了两个判断方法,isInitializing判断是否正在初始化阶段,isConfigured表示是否已经构建完成。
AbstractConfiguredSecurityBuilder中的方法比较多,在这里只列出两个关键的方法:

  1. 第一个方法就是这个add方法,这相当于在收集所有的配置。将所有的xxxConfigurer收集起来存储到configurers中,将来再统一初始化并配置。configurers本身是一个LinkedHashMap,key 是配置类的 class,value 是xxxConfigurer配置类集合。当需要对这些配置类进行集中配置的时候,会通过getConfigurers方法获取配置类集合,这个获取过程就是就是把LinkedHashMap中的 value 拿出来,放到一个集合中返回。

    1. private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
    2. Assert.notNull(configurer, "configurer cannot be null");
    3. Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
    4. .getClass();
    5. synchronized (configurers) {
    6. if (buildState.isConfigured()) {
    7. throw new IllegalStateException("Cannot apply " + configurer
    8. + " to already built object");
    9. }
    10. List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
    11. .get(clazz) : null;
    12. if (configs == null) {
    13. configs = new ArrayList<>(1);
    14. }
    15. configs.add(configurer);
    16. this.configurers.put(clazz, configs);
    17. if (buildState.isInitializing()) {
    18. this.configurersAddedInInitializing.add(configurer);
    19. }
    20. }
    21. }
    22. private Collection<SecurityConfigurer<O, B>> getConfigurers() {
    23. List<SecurityConfigurer<O, B>> result = new ArrayList<>();
    24. for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
    25. result.addAll(configs);
    26. }
    27. return result;
    28. }
  2. 另一个方法就是doBuild方法。在AbstractSecurityBuilder类中,构建的核心逻辑被放到doBuild方法上,但是AbstractSecurityBuilder类中只是定义了抽象的doBuild方法,真正的实现还是在该类中的doBuild方法。

    1. @Override
    2. protected final O doBuild() throws Exception {
    3. synchronized (configurers) {
    4. buildState = BuildState.INITIALIZING;
    5. beforeInit();
    6. init();
    7. buildState = BuildState.CONFIGURING;
    8. beforeConfigure();
    9. configure();
    10. buildState = BuildState.BUILDING;
    11. O result = performBuild();
    12. buildState = BuildState.BUILT;
    13. return result;
    14. }
    15. }
    16. private void init() throws Exception {
    17. Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    18. for (SecurityConfigurer<O, B> configurer : configurers) {
    19. configurer.init((B) this);
    20. }
    21. for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
    22. configurer.init((B) this);
    23. }
    24. }
    25. private void configure() throws Exception {
    26. Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    27. for (SecurityConfigurer<O, B> configurer : configurers) {
    28. configurer.configure((B) this);
    29. }
    30. }

    doBuild方法就是一边更新状态一边进行构建。
    beforeInit是一个预留方法,没有任何实现;init方法就是找到所有的xxxConfigurer,挨个调用其init方法进行初始化;beforeConfigure是一个预留方法,没有任何实现;configure方法就是找到所有的xxxConfigurer,挨个调用其configure方法进行配置;performBuild方法是真正的过滤器链构建方法,但是在当前AbstractConfiguredSecurityBuilder中的performBuild方法只是一个抽象方法,具体的实现在HttpSecurity类中。

    2.2、SecurityBuilder

    HttpSecurity实现SecurityBuilder时,传入的泛型是DefaultSecurityFilterChain,所以SecurityBuilder#build方法的功能就很明确,就是用来构建一个过滤器链出来,但是那个过滤器链是 Spring Security 中的。在WebSecurityConfigurerAdapter中定义的泛型是WebSecurity,所以最终构建的是一个普通的Filter,其实就是FilterChainProxy,关于FilterChainProxy,可以参考Spring Security过滤器架构(源码篇)文章。

    2.3、WebSecurity

    WebSecurity的核心逻辑集中在performBuild构建方法上:

    1. @Override
    2. protected Filter performBuild() throws Exception {
    3. Assert.state(
    4. !securityFilterChainBuilders.isEmpty(),
    5. () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
    6. + "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
    7. + "More advanced users can invoke "
    8. + WebSecurity.class.getSimpleName()
    9. + ".addSecurityFilterChainBuilder directly");
    10. int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
    11. List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
    12. chainSize);
    13. for (RequestMatcher ignoredRequest : ignoredRequests) {
    14. securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    15. }
    16. for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
    17. securityFilterChains.add(securityFilterChainBuilder.build());
    18. }
    19. FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
    20. if (httpFirewall != null) {
    21. filterChainProxy.setFirewall(httpFirewall);
    22. }
    23. filterChainProxy.afterPropertiesSet();
    24. Filter result = filterChainProxy;
    25. if (debugEnabled) {
    26. logger.warn("\n\n"
    27. + "********************************************************************\n"
    28. + "********** Security debugging is enabled. *************\n"
    29. + "********** This may include sensitive information. *************\n"
    30. + "********** Do not use in a production system! *************\n"
    31. + "********************************************************************\n\n");
    32. result = new DebugFilter(filterChainProxy);
    33. }
    34. postBuildAction.run();
    35. return result;
    36. }

    这里的performBuild方法只有一个功能,那就是构建FilterChainProxy

  3. 首先,统计过滤器链的总数,总条数包括两个方面,一个是ignoredRequests,这是忽略的请求,通过WebSecurity配置的忽略请求;另一个则是securityFilterChainBuilders,也就是通过HttpSecurity配置的过滤器链,有几个算几个。

  4. 创建securityFilterChains集合,并且遍历上面提到的两种类型的过滤器链,并将过滤器链放入securityFilterChains集合中。
  5. 在前面的深入理解Spring Security配置与构建(源码篇)文章中介绍过,HttpSecurity构建出来的过滤器链对象就是DefaultSecurityFilterChain,所以可以直接将build结果放入securityFilterChains集合中,而ignoredRequests中保存时则需要包装一下才可以存入securityFilterChains中。
  6. securityFilterChains集合有数据之后,创建一个FilterChainProxy
  7. 给新建的FilterChainProxy配置上防火墙。
  8. 最后我们返回的就是FilterChainProxy实例。

🎯从这段分析中,可以看出WebSecurityHttpSecurity的区别:

  1. HttpSecurity目的是构建过滤器链,一个HttpSecurity对象构建一条过滤器链,一个过滤器链中有N多个过滤器,HttpSecurity所做的事情实际上就是在配置这N个过滤器。
  2. WebSecurity目的是构建FilterChainProxy,一个FilterChainProxy中包含有多个过滤器链和一个Firewall

    3、WebSecurityConfigurerAdapter

    我们最后来看看WebSecurityConfigurerAdapter,由于WebSecurityConfigurer只是一个空接口,WebSecurityConfigurerAdapter就是针对这个空接口提供一个具体的实现,最终目的还是为了方便你配置WebSecurity
    WebSecurityConfigurerAdapter中的方法比较多,但是根据我们前面这么文章的分析,重要的方法就两个,一个是init方法,另一个就是configure(WebSecurity web),其他方法都是为这两个方法服务的。
    先看init方法:
    1. public void init(final WebSecurity web) throws Exception {
    2. final HttpSecurity http = getHttp();
    3. web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
    4. FilterSecurityInterceptor securityInterceptor = http
    5. .getSharedObject(FilterSecurityInterceptor.class);
    6. web.securityInterceptor(securityInterceptor);
    7. });
    8. }
    9. protected final HttpSecurity getHttp() throws Exception {
    10. if (http != null) {
    11. return http;
    12. }
    13. AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
    14. localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    15. AuthenticationManager authenticationManager = authenticationManager();
    16. authenticationBuilder.parentAuthenticationManager(authenticationManager);
    17. Map<Class<?>, Object> sharedObjects = createSharedObjects();
    18. http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
    19. sharedObjects);
    20. if (!disableDefaults) {
    21. // @formatter:off
    22. http
    23. .csrf().and()
    24. .addFilter(new WebAsyncManagerIntegrationFilter())
    25. .exceptionHandling().and()
    26. .headers().and()
    27. .sessionManagement().and()
    28. .securityContext().and()
    29. .requestCache().and()
    30. .anonymous().and()
    31. .servletApi().and()
    32. .apply(new DefaultLoginPageConfigurer<>()).and()
    33. .logout();
    34. // @formatter:on
    35. ClassLoader classLoader = this.context.getClassLoader();
    36. List<AbstractHttpConfigurer> defaultHttpConfigurers =
    37. SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
    38. for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
    39. http.apply(configurer);
    40. }
    41. }
    42. configure(http);
    43. return http;
    44. }
    45. protected void configure(HttpSecurity http) throws Exception {
    46. logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    47. http
    48. .authorizeRequests()
    49. .anyRequest().authenticated()
    50. .and()
    51. .formLogin().and()
    52. .httpBasic();
    53. }
    init方法可以算是这里的入口方法了:首先调用getHttp方法进行HttpSecurity的初始化。HttpSecurity的初始化,实际上就是配置了一堆默认的过滤器,配置完成后,最终还是调用了configure(http)方法,该方法又配置了一些过滤器,不过在实际的开发中,我们经常会重写configure(http)方法。HttpSecurity配置完成后,再将HttpSecurity放入WebSecuritysecurityFilterChainBuilders集合中。

configure(WebSecurity web)方法实际上是一个空方法,我们在实际开发中可能会重写该方法:

  1. public void configure(WebSecurity web) throws Exception {
  2. }