定义多个过滤器链

在 Spring Security 中可以同时存在多个过滤器链,一个 WebSecurityConfigurerAdapter 的实例就可以配置一条过滤器链。我们来看如下一个案例:

@Configuration
public class SecurityConfig {
    @Bean
    UserDetailsService us() {
        InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
        users.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
        return users;
    }
    @Configuration
    @Order(1)
    static class SecurityConfig01 extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
            users.createUser(User.withUsername("bar").password("{noop}123").roles("admin").build());
            http.antMatcher("/bar/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/bar/login")
                    .successHandler((req, resp, auth) -> {
                        resp.setContentType("application/json;charset=utf-8");
                        String s = new ObjectMapper().writeValueAsString(auth);
                        resp.getWriter().write(s);
                    })
                    .permitAll()
                    .and()
                    .csrf().disable()
                    .userDetailsService(users);
        }
    }

    @Configuration
    @Order(2)
    static class SecurityConfig02 extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("javagirl")
                    .password("{noop}123")
                    .roles("admin");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
            users.createUser(User.withUsername("foo").password("{noop}123").roles("admin").build());
            http.antMatcher("/foo/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/foo/login")
                    .successHandler((req, resp, auth) -> {
                        resp.setContentType("application/json;charset=utf-8");
                        String s = new ObjectMapper().writeValueAsString(auth);
                        resp.getWriter().write(s);
                    })
                    .permitAll()
                    .and()
                    .csrf().disable()
                    .userDetailsService(users);
        }
    }

}

在 SecurityConfig 中分别定义两个静态内部类 SecurityConfig01 和 SecurityConfig02,两个配置类都继承自 WebSecurityConfigurerAdapter,可以分别配置一条过滤器链。

先来看 Security01。在 Security01 中,我们设置过滤器链的拦截规则是 /bar/**,即如果请求路径是 /bar/** 格式的,则进入到 Security01 的过滤器链中进行处理。同时我们配置了局部 AuthenticationManager 对应的用户是 bar/123,由于没有重写 configure(AuthenticationManagerBuilder) 方法,所以注册到 Spring容器中的 UserDetailsService 将作为局部 AuthenticationManager 的 parent 对应的用户。换句话说,如果登录的路径是 /bar/login,那么开发者可以使用 bar/123 和 javaboy/123 两个用户进行登录。登录效果如图4-8 所示(注意登录路径是 /bar/login)。

image 2024 04 12 16 28 50 036
Figure 1. 图4-8 使用javaboy/123进行登录

再来看 SecurityConfig02。在 Security02 中,我们设置过滤器链的拦截规则是 /foo/**,即如果请求路径是 /foo/** 格式的,则进入到 Security02 的过滤器链中进行处理,同时我们配置了局部 AuthenticationManager 对应的用户是 foo/123,由于重写了 configure(AuthenticationManagerBuilder) 方法,在该方法中定义了局部 AuthenticationManager 的 parent 对应的用户,此时注册到 Spring 容器中的 UserDetailsService 实例对于 /foo/** 过滤器链不再生效。换句话说,如果登录路径是 /foo/login,开发者可以使用 foo/123 和 javagirl/123 两个用户进行登录,而不可以使用 javaboy/123 进行登录。登录效果如图4-9所示(注意登录路径是 /foo/login)。

image 2024 04 12 16 32 49 385
Figure 2. 图4-9 使用 foo/123 进行登录

需要注意的是,如果配置了多个过滤器链,需要使用 @Order 注解来标记不同配置的优先级(即不同过滤虑器链的优先级),数字越大优先级越低。当请求到来时,会按照过滤器链的优先级从高往低,依次进行匹配。