spring权限设计

539 阅读4分钟

「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战」

前言

  • 前面我们学习了springboot中如何创建DelegatingFilterProxy的过程。哪里我们只是简单介绍springboot方式如何向servlet创建Filter 。 那篇文章主要介绍了如何注册。本文我们就当详细剖析下创建流程

入口

  • 在上面我们知道DelegatingFilterProxyRegistrationBean注册了DelegatingFilterProxy。而真正创建过滤器的地方是在WebSecurityConfiguration中会创建名叫springSecurityFilterChain的Filter

webSecurityConfigures

  • private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
    
  • 他就是一个List里面存储了SecurityConfigurer配置。他的加载在WebSecurityConfiguration#setFilterChainProxySecurityConfigurer方法中。

  • 在方法中通过@Value方式注入了我们获取配置的方法。

public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
   List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
   Map<String, WebSecurityConfigurer> beansOfType = beanFactory
         .getBeansOfType(WebSecurityConfigurer.class);
   for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
      webSecurityConfigurers.add(entry.getValue());
   }
   return webSecurityConfigurers;
}
  • beanFactory#getBeansOfType获取的是spring容器中所有WebSecurityConfigurer本身及其子类的bean 。而我们security的配置类正是继承了WebSecirityConfigurerAdapter

  • 我们通过类图又知道WebSecurityConfigurerAdapterWebSecurityConfigurer的实现类。所有在getWebSecurityConfigurers中获取到的信息正式我们security的配置类。

  • 而在setFilterChainProxySecurityConfigurer中除了初始化我们需要的webSecurityConfigurers以外还会初始化我们另外一个参数webSecurity。他的创建时通过objectPostProcessor对普通类完成spring bean的转换的,这个我们之前也提到过。

for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
   webSecurity.apply(webSecurityConfigurer);
}
  • 最后会将webSecurityConfigurer赋值给webSecurity

回到主线

  • 上面我们穿插了下说明webSecurityConfigurers属性的加载实际上就是我们自定义的SecurityConfig 。期间同时完成了webSecurity的初始化。下面就开始了我们webSecurity.build操作

  • build操作实际上就是WebSecurity完成的,只不过有些方法依赖于父类进行调度

  • 根据这个时序图我们可以明显看出最终核心在其父类的initconfigureperformBuild三个方法中。

protected final O doBuild() throws Exception {
   synchronized (configurers) {
      buildState = BuildState.INITIALIZING;
      beforeInit();
      init();
      buildState = BuildState.CONFIGURING;
      beforeConfigure();
      configure();
      buildState = BuildState.BUILDING;
      O result = performBuild();
      buildState = BuildState.BUILT;
      return result;
   }
}
  • AbstractConfiguredSecurityBuilder#doBuild中我们可以看到有四种状态用来标识建造状态。其中before开头的两个方法交由子类实现也就是有WebSecurity,追踪下源码发现WebSecurity并未实现,所以这两个方法我们暂时忽略

INITIALIZING

  • 首先进入的就是initializing状态。这个期间我们执行的init(); 源码点进去看看执行逻辑。
private void init() throws Exception {
   Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

   for (SecurityConfigurer<O, B> configurer : configurers) {
      configurer.init((B) this);
   }

   for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
      configurer.init((B) this);
   }
}
  • 不难看出首先是遍历WebSecurityConfigurer#init。由上面我们也知道实际就是我们SecurityConfig配置类。最终的init也是由WebSecurityConfigurerAdapter来完成的。

  • 关于这段代码,他的作用就是构建一个HttpSecurity赋值给WebSecurity并对他添加一个拦截器。

  • SecurityConfig中我们配置了相关测试信息。那么在WebSecurityConfigurerAdapter中的getHttp一定是依据我们构建的信息进行构建HttpSecurity对象。

  • 进入init之后就能发现两个变量localConfigureAuthenticationBldrauthenticationBuilder 。有趣的是这两个变量是相同的类定义的。为什么要多此一举呢?这里买个坑以后有时间回来补!

  • 还是在WebSecurityConfigurerAdapter中设置上下文的地方对这两个变量进行了初始化

@Autowired
public void setApplicationContext(ApplicationContext context) {
   this.context = context;

   ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
   LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);

   authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
   localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
      @Override
      public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
         authenticationBuilder.eraseCredentials(eraseCredentials);
         return super.eraseCredentials(eraseCredentials);
      }

   };
}
  • 两者不同之处就是后者会复用前者的eraseCredentials方法。
  • 让我们回到getHttp方法内部。首先是设置DefaultAuthenticationEventPublisher。然后是AuthenticationManager . 关于AuthenticationManager我们可以理解成认证管理器。在权限功能中认证是至关重要的,所以关于authenticationManager方法我们是必须点进去一探究竟的。
protected AuthenticationManager authenticationManager() throws Exception {
   if (!authenticationManagerInitialized) {
      configure(localConfigureAuthenticationBldr);
      if (disableLocalConfigureAuthenticationBldr) {
         authenticationManager = authenticationConfiguration
               .getAuthenticationManager();
      }
      else {
         authenticationManager = localConfigureAuthenticationBldr.build();
      }
      authenticationManagerInitialized = true;
   }
   return authenticationManager;
}
  • 首先根据authenticationManagerInitialized属性判断是否已经初始化过,如果是第一次初始化那么就会进入下面逻辑否则直接返回已有对象。
  • 首当其冲就是执行configure(AuthenticationManagerBuilder)方法,这里对应我们securityConfig中的代码
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("zxhtom").password(new BCryptPasswordEncoder().encode("123456")).roles("admin");
}
  • 因为我们重写了configure(AuthenticationManagerBuilder)方法,所以父类WebSecurityConfigurerAdapter的默认方法就会失效
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   this.disableLocalConfigureAuthenticationBldr = true;
}
  • 所以disableLocalConfigureAuthenticationBldr=false。 所以直接执行localConfigureAuthenticationBldr.build();