spring-security(一)源码分析

DisableEncodeUrlFilter extends OncePerRequestFilter
WebAsyncManagerIntegrationFilter extends OncePerRequestFilter
SecurityContextPersistenceFilter extends GenericFilterBean
HeaderWriterFilter extends OncePerRequestFilter
CsrfFilter extends OncePerRequestFilter
LogoutFilter extends GenericFilterBean
UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
DefaultLoginPageGeneratingFilter extends GenericFilterBean
DefaultLogoutPageGeneratingFilter extends OncePerRequestFilter
BasicAuthenticationFilter extends OncePerRequestFilter
RequestCacheAwareFilter extends GenericFilterBean
SecurityContextHolderAwareRequestFilter extends GenericFilterBean
AnonymousAuthenticationFilter extends GenericFilterBean
SessionManagementFilter extends GenericFilterBean
ExceptionTranslationFilter extends GenericFilterBean
FilterSecurityInterceptor extends AbstractSecurityInterceptor

GenericFilterBean implements Filter
OncePerRequestFilter extends GenericFilterBean
AbstractAuthenticationProcessingFilter extends GenericFilterBean
FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter

Spring Security的初始化过程
项目添加spirng security依赖后,会自动创建一个SecurityFilterChain,包含16个如上的过滤器
问题
1.SecurityFilterChain又是如何成为web filter的?
2.16个过滤器是什么是时候实例化以及被添加到SecurityFilterChain中的?如何排序的?
3.如何将16个过滤器中的某个去掉或者自定义过滤器?
4.如何添加自定义过滤器?

一.总体:
一.SecurityFilterChain
单条链SecurityFilterChain初始化
从下到上 ->
->
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {

}
->
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
  HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {}

->
interface SecurityFilterChain
DefaultSecurityFilterChain implements SecurityFilterChain
默认的16个filter chain
哪里实例化DefaultSecurityFilterChain对象?
DefaultSecurityFilterChain HttpSecurity.performBuild()

从上到下 ->
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnDefaultWebSecurity
    static class SecurityFilterChainConfiguration {

        @Bean
        @Order(SecurityProperties.BASIC_AUTH_ORDER)
        SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().authenticated();
            http.formLogin();
            http.httpBasic();
            return http.build(); // -> HttpSecurity.performBuild() 这里实例化了DefaultSecurityFilterChain
        }

    }

二.HttpSecurity
而HttpSecurity在此实例化,在上面被使用
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
  HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {}

->
class HttpSecurityConfiguration {
  @Bean(HTTPSECURITY_BEAN_NAME)
  @Scope("prototype")
  HttpSecurity httpSecurity() throws Exception {
    WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
        this.context);
    AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
        this.objectPostProcessor, passwordEncoder);
    authenticationBuilder.parentAuthenticationManager(authenticationManager());
    authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
    HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
    // @formatter:off
    http
      .csrf(withDefaults())
      .addFilter(new WebAsyncManagerIntegrationFilter())
      .exceptionHandling(withDefaults())
      .headers(withDefaults())
      .sessionManagement(withDefaults())
      .securityContext(withDefaults())
      .requestCache(withDefaults())
      .anonymous(withDefaults())
      .servletApi(withDefaults())
      .apply(new DefaultLoginPageConfigurer<>());
    http.logout(withDefaults());
    // @formatter:on
    applyDefaultConfigurers(http);
    return http;
  }
}

三.WebSecurity
现在已经知道单条过滤链SecurityFilterChain啥时候实例化的了,那么SecurityFilterChain加入filter中呢?
从上到下 -> 请求进来
CharacterEncodingFilter extends OncePerRequestFilter ->
  OncePerRequestFilter extends GenericFilterBean ->
    RequestContextFilter extends OncePerRequestFilter ->
      org.springframework.web.filter.DelegatingFilterProxy extends GenericFilterBean -> Delegating(授权)
      org.springframework.security.web.FilterChainProxy extends GenericFilterBean
      public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
      }
FilterChainProxy是啥时候初始化的?
Filter WebSecurity.performBuild() {
  ...
  // 将所有的SecurityFilterChain添加到FilterChainProxy中
  FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
  ......
  Filter result = filterChainProxy;
  return result;
}
会返回一个filter,然后生成一个bean对象,如下:
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
  return filter; // 然后在request请求的时候会调用此filter
}
WebSecurity与httpSecurity的区别是:WebSecurity的filter是httpSecurity中SecurityFilterChain的所有集合

从下到上 
->
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {

}
->
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
  HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {}


四.我们再来分析一下单条链SecurityFilterChain中一个个特定的过滤器如CsrfFilter、UsernamePasswordAuthenticationFilter是什么时候加入SecurityFilterChain的?
1.我们在仔细看看HttpSecurity实例化
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
  WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
      this.context);
  AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
      this.objectPostProcessor, passwordEncoder);
  authenticationBuilder.parentAuthenticationManager(authenticationManager());
  authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
  HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
  // @formatter:off
  http
    .csrf(withDefaults())
    .addFilter(new WebAsyncManagerIntegrationFilter())
    .exceptionHandling(withDefaults())
    .headers(withDefaults())
    .sessionManagement(withDefaults())
    .securityContext(withDefaults())
    .requestCache(withDefaults())
    .anonymous(withDefaults())
    .servletApi(withDefaults())
    .apply(new DefaultLoginPageConfigurer<>());
  http.logout(withDefaults());
  // @formatter:on
  applyDefaultConfigurers(http);
  return http;
}

HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
->
FilterOrderRegistration() {
  Step order = new Step(INITIAL_ORDER, ORDER_STEP);
  put(DisableEncodeUrlFilter.class, order.next());
  put(ForceEagerSessionCreationFilter.class, order.next());
  put(ChannelProcessingFilter.class, order.next());
  order.next(); // gh-8105
  put(WebAsyncManagerIntegrationFilter.class, order.next());
  put(SecurityContextHolderFilter.class, order.next());
  put(SecurityContextPersistenceFilter.class, order.next());
  put(HeaderWriterFilter.class, order.next());
  put(CorsFilter.class, order.next());
  put(CsrfFilter.class, order.next());
  put(LogoutFilter.class, order.next());
  this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter", order.next());
  this.filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter", order.next());
  put(X509AuthenticationFilter.class, order.next());
  put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
  this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
  this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter", order.next());
  this.filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter", order.next());
  put(UsernamePasswordAuthenticationFilter.class, order.next());
  order.next(); // gh-8105
  this.filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
  put(DefaultLoginPageGeneratingFilter.class, order.next());
  put(DefaultLogoutPageGeneratingFilter.class, order.next());
  put(ConcurrentSessionFilter.class, order.next());
  put(DigestAuthenticationFilter.class, order.next());
  this.filterToOrder.put("org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
  put(BasicAuthenticationFilter.class, order.next());
  put(RequestCacheAwareFilter.class, order.next());
  put(SecurityContextHolderAwareRequestFilter.class, order.next());
  put(JaasApiIntegrationFilter.class, order.next());
  put(RememberMeAuthenticationFilter.class, order.next());
  put(AnonymousAuthenticationFilter.class, order.next());
  this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter", order.next());
  put(SessionManagementFilter.class, order.next());
  put(ExceptionTranslationFilter.class, order.next());
  put(FilterSecurityInterceptor.class, order.next());
  put(AuthorizationFilter.class, order.next());
  put(SwitchUserFilter.class, order.next());
}

我们再看 http.csrf(withDefaults())
-> csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context))); 
  实例化CsrfConfigurer
  getOrApply方法
    -> AbstractConfiguredSecurityBuilder.apply(configurer)
      configs.add(configurer); // 此处将实例CsrfConfigurer添加到了AbstractConfiguredSecurityBuilder.configurers中,在后面HttpSecurity.build()的时候会用到


二.再次回到SecurityFilterChain实例化
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {

  @Bean
  @Order(SecurityProperties.BASIC_AUTH_ORDER)
  SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated();
    http.formLogin();
    http.httpBasic();
    return http.build();
  }

}
-> 着重关注 HttpSecurity.build()
@Override
    protected final O AbstractConfiguredSecurityBuilder.doBuild() throws Exception {
        synchronized (this.configurers) {
            this.buildState = BuildState.INITIALIZING;
            beforeInit();
            init();
            this.buildState = BuildState.CONFIGURING;
            beforeConfigure();
            configure();
            this.buildState = BuildState.BUILDING;
            O result = performBuild();
            this.buildState = BuildState.BUILT;
            return result;
        }
    }
  
关注init();
  -> configurer.init((B) this); // 遍历AbstractConfiguredSecurityBuilder.configurers所有子对象将某些(注意是某些)SecurityConfigurer进行初始化

再关注configure();
  ->configurer.configure((B) this);
    ->CsrfConfigurer.configure(H http) {
      ... 实例化一个对应的过滤器...
      http.addFilter(filter); // 在此处将对应的filter加入到HttpSecurity.filters中
    }

    示例:CsrfFilter
    public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
        extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
      ...
      @Override
      public void configure(H http) {
        CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository); //这里实例化过滤器
        RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
        if (requireCsrfProtectionMatcher != null) {
          filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
        }
        AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
        if (accessDeniedHandler != null) {
          filter.setAccessDeniedHandler(accessDeniedHandler);
        }
        LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
        if (logoutConfigurer != null) {
          logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
        }
        SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
        if (sessionConfigurer != null) {
          sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
        }
        filter = postProcess(filter);
        http.addFilter(filter); // 这里加入
      }
      ...
    }

    ->
    HttpSecurity {
      private List<OrderedFilter> filters = new ArrayList<>();
    
      @Override
      public HttpSecurity addFilter(Filter filter) {
        Integer order = this.filterOrders.getOrder(filter.getClass());
        if (order == null) {
          throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
              + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
        }
        this.filters.add(new OrderedFilter(filter, order));
        return this;
      }
      
    }

分析UsernamePasswordAuthenticationFilter

1.CsrfFilter
  使请求仅支持 "GET", "HEAD", "TRACE", "OPTIONS" 四种请求方式。
  http.csrf().disable(); 如此就可以禁用CsrfFilter

2.最重要的filter - UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter本身不是过滤器,而是继承AbstractAuthenticationProcessingFilter才拥有过滤器的性能,其主要是验证用户名密码。
我们详细分析一下UsernamePasswordAuthenticationFilter以及如何自定义用户认证
(1).先看看UsernamePasswordAuthenticationFilter实例化和被添加的时机
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {

  @Bean
  @Order(SecurityProperties.BASIC_AUTH_ORDER)
  SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated();
    http.formLogin(); // 此处实例化和被添加到SecurityFilterChain中
    http.httpBasic();
    return http.build();
  }

}

(2).定义用户,并使用InMemoryUserDetailsManager管理用户
@Bean
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
    ObjectProvider<PasswordEncoder> passwordEncoder) {
  SecurityProperties.User user = properties.getUser();
  List<String> roles = user.getRoles();
  return new InMemoryUserDetailsManager(
      User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
          .roles(StringUtils.toStringArray(roles)).build());
}

(3).当请求进来UsernamePasswordAuthenticationFilter拦截处理
public class UsernamePasswordAuthenticationFilter {
  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    String username = obtainUsername(request);
    username = (username != null) ? username.trim() : "";
    String password = obtainPassword(request);
    password = (password != null) ? password : "";
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
        password);
    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest); // 这里是认证的地方,返回一个身份验证
  }
}

(4).用户校验
InMemoryUserDetailsManager {
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    UserDetails user = this.users.get(username.toLowerCase());
    if (user == null) {
      throw new UsernameNotFoundException(username);
    }
    return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
        user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
  }
}

分析一下http.formLogin();都发生了什么
在config()的时候
this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
->this.authFilter.setAuthenticationManager(ProviderManager.class);

ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接
口,也是认证的入口。在实际开发中,我们可能有多种不同的认证方式,例如:用户名+
密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是
AuthenticationManager。在该接口的常用实现类 ProviderManager 内部会维护一个
List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式
(Delegate)的应用。每种认证方式对应着一个 AuthenticationProvider,
AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托
对应的 AuthenticationProvider 进行用户认证。
因为传入的是UsernamePasswordAuthenticationToken 所以与之适配认证方式为DaoAuthenticationProvider
原因是看其父类
AbstractUserDetailsAuthenticationProvider {
        @Override
        public boolean supports(Class<?> authentication) {
            return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
        }
}


如何自定义认证方法?
方法1.如果只是获取对应数据库用户数据,可以实现UserDetailsService接口即可(推荐)
  UserDetailsServiceImpl implements UserDetailsService
方法2.如果想管理用户,而不是每次都从数据库中获取,可以实现UserDetailsManager接口,如同InMemoryUserDetailsManager管理用户数据
方法3.当然也可以实现AuthenticationProvider,自定义authenticate()方法。那就需要判断当前登录用户是否在数据库中
  HttpSecurity.authenticationProvider(authenticationProvider);
方法4.实现AuthenticationManager接口,重写authenticate()认证方法,那就需要判断当前登录用户是否在数据库中
  HttpSecurity.authenticationManager(AuthenticationManager authenticationManager);
方法5.再往上的话可以继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication()方法
  public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter{}

如何自定义用户信息,而不是通过SecurityProperties.classs中写死?
方法1.实现UserDetails接口
方法2.继承User

如何动态判断用户拥有的角色是否含有url权限?
FilterSecurityInterceptor 鉴权
鉴权虽然名字叫做Interceptor 但其实他还是实现了Filter接口

FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter 
dofilter方法只是包装FilterInvocation类执行invoke方法。

    public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        //防止重复鉴权
        if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } else {
            if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
                filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
            //都是执行的父类方法,beforeInvocation是具体的鉴权逻辑
            InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
​
            try {
                filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
            } finally {
                super.finallyInvocation(token);
            }
​
            super.afterInvocation(token, (Object)null);
        }
    }
在父方法beforeInvocation调用了attemptAuthorization方法。

    private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) {
        try {
            //使用accessDecisionManager.decide鉴权
            this.accessDecisionManager.decide(authenticated, object, attributes);
        } catch (AccessDeniedException var5) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager));
            } else if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
            }
​
            this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var5));    
            //不成功抛出异常。
            throw var5;
        }
    }
补充一个点,校验不成功抛出的AccessDeniedException异常是谁处理的呢。

这之前的一个filter,ExceptionTranslationFilter的doFilter逻辑

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            //通过try catch去捕获后续filter的异常
            chain.doFilter(request, response);
        } catch (IOException var7) {
            throw var7;
        } catch (Exception var8) {
            //只获取AuthenticationException和AccessDeniedException异常,其余的直接抛
            Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var8);
            RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            if (securityException == null) {
                securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }
            //直接抛
            if (securityException == null) {
                this.rethrow(var8);
            }
            ...
             //根据类型处理异常,最后通过accessDeniedHandler进行处理
            this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException);
        }
​
    }
DaoAuthenticationProvider {
    // 依赖UserDetailsService
    private UserDetailsService userDetailsService;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值