Java Spring WebSecurity HttpSecurity

Spring Security 5.4版本带来的新玩法

1. 前言

在以往Spring Security的中自定义配置都是声明一个配置类WebSecurityConfigurerAdapter,然后覆写(@Override)对应的几个方法就行了。然而这一切在Spring Security 5.4开始就得到了改变,从Spring Security 5.4 起不需要继承WebSecurityConfigurerAdapter就可以配置HttpSecurity了。相关的说明原文:

  • Remove need for WebSecurityConfigurerAdapter #8805
  • Configure HTTP Security without extending WebSecurityConfigurerAdapter #8804

    2. 新的配置方式

    旧的配置方式目前依然有效:

    1. @Configuration
    2. static class SecurityConfig extends WebSecurityConfigurerAdapter {
    3. @Override
    4. protected void configure(HttpSecurity http) throws Exception {
    5. http
    6. .antMatcher("/**")
    7. .authorizeRequests(authorize -> authorize
    8. .anyRequest().authenticated()
    9. );
    10. }
    11. }

    5.4.x版本有新的选择:

    1. @Bean
    2. SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    3. return http
    4. .antMatcher("/**")
    5. .authorizeRequests(authorize -> authorize
    6. .anyRequest().authenticated()
    7. )
    8. .build();
    9. }

    这种JavaConfig的方式看起来更加清爽舒服,而且和适配器解耦了。上面filterChain方法的参数是HttpSecurity类型。熟悉@Bean注解的同学应该会意识到一定有一个HttpSecurity类型的Spring Bean。在HttpSecurityConfiguration中有一个这样的Bean:

    1. @Bean({"org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity"})
    2. @Scope("prototype")
    3. HttpSecurity httpSecurity() throws Exception {
    4. // 省略掉
    5. return http;
    6. }

    初始化的内容已经忽略掉,注意到HttpSecurity@Scope("prototype")标记。也就是这个HttpSecurityBean不是单例的,每一次请求都会构造一个新的实例。这个设定非常方便构建多个互相没有太多关联的SecurityFilterChain,进而能在一个安全体系中构建相互隔离的安全策略。比如后端管理平台用Session模式,前台应用端用Token模式。
    Spring Security中WebSecurity和HttpSecurity的关系 - 图1
    多个SecurityFilterChain

    3. 原理

    Spring Security 有一个名为springSecurityFilterChain默认的过滤器链类(实际位置就是上图的Bean Filter位置),其类型为FilterChainProxy, 它代理了所有的SecurityFilterChain,关键的代理注入代码:

    1. for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
    2. this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
    3. for (Filter filter : securityFilterChain.getFilters()) {
    4. if (filter instanceof FilterSecurityInterceptor) {
    5. this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
    6. break;
    7. }
    8. }
    9. }

    那么this.securityFilterChains来自哪里呢?

    1. @Autowired(required = false)
    2. void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
    3. securityFilterChains.sort(AnnotationAwareOrderComparator.INSTANCE);
    4. this.securityFilterChains = securityFilterChains;
    5. }

    到这里就一目了然了吧,SecurityFilterChain类型的Bean会被加载到this.securityFilterChains中。

    HttpSecurity的本质

    在Spring Security 5.4的新玩法中介绍了一种新的配置HttpSecurity的方式:

    1. @Bean
    2. SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    3. return http
    4. .antMatcher("/**")
    5. .authorizeRequests(authorize -> authorize
    6. .anyRequest().authenticated()
    7. )
    8. .build();
    9. }

    其实就能够知道HttpSecurity是用来构建包含了一系列过滤器链的过滤器SecurityFilterChain,平常的配置就是围绕构建SecurityFilterChain进行。继续看这张图:
    Spring Security中WebSecurity和HttpSecurity的关系 - 图2
    安全过滤链
    从上面这个图中可以看出构建好的还要交给FilterChainProxy来代理,是不是有点多此一举?

    WebSecurity的本质

    在有些情况下这种确实多此一举, 不过更多时候可能需要配置多个SecurityFilterChain来实现对多种访问控制策略。
    Spring Security中WebSecurity和HttpSecurity的关系 - 图3
    多个SecurityFilterChain
    为了精细化的管理多个SecurityFilterChain的生命周期,搞一个统一管理这些SecurityFilterChain的代理就十分必要了,这就是WebSecurity的意义。下面是WebSecuritybuild方法的底层逻辑:

    1. @Override
    2. protected Filter performBuild() throws Exception {
    3. Assert.state(!this.securityFilterChainBuilders.isEmpty(),
    4. () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
    5. + "Typically this is done by exposing a SecurityFilterChain bean "
    6. + "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
    7. + "More advanced users can invoke " + WebSecurity.class.getSimpleName()
    8. + ".addSecurityFilterChainBuilder directly");
    9. // 被忽略请求的个数 和 httpscurity的个数 构成了过滤器链集合的大小
    10. int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
    11. List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
    12. // 初始化过滤器链集合中的 忽略请求过滤器链
    13. for (RequestMatcher ignoredRequest : this.ignoredRequests) {
    14. securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    15. }
    16. // 初始化过滤器链集合中的 httpsecurity定义的过滤器链
    17. for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
    18. securityFilterChains.add(securityFilterChainBuilder.build());
    19. }
    20. FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
    21. if (this.httpFirewall != null) {
    22. // 请求防火墙
    23. filterChainProxy.setFirewall(this.httpFirewall);
    24. }
    25. if (this.requestRejectedHandler != null) {
    26. // 请求拒绝处理器
    27. filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
    28. }
    29. filterChainProxy.afterPropertiesSet();
    30. Filter result = filterChainProxy;
    31. if (this.debugEnabled) {
    32. this.logger.warn("\n\n" + "********************************************************************\n"
    33. + "********** Security debugging is enabled. *************\n"
    34. + "********** This may include sensitive information. *************\n"
    35. + "********** Do not use in a production system! *************\n"
    36. + "********************************************************************\n\n");
    37. result = new DebugFilter(filterChainProxy);
    38. }
    39. this.postBuildAction.run();
    40. return result;
    41. }

    从上面中的源码可以看出,WebSecurity用来构建一个名为springSecurityFilterChainSpring BeanFilterChainProxy。它的作用是来定义哪些请求忽略安全控制,哪些请求必须安全控制,在合适的时候清除SecurityContext以避免内存泄漏,同时也可以用来定义请求防火墙和请求拒绝处理器,另外开启Spring Seuciry Debug模式也是这里配置的。
    同时还有一个作用可能是其它文章没有提及的,FilterChainProxy是Spring Security对Spring framework应用的唯一出口,然后通过它与一个Servlet在Spring的桥接代理DelegatingFilterProxy结合构成Spring对Servlet体系的唯一出口。这样就将Spring Security、Spring framework、Servlet API三者隔离了起来。

    总结

    事实上可以认为,WebSecurity是Spring Security对外的唯一出口,而HttpSecurity只是内部安全策略的定义方式;WebSecurity对标FilterChainProxy,而HttpSecurity则对标SecurityFilterChain,另外它们的父类都是AbstractConfiguredSecurityBuilder。掌握了这些基本上就能知道它们之间的区别是什么了。