原创

Spring Security(4)——springSecurity框架如何使用多个过滤器链(附完整项目代码)

1. 先看看多个过滤器链结果

这部分内容比较多,建议读者下载项目启动后再看。直接查看配置方式的话,请看第二部分。

我的demo项目启动时候,打印的日志如下所示,显示系统生成了3个FilterChain。

第一个FilterChain

2021-02-08 14:14:07.614  INFO 18200 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/user/**'], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4f82663e, org.springframework.security.web.context.SecurityContextPersistenceFilter@ec1b2e4, org.springframework.security.web.header.HeaderWriterFilter@34a0ef00, org.springframework.security.web.authentication.logout.LogoutFilter@58a120b0, com.springsecurity.filter.UserFilter@e04ccf8, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@971e903, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@67e28be3, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@597f48df, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@3e4f80cb, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@51d143a1, org.springframework.security.web.session.SessionManagementFilter@21fdfefc, org.springframework.security.web.access.ExceptionTranslationFilter@6293e39e, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@50f40653]

第二个FilterChain

2021-02-08 14:14:07.617  INFO 18200 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: Ant [pattern='/admin/**'], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7cb2651f, org.springframework.security.web.context.SecurityContextPersistenceFilter@66bacdbc, org.springframework.security.web.header.HeaderWriterFilter@77c7ed8e, org.springframework.security.web.authentication.logout.LogoutFilter@4b54af3d, com.springsecurity.filter.AdminFilter@4441d567, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@b8e246c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2c6ee758, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@640dc4c6, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@1f387978, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3e1624c7, org.springframework.security.web.session.SessionManagementFilter@453d496b, org.springframework.security.web.access.ExceptionTranslationFilter@191a709b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@616b241a]

第三个FilterChain

2021-02-08 14:14:07.621  INFO 18200 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7144655b, org.springframework.security.web.context.SecurityContextPersistenceFilter@64c4c01, org.springframework.security.web.header.HeaderWriterFilter@79d743e6, org.springframework.security.web.authentication.logout.LogoutFilter@6ee8dcd3, com.springsecurity.filter.CommonFilter@5c82cd4f, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3c6aa04a, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1aa99005, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@592238c5, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@2257fadf, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@35835e65, org.springframework.security.web.session.SessionManagementFilter@776802b0, org.springframework.security.web.access.ExceptionTranslationFilter@2b56f5f8, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@bc4d5e1]

3个过滤器链的初始化顺序是Ant [pattern='/user/'],Ant [pattern='/admin/'],any request。

这个跟我springSecurity配置中3个配置类的@Order注解配置的编号大小顺序是一致的,CommonWebSecurityConfig>UserWebSecurityConfig>AdminWebSecurityConfig。

3个FilterChain的filter顺序如日志所示,UsernamePasswordAuthenticationFilter过滤器在UserFilter,AdminFilter,CommonFilter之后执行,因此过滤器链路的责任链执行顺序就是跟打印的顺序一样。

启动demo项目后,登入系统系列操作,查看打印的日志。

  1. 进入http://localhost:8082/,打印日志如下
    CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/
    CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/css/main.css
    CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/error
    
    因为CommonWebSecurityConfig排序靠后的,匹配/user/,/admin/的url都进入到前两个过滤器链执行了。其他没有匹配规则的url都进入了CommonWebSecurityConfig的过滤器链。这点从打印的日志内容就能看出。

url“/“、“/css/main.css“、“/error“都不满足user以及admin过滤器链的匹配规则,都进入到了Common链。

  1. 在home页面访问admin page
    alt

进入后,跳转到登入页面,因为此时还未登入。日志打印如下

AdminFilter在 admin 过滤链的 UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/admin
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/login
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/css/main.css
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/error
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/login

输入用户名密码 root/666666,登录后自动跳转到/admin路径。如下所示跳转后的页面以及打印的日志。

alt

日志:

AdminFilter在 admin 过滤链的 UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/admin
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/error
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/css/main.css
CommonFilter 在 user 过滤链的  UsernamePasswordAuthenticationFilter 前调用 , 过滤器拦截的url是:/error

从日志可以看出,浏览器发出/admin请求,校验后权限不够,就进入error页面,提示用户权限不够。

/admin的权限配置如下,是ADMIN001,root用户的权限只有ADMIN、USER,因此无法访问。将ADMIN001换成ADMIN,重启应用就能正常访问了。

.antMatchers("/admin/**").hasAnyRole("ADMIN001")   // 满足该条件下的路由需要ROLE_ADMIN的角色

以上,就是我的demo项目的测试过程,大家可以试着操作下。

2. springSecurity添加多个过滤器链的方法

这部分说的多个过滤器链配置方式是要求读者有Security配置经验基础上介绍的,如果不熟悉security基本配置,请查看网站的SpringSecurity系列文章Java架构师方案——Spring Security(一)快速入门(附完整项目代码)

  1. security配置类去掉注解@EnableWebSecurity,只用@Configuration注解配置即可,作为一个普通的configuration配置类。

  2. 配置一个configureGlobal方法,使用@Autowired修饰。在方法中使用AuthenticationManagerBuilder对象注入UserDetailsService对象。配置用户名密码及权限。

  3. demo配置了3个内部静态配置类,3个配置类的优先级关系是:UserWebSecurityConfig>AdminWebSecurityConfig>CommonWebSecurityConfig。是由@order注解决定的。url匹配顺序也是按照这个优先级来的。

  4. 3个静态配置类就是springSecurity的3个过滤器链,三个过滤器链,匹配/admin/的url进入AdminWebSecurityConfig过滤链,匹配/user/的url进入UserWebSecurityConfig过滤链,其他的url就进入CommonWebSecurityConfig过滤链。

  5. 注意,普通的SpringSecurity在configure(HttpSecurity http)方法中配置方式是以http.authorizeRequests()开始的,而在配置多个过滤链的场景中,就不是之前的配置方式了,而是以http.antMatcher("/user/**")开始,后续再加上.authorizeRequests()方法。其配置就一样了。

6. 还要注意的是@order注解配置的优先级,不能让CommonWebSecurityConfig过滤器配置类优先级高,不然所有的/admin/以及/user/访问路径都会只走CommonWebSecurityConfig过滤器链。

*3. 多个过滤器链配置类实现代码

@Configuration
public class SecurityConfiguration {
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Autowired
    private DataSource dataSource;

    /*
     * 不使用 auth.inMemoryAuthentication().withUser的api,只是用InMemoryUserDetailsManager方法。
     * 
     *  InMemoryUserDetailsManager 类的方法 能让方法都能正常 登录,没有任何错误。
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
        auth.userDetailsService(userDetailsService());//配置userdetail必不可少

//        auth.inMemoryAuthentication().withUser(User.withUsername("jack").password("{bcrypt}" + new BCryptPasswordEncoder().encode("88888888")).roles("USER"));
//        auth.inMemoryAuthentication().withUser(User.withUsername("king").password(passwordEncoder().encode("88888888")).roles("ADMIN","USER"));
//        auth.inMemoryAuthentication().withUser(User.withUsername("root").password(passwordEncoder().encode("88888888")).roles("ADMIN","USER"));
    }

    public UserDetailsService userDetailsService() {
        // TODO Auto-generated method stub
         //直接建两个用户存在内存中,生产环境可以从数据库中读取,对应管理器JdbcUserDetailsManager
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 创建两个用户
        //通过密码的前缀区分编码方式,推荐,这种加密方式很好的利用了委托者模式,使得程序可以使用多种加密方式,并且会自动
        //根据前缀找到对应的密码编译器处理。
        manager.createUser(User.withUsername("guest").password("{bcrypt}" +
                new BCryptPasswordEncoder().encode("111111")).roles("USER").build());
        manager.createUser(User.withUsername("root").password("{sha256}" +
                new StandardPasswordEncoder().encode("666666"))
                .roles("ADMIN", "USER").build());
        System.out.println("获取用户信息:userDetailsService");
        return manager;
    }



    /**
     * 支持多种编码,通过密码的前缀区分编码方式,推荐
     *
     * @return the password encoder
     */
    @Bean
    PasswordEncoder passwordEncoder() {

        PasswordEncoder PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        PasswordEncoder.upgradeEncoding("{sha256}");
        return PasswordEncoder;
    }


    @Configuration
    @Order(3)
    static class AdminWebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/admin/**")
            .authorizeRequests()
            .antMatchers("/admin/**").hasAnyRole("ADMIN001")   // 满足该条件下的路由需要ROLE_ADMIN的角色
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .defaultSuccessUrl("/admin/admin")//默认的成功跳转到的url
            .failureUrl("/403")
            .and()
            .rememberMe()
            .and()
            .logout()
            .permitAll()
            .and()
            .csrf().disable();

            http.addFilterBefore(new AdminFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }

    @Configuration
    @Order(2)
    static class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:on
            http.antMatcher("/user/**")
                    .authorizeRequests()
//                    .antMatchers("/css/**", "/js/**", "/fonts/**").permitAll()  // 允许访问资源
//                    .antMatchers("/", "/home", "/about", "/login").permitAll() //允许访问这三个路由
                    .antMatchers("/user/**").hasAnyRole("USER")     // 满足该条件下的路由需要ROLE_USER的角色
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login")
//                    .loginProcessingUrl("/login")
//                    .usernameParameter("username")
//                    .passwordParameter("password")
//                    .successForwardUrl("/admin")
                    .defaultSuccessUrl("/user/user")//默认的成功跳转到的url
                    .failureUrl("/403")
                    .and()
                    .rememberMe()
                    .and()
                    .logout()
                    .permitAll()
                    .and()
                    .csrf().disable();

            http.addFilterBefore(new UserFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }


    @Configuration
    @Order(4)
    static class CommonWebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:on
            http.authorizeRequests()
                    .antMatchers("/css/**", "/js/**", "/fonts/**").permitAll()  // 允许访问资源
                    .antMatchers("/", "/home", "/about", "/login").permitAll() //允许访问这三个路由
                    .antMatchers("/user/**").hasAnyRole("USER")     // 满足该条件下的路由需要ROLE_USER的角色
                    .antMatchers("/admin/**").hasAnyRole("ADMIN")   // 满足该条件下的路由需要ROLE_ADMIN的角色
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login")
//                    .loginProcessingUrl("/login")
//                    .usernameParameter("username")
//                    .passwordParameter("password")
//                    .successForwardUrl("/admin")
                    .defaultSuccessUrl("/admin/admin")//默认的成功跳转到的url
                    .failureUrl("/403")
                    .and()
                    .rememberMe()
                    .and()
                    .logout()
                    .permitAll()
                    .and()
                    .csrf().disable();

            http.addFilterBefore(new CommonFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }
}

完整的demo项目,请关注公众号“前沿科技bot“并发送"SEC-FIVE"获取。

alt

正文到此结束
本文目录