找回密码
 立即注册
首页 业界区 业界 SpringSecurity配置和源码解析

SpringSecurity配置和源码解析

赴忽 12 小时前
1. 背景

Spring Security最主要的两个功能:认证和授权
功能解决的问题Spring Security中主要类认证(Authentication)你是谁AuthenticationManager授权(Authorization)你可以做什么AuthorizationManager之前的文章讲过了使用Spring Security来接入其他单点登录系统,其中单点登录系统来解决你是谁的问题,Spring Security用来解决你可以做什么的问题
这次我们主要使用Spring Security的认证功能
这次的需求是这样的,我们有一个A服务,A服务内部有n个接口,这些接口根据使用方的不同可以分为三部分

  • C端用户使用的接口,需要c端账户体系鉴权,后续会有网关统一鉴权,目前暂时用账号密码鉴权
  • B端用户使用的接口,需要b端账户体系鉴权,由其他OAuth2 Server来鉴权
  • 开发人员使用的接口,账号密码鉴权
Spring Security版本: 5.3.x
2. 目的

本篇文章的目的是通过实际的场景来让大家如何配置Spring Security来实现自己的需求,明白每一行的配置作用
3. 整体配置
  1. @Configuration
  2. @EnableWebSecurity
  3. public class SecurityConfig {
  4.     @Configuration
  5.     @Order(1)
  6.     public static class BSecurityConfig extends WebSecurityConfigurerAdapter {
  7.         @Override
  8.         protected void configure(HttpSecurity http) throws Exception {
  9.             http
  10.                     .csrf()
  11.                                       .disable()
  12.                     .antMatcher("/b/**")
  13.                     .authorizeRequests()
  14.                     .anyRequest()
  15.                                       .authenticated()
  16.                     .and()
  17.                     .addFilterBefore(new MyTokenJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  18.         }
  19.         @Override
  20.         public void configure(WebSecurity web) {
  21.             web.ignoring().antMatchers("/b/api/auth/access_token");
  22.         }
  23.     }
  24.     @Configuration
  25.     @Order(2)
  26.     public static class CSecurityConfig extends WebSecurityConfigurerAdapter {
  27.         @Value("${algo-api.password}")
  28.         private String password;
  29.         @Override
  30.         protected void configure(HttpSecurity http) throws Exception {
  31.             http
  32.                     .csrf().disable()
  33.                     .antMatcher("/c/**")
  34.                     .authorizeRequests()
  35.                     .anyRequest()
  36.                     .hasRole("C_ADMIN")
  37.                     .and()
  38.                     .httpBasic();
  39.         }
  40.         @Override
  41.         protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  42.             auth.inMemoryAuthentication()
  43.                     .withUser("cAdmin777")
  44.                     .password(password)  
  45.                     .roles("ALGO_ADMIN");
  46.         }
  47.     }
  48.     @Configuration
  49.     @Order(3)
  50.     public static class DevSecurityConfig extends WebSecurityConfigurerAdapter {
  51.         @Value("${swagger.password}")
  52.         private String password;
  53.         @Override
  54.         protected void configure(HttpSecurity http) throws Exception {
  55.             http
  56.                     .csrf().disable()
  57.                     .authorizeRequests()
  58.                     .antMatchers("/swagger-ui/**", "/v3/api-docs/**", "/test/**")
  59.                     .hasRole("SWAGGER_ADMIN")
  60.                     .antMatchers("/login", "/css/**", "/js/**", "/images/**")
  61.                     .permitAll()
  62.                     .anyRequest()
  63.                     .denyAll()
  64.                     .and()
  65.                     .formLogin();
  66.         }
  67.         @Override
  68.         protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  69.             auth.inMemoryAuthentication()
  70.                     .withUser("swaggerAdmin999")
  71.                     .password(password)
  72.                     .roles("SWAGGER_ADMIN");
  73.         }
  74.     }
  75. }
复制代码
3.1 配置解读

3.1.1 SecurityConfig类

首先是一个SecurityConfig类,带了@Configuration和@EnableWebSecurity注解,代表接入了Springboot和开启了SpringSecurity功能
3.1.2 WebSecurityConfigurerAdapter子类

内部是三个静态内部类BSecurityConfig、CSecurityConfig、DevSecurityConfig分别对应我们的需求的使用方,B端用户、C端用户和开发人员,他们继承了WebSecurityConfigurerAdapter类,继承此类可以重写其中的方法来进行配置,具体Spring Security是如何做的可以看后面的源码分析
1.png

从Spring Security官方文档中可以看到,使用不同的SecurityFilterChain对不同的url前缀进行安全配置
所以,这里的三个静态内部类就是对应的三个SecurityFilterChain
那么有的同学可能会问,如果url的路径冲突了怎么办,比如两个SecurityFilterChain的路径前缀一样,那就取决于WebSecurityConfigurerAdapter的子类加载顺序,会使用先匹配的SecurityFilterChain,可以看到我们的静态内部类也使用了@Order(3)这样的注解来标识顺序
2.png

从断点中可以有4个SecurityFilterChain,且每个SecurityFilterChain内部的过滤器数量不同
为什么多了一个SecurityFilterChain,因为在BSecurityConfig中我们忽略了/b/api/auth/access_token路径的安全配置,所以对于这个路径下一个过滤器也没有
WebSecurityConfigurerAdapter类三个的主要的配置方法,也就是我们需要重写的方法:
  1. protected void configure(AuthenticationManagerBuilder auth)
  2. public void configure(WebSecurity web)
  3. protected void configure(HttpSecurity http)
复制代码
AuthenticationManagerBuilder这个类是来配置认证相关的,例如使用内存中的账号密码,还是JDBC的数据来认证,从图中可以看出其作用
3.png

3.1.3 HttpSecurity配置

这个是我们主要使用的配置类,它的常用方法有:
csrf和cors,具体可以参考我之前的博客
3.1.3.1 Match方法

这些方法用来指定这个配置是对哪些路径生效的

  • antMatcher
  • mvcMatcher
  • regexMatcher
  • requestMatchers
其中requestMatchers是比较灵活的匹配路径的一种方式,适合匹配多个路径
注意: 区分antMatcher和antMatchers
antMatchersantMatcher所属类AbstractRequestMatcherRegistryHttpSecurity参数多个url通配符单个url通配符作用细粒度的鉴权,每个路径下面需要的鉴权不同,权限也可能不同粗粒度的鉴权,每个路径是一个SecurityFilterChain,有一组过滤器3.1.3.2 authorizeRequests方法

返回的是ExpressionUrlAuthorizationConfigurer类
用来表示要开启一个细粒度的鉴权,两个方法功能一样,写法不一样,推荐下面这种,可以把某个细粒度的路径的鉴权写到一起,而不是整个链式,难以阅读,后续使用的类是ExpressionInterceptUrlRegistry

  • authorizeRequests
  • authorizeRequests(authorizeRequestsCustomizer)
3.1.3.2.1 match

和HttpSecurity一样,细粒度的圈选路径

  • antMatchers,符合表达式的请求
  • mvcMatchers
  • regexMatchers
  • anyRequest,任意请求
3.1.3.2.2 访问权限

在圈选完路径之后,声明此路径的鉴权方式,也就是需不需要登录,需要什么权限才能方法

  • permitAll,允许访问
  • anonymous,允许匿名
  • rememberMe,记住我
  • denyAll,不允许访问
  • authenticated,允许登录后访问
  • hasRole,需要某个角色可以访问
  • hasAuthority,需要某个权限可以访问
3.1.3.3 其他

3.1.3.3.1 xxxLogin

表示需要此种登录,例如formLogin表示需要进行表单登录,可以指定一些登录页,登录失败页面等,或者默认生成一些页面
oauth2Login表示使用OAuth2.0来做验证,这种就很方便的使用第三方应用来登录,例如支付宝授权登录
3.1.3.3.2 sessionManagement

需要进行session管理
3.1.3.3.3 addFilterxxx

手动的往Spring的过滤器链里面加一个过滤器,实际上,SpringSecurity所有的功能都是在过滤器链里面加过滤器,只不过有现成的稳定的类就不用我们自己写的,当现有的满足不了我们的需要时,我们可以自己来实现
3.1.4 WebSecurity配置

WebSecurity是全局配置类,它的常用方法有:

  • ignoring   配置不做控制的路径
  • httpFirewall  设置防火墙,拦截非法的请求,非法的http method,包含非法的符号等
  • debug  开启debug日志
上面的代码中,我们对/b/api/auth/access_token路径的请求不做控制
4. 源码分析

4.jpeg

整体架构如上所示:

  • SecurityBuilder和SecurityConfigurer是核心接口类,前者是构建者模式,后者负责定制化配置
  • HttpSecurity和WebSecurity是SecurityBuilder的两个核心实现类,一个负责收集整体的认证配置,一个负责收集指定路径的认证配置
  • XXXXSecurityConfigurer代表一系列的配置类,包括前面将的CRSF, CORS,登录、session等各种功能的实现
  • WebSecurityConfigurerAdapter是适配器模式,通过定制化HttpSecurity和WebSecurity这两个Builder来定制化真正的配置
  • WebSecurityConfiguration是自动配置类,也是Spring Security的启动类,它调用WebSecurity的init/build方法最终生成一个Filter,作为Servlet容器的一个普通Filter来使用
4.1 EnableWebSecurity

我们在使用Spring Security的时候需要加这个注解,这个注解使用@Import导入了WebSecurityConfiguration配置类
  1. @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
  2. @Target(value = { java.lang.annotation.ElementType.TYPE })
  3. @Documented
  4. @Import({ WebSecurityConfiguration.class,
  5.                 SpringWebMvcImportSelector.class,
  6.                 OAuth2ImportSelector.class })
  7. @EnableGlobalAuthentication
  8. @Configuration
  9. public @interface EnableWebSecurity {
  10.         /**
  11.          * Controls debugging support for Spring Security. Default is false.
  12.          * @return if true, enables debug support with Spring Security
  13.          */
  14.         boolean debug() default false;
  15. }
复制代码
4.2 WebSecurityConfiguration

这个类很关键,下面是这个类的核心方法,通过Value注入了List 这个就是我们配置的多个WebSecurityConfigurerAdapter的类的父类,也就是把我们的实现类都给收集起来,然后排序,最后放到webSecurity这个对象中去
  1.         public void setFilterChainProxySecurityConfigurer(
  2.                         ObjectPostProcessor<Object> objectPostProcessor,
  3.             // 我们继承WebSecurityConfigurerAdapter的子类被在这被收集起来
  4.                         @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
  5.                         throws Exception {
  6.                 webSecurity = objectPostProcessor
  7.                                 .postProcess(new WebSecurity(objectPostProcessor));
  8.                 if (debugEnabled != null) {
  9.                         webSecurity.debug(debugEnabled);
  10.                 }
  11.                 // 排序
  12.                 webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
  13.                 Integer previousOrder = null;
  14.                 Object previousConfig = null;
  15.                 for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
  16.                         Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
  17.                         if (previousOrder != null && previousOrder.equals(order)) {
  18.                                 throw new IllegalStateException(
  19.                                                 "@Order on WebSecurityConfigurers must be unique. Order of "
  20.                                                                 + order + " was already used on " + previousConfig + ", so it cannot be used on "
  21.                                                                 + config + " too.");
  22.                         }
  23.                         previousOrder = order;
  24.                         previousConfig = config;
  25.                 }
  26.     // 放入webSecurity中
  27.                 for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
  28.                         webSecurity.apply(webSecurityConfigurer);
  29.                 }
  30.                 this.webSecurityConfigurers = webSecurityConfigurers;
  31.        
复制代码
下面注册了一个Servlet Filter,这个Filter实现了整个Spring Security的功能
  1. @Bean(
  2.     name = {"springSecurityFilterChain"}
  3. )
  4. public Filter springSecurityFilterChain() throws Exception {
  5.     boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
  6.           // 假如开发者没有定制化过WebSecurityConfigurerAdapter,会默认new一个
  7.     if (!hasConfigurers) {
  8.         WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
  9.         });
  10.         this.webSecurity.apply(adapter);
  11.     }
  12.     return (Filter)this.webSecurity.build();
  13. }
复制代码
4.2 WebSecurity

其实没有很多逻辑在里面,基本就是构造者的思想,在不断填充各种属性,包括调用了HttpSecurity的 securityFilterChainBuilder.build()
[code]        protected Filter performBuild() throws Exception {                Assert.state(                                !securityFilterChainBuilders.isEmpty(),                                () -> "At least one SecurityBuilder
您需要登录后才可以回帖 登录 | 立即注册