【Spring-Security源码分析】WebSecurity

上一篇我们分析了AuthenticationManagerBuilder的实现,这一篇分析另一个SecurityBuilder实现类——WebSecurity。

1、FilterChainProxy的创建
WebSecurity由WebSecurityConfiguration创建,用于创建称为Spring Security Filter Chain(springSecurityFilterChain)的FilterChainProxy。 springSecurityFilterChain是DelegatingFilterProxy委派给的Filter。
可以通过创建WebSecurityConfigurer或更可能通过重写WebSecurityConfigurerAdapter来对WebSecurity进行自定义。

由上图可知,WebSecurity是来创建一个Web过滤器的,它继承结构与AuthenticationManagerBuilder类似,最终都是使用performBuild()方法完成Filter的创建。

@Override
protected Filter performBuild() throws Exception {
   Assert.state(
         !securityFilterChainBuilders.isEmpty(),
         () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
               + "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
               + "More advanced users can invoke "
               + WebSecurity.class.getSimpleName()
               + ".addSecurityFilterChainBuilder directly");
   int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
   List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
         chainSize);
   for (RequestMatcher ignoredRequest : ignoredRequests) {
      securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
   }
   for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
      securityFilterChains.add(securityFilterChainBuilder.build());
   }
   FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
   if (httpFirewall != null) {
      filterChainProxy.setFirewall(httpFirewall);
   }
   filterChainProxy.afterPropertiesSet();
 
   Filter result = filterChainProxy;
   if (debugEnabled) {
      logger.warn("\n\n"
            + "********************************************************************\n"
            + "**********        Security debugging is enabled.       *************\n"
            + "**********    This may include sensitive information.  *************\n"
            + "**********      Do not use in a production system!     *************\n"
            + "********************************************************************\n\n");
      result = new DebugFilter(filterChainProxy);
   }
   postBuildAction.run();
   return result;
}
performBuild()方法实际创建的是一个FilterChainProxy对象,这个对象的构造方法需要传入一个List<SecurityFilterChain>,这个List由两部分:

一部分是集合ignoredRequests中元素RequestMatcher作为构造参数的DefaultSecurityFilterChain
一部分是集合securityFilterChainBuilders中元素SecurityBuilder创建的SecurityFilterChain
关于这两部分的SecurityFilterChain稍后再讲,先看看FilterChainProxy是怎样的一个过滤器。

2、FilterChainProxy介绍
委托将请求过滤到Spring管理的过滤器bean列表。从2.0版开始,您不需要在应用程序上下文中显式配置FilterChainProxy bean,除非您需要对过滤器链内容进行非常精细的控制。大多数情况应该由默认的<security:http />命名空间配置选项充分涵盖。
通过在应用程序web.xml文件中添加标准的Spring DelegatingFilterProxy声明,FilterChainProxy链接到servlet容器过滤器链。

2.1、配置
从版本3.1开始,FilterChainProxy使用SecurityFilterChain实例列表进行配置,每个实例包含一个RequestMatcher和一个应用于匹配请求的过滤器列表。大多数应用程序只包含一个过滤器链,如果您使用的是命名空间,则不必显式设置链。如果需要更精细的控件,则可以使用<filter-chain>命名空间元素。这定义了一个URI模式和过滤器列表(作为逗号分隔的bean名称),它们应该应用于与模式匹配的请求。示例配置可能如下所示:

<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
    <constructor-arg>
        <util:list>
            <security:filter-chain pattern="/do/not/filter*" filters="none"/>
            <security:filter-chain pattern="/**" filters="filter1,filter2,filter3"/>
        </util:list>
    </constructor-arg>
</bean>
名称“filter1”,“filter2”,“filter3”应该是应用程序上下文中定义的Filter实例的bean名称。名称的顺序定义了过滤器的应用顺序。如上所示,对“过滤器”使用值“无”可以用于完全从安全过滤器链中排除请求模式。有关可用配置选项的完整列表,请参阅security namespace schema文件。

2.2、请求处理
必须输入FilterChainProxy应服务的每种可能模式。给定请求的第一个匹配项将用于定义应用于该请求的所有过滤器。这意味着您必须将大多数特定匹配放在列表顶部,并确保针对相应条目输入应该应用于给定匹配器的所有过滤器。 FilterChainProxy不会遍历其余的映射条目以查找其他过滤器。
FilterChainProxy尊重选择不调用Filter.doFilter(ServletRequest,ServletResponse,FilterChain)的Filter的正常处理,因为不会调用原始或FilterChainProxy声明的过滤器链的其余部分。

请求防火墙
HttpFirewall实例用于验证传入请求并创建包装请求,该请求提供用于匹配的一致路径值。有关默认实现可防御的攻击类型的更多信息,请参阅StrictHttpFirewall。可以注入自定义实现以提供对请求内容的更严格控制,或者如果应用程序需要支持默认拒绝的某些类型的请求。
请注意,这意味着如果需要此保护,则必须将Spring Security过滤器与FilterChainProxy结合使用。不要在web.xml文件中明确定义它们。
FilterChainProxy将使用防火墙实例来获取将在过滤器链中提供的请求和响应对象,因此也可以使用此功能来控制响应的功能。当请求通过安全过滤器链时,将调用reset方法。使用默认实现,这意味着此后将返回servletPath和pathInfo的原始值,而不是用于安全模式匹配的修改后的值。
由于此附加包装功能由FilterChainProxy执行,因此我们不建议您在同一过滤器链中使用多个实例。它不应该仅仅被视为在单个Filter实例中包装过滤器bean的实用程序。

2.3、过滤器生命周期
请注意servlet容器和IoC容器之间的过滤器生命周期不匹配。如DelegatingFilterProxy Javadocs中所述,我们建议您允许IoC容器管理生命周期而不是servlet容器。 FilterChainProxy不会在您添加到应用程序上下文的任何过滤器bean上调用标准过滤器生命周期方法。

2.4、实现
private void doFilterInternal(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
   FirewalledRequest fwRequest = firewall
         .getFirewalledRequest((HttpServletRequest) request);
   HttpServletResponse fwResponse = firewall
         .getFirewalledResponse((HttpServletResponse) response);
   List<Filter> filters = getFilters(fwRequest);
   if (filters == null || filters.size() == 0) {
      if (logger.isDebugEnabled()) {
         logger.debug(UrlUtils.buildRequestUrl(fwRequest)
               + (filters == null ? " has no matching filters"
                     : " has an empty filter list"));
      }
      fwRequest.reset();
      chain.doFilter(fwRequest, fwResponse);
      return;
   }
   //VirtualFilterChain包装的目的是为了filters都会执行
   VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
   vfc.doFilter(fwRequest, fwResponse);
}
//返回与提供的URL匹配的第一个过滤器链。
private List<Filter> getFilters(HttpServletRequest request) {
   for (SecurityFilterChain chain : filterChains) {
      if (chain.matches(request)) {
         return chain.getFilters();
      }
   }
   return null;
}
StrictHttpFirewall
严格执行HttpFirewall,拒绝任何带有RequestRejectedException的可疑请求。
以下规则适用于防火墙:

拒绝不允许的HTTP方法。这指定阻止HTTP动词篡改和XST攻击。请参阅setAllowedHttpMethods(Collection)
拒绝未规范化的URL以避免绕过安全约束。没有办法禁用它,因为禁用此约束被认为是非常危险的。允许此行为的一些选项是在防火墙之前规范化请求或使用DefaultHttpFirewall。请记住,规范化请求是脆弱的,为什么请求被拒绝而不是规范化。
拒绝包含不可打印ASCII字符的字符的URL。没有办法禁用它,因为禁用此约束被认为是非常危险的。
拒绝包含分号的URL。请参阅setAllowSemicolon(boolean)
拒绝包含URL编码斜杠的URL。请参阅setAllowUrlEncodedSlash(boolean)
拒绝包含反斜杠的URL。请参阅setAllowBackSlash(boolean)
拒绝包含URL编码百分比的URL。请参阅setAllowUrlEncodedPercent(boolean)
通过doFilterInternal()方法可以看出,真正起到拦截作用的是SecurityFilterChain,下面我们分析上面提到的两种SecurityFilterChain。

3、ignoredRequest为构造参数的DefaultSecurityFilterChain
for (RequestMatcher ignoredRequest : ignoredRequests) {
   securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
ignoredRequest代表忽略的请求,意思是match()方法返回为true就可以通过拦截,不再受其他过滤器链拦截,因此不需要传入过滤器链。这里有个问题,这个ignoredRequests是怎么产生的呢?

WebSecurity中有个成员变量ignoredRequestRegistry,在ApplicationContextAware接口方法被触发时会赋值三个成员变量:

defaultWebSecurityExpressionHandler
ignoredRequestRegistry
httpFirewall
@Override
public void setApplicationContext(ApplicationContext applicationContext)
      throws BeansException {
   this.defaultWebSecurityExpressionHandler
         .setApplicationContext(applicationContext);
   try {
      this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean(
            PermissionEvaluator.class));
   } catch(NoSuchBeanDefinitionException e) {}
 
   this.ignoredRequestRegistry = new IgnoredRequestConfigurer(applicationContext);
   try {
      this.httpFirewall = applicationContext.getBean(HttpFirewall.class);
   } catch(NoSuchBeanDefinitionException e) {}
}
我们可以在外部调用WebSecurity的ignoring()方法返回这个IgnoredRequestConfigurer对象,然后调用其mvcMatchers()方法为WebSecurity的ignoredRequests属性添加RequestMatcher对象。

4、securityFilterChainBuilders创建的SecurityFilterChain
//添加构建器以创建SecurityFilterChain实例。
//通常,此方法在WebSecurityConfigurerAdapter.init(WebSecurity)的框架内自动调用
public WebSecurity addSecurityFilterChainBuilder(
      SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
   this.securityFilterChainBuilders.add(securityFilterChainBuilder);
   return this;
}
如果开启了@EnableWebSecurity,Spring会在WebSecurityConfiguration中通过setFilterChainProxySecurityConfigurer()方法的参数注入容器中所有的SecurityConfigurer<Filter, WebSecurity>用来配置WebSecurity。具体实现请参考《Spring Security启动过程》。

org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init

public void init(final WebSecurity web) throws Exception {
   final HttpSecurity http = getHttp();
   web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
      public void run() {
         FilterSecurityInterceptor securityInterceptor = http
               .getSharedObject(FilterSecurityInterceptor.class);
         web.securityInterceptor(securityInterceptor);
      }
   });
}
关于WebSecurityConfigurerAdapter的配置细节,请看下面。

5、WebSecurityConfigurerAdapter
5.1、概述
为创建WebSecurityConfigurer实例提供方便的基类。 该实现允许通过重写方法进行自定义。将自动应用从SpringFactoriesLoader查找AbstractHttpConfigurer的结果,以允许开发人员扩展默认值。 为此,您必须创建一个扩展AbstractHttpConfigurer的类,然后在“META-INF/spring.factories”的类路径中创建一个类似于以下内容的类:

org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer

如果您有多个应添加的类,则可以使用“,”来分隔值。 例如:
    org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer,sample.OtherThatExtendsAbstractHttpConfigurer

5.2、继承结构


WebSecurityConfigurerAdapter也是一种SecurityConfigurer,只不过是用来配置构建对象类型是Filter的SecurityBuilder。

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
      SecurityConfigurer<Filter, T> {
}
5.3、源码分析
我们知道SecurityBuilder在构建目标对象先会先后调用SecurityConfigurer的init()方法和configure()方法对自身进行配置,下面我们就从init()方法开始看起。

public void init(final WebSecurity web) throws Exception {
   final HttpSecurity http = getHttp();
   web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
      public void run() {
         FilterSecurityInterceptor securityInterceptor = http
               .getSharedObject(FilterSecurityInterceptor.class);
         web.securityInterceptor(securityInterceptor);
      }
   });
}
public void configure(WebSecurity web) throws Exception {
}
HttpSecurity是SecurityBuilder 的实例用来创建SecurityFilterChain,作为WebSecurity创建的FilterChainProxy的过滤器链元素。从上面WebSecurity的performBuild()方法源码看到,在创建完FilterChainProxy对象后,会调用postBuildAction.run()来执行一些完成构建对象后的一些工作,而这里提供的postBuildAction是为WebSecurity提供了一个FilterSecurityInterceptor,关于FilterSecurityInterceptor的用途先有个印象我们稍后再来,这里先探究HttpSecurity的创建过程,然后看看具体可以为WebSecurity提供什么样的SecurityFilterChain。

protected final HttpSecurity getHttp() throws Exception {
   if (http != null) {
      return http;
   }
 
   DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
         .postProcess(new DefaultAuthenticationEventPublisher());
   //如果disableLocalConfigureAuthenticationBldr是true则不使用localConfigureAuthenticationBldr而使用authenticationConfiguration的authenticationManager
   //默认true
   localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
 
   AuthenticationManager authenticationManager = authenticationManager();
   authenticationBuilder.parentAuthenticationManager(authenticationManager);
   authenticationBuilder.authenticationEventPublisher(eventPublisher);
   Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
 
   http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
         sharedObjects);
   if (!disableDefaults) {
      // @formatter:off
      http
         .csrf().and()
         //通过使用SecurityContextCallableProcessingInterceptor.beforeConcurrentHandling(NativeWebRequest,Callable)在Callable上填充SecurityContext,
         //提供SecurityContext和Spring Web的WebAsyncManager之间的集成。
         .addFilter(new WebAsyncManagerIntegrationFilter())
         //下面每个方法都是为HttpSecurity配置一个对应功能的SecurityConfigurer
         .exceptionHandling().and()//ExceptionHandlingConfigurer
         .headers().and()//HeadersConfigurer
         .sessionManagement().and()//SessionManagementConfigurer
         .securityContext().and()//SecurityContextConfigurer
         .requestCache().and()//RequestCacheConfigurer
         .anonymous().and()//AnonymousConfigurer
         .servletApi().and()//ServletApiConfigurer
         .apply(new DefaultLoginPageConfigurer<>()).and()
         .logout();
      // @formatter:on
      ClassLoader classLoader = this.context.getClassLoader();
      //允许开发人员扩展默认值。 为此,您必须创建一个扩展AbstractHttpConfigurer的类
      List<AbstractHttpConfigurer> defaultHttpConfigurers =
            SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
      for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
         http.apply(configurer);
      }
   }
   //重写此方法以配置HttpSecurity。  默认配置是:
 //http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
   configure(http);
   return http;
}
//默认策略是,如果重写configure(AuthenticationManagerBuilder)方法以使用传入的AuthenticationManagerBuilder。
//否则,按类型自动装配AuthenticationManager。
protected AuthenticationManager authenticationManager() throws Exception {
   if (!authenticationManagerInitialized) {
      configure(localConfigureAuthenticationBldr);
      if (disableLocalConfigureAuthenticationBldr) {
         authenticationManager = authenticationConfiguration
               .getAuthenticationManager();
      }
      else {
         authenticationManager = localConfigureAuthenticationBldr.build();
      }
      authenticationManagerInitialized = true;
   }
   return authenticationManager;
}
上面代码中的objectPostProcessor和authenticationConfiguration都是通过@Autowired方法自动注入的,详情请参考《Spring Security启动过程》。以上就是HttpSecurity的创建过程,下面看HttpSecurity的实现。

6、HttpSecurity
HttpSecurity默认使用AnyRequestMatcher.INSTANCE可以匹配任意请求,但是也提供了一系列方法来对HttpSecurity进行配置。

@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
   Collections.sort(filters, comparator);
   return new DefaultSecurityFilterChain(requestMatcher, filters);
}


//间接调用apply()方法完成配置
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
      C configurer) throws Exception {
   C existingConfig = (C) getConfigurer(configurer.getClass());
   if (existingConfig != null) {
      return existingConfig;
   }
   return apply(configurer);
}
这里讲一下用于允许基于HttpServletRequest使用限制访问的authorizeRequests()方法。

public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
      throws Exception {
   ApplicationContext context = getContext();
   return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
         .getRegistry();
}
这个方法返回一个ExpressionInterceptUrlRegistry,我们可以调用antMatchers()方法注册指定的url,它会将此url封装到一个AuthorizedUrl对象,使用这个对象的方法就可以做到对此url进行指定的访问控制。详细如下调用:

org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry#antMatchers(java.lang.String...)方法:

//映射AntPathRequestMatcher实例列表。
public C antMatchers(HttpMethod method, String... antPatterns) {
   return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
}
org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers()方法:

protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
   this.unmappedMatchers = requestMatchers;
   return chainRequestMatchersInternal(requestMatchers);
}
org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry#chainRequestMatchersInternal()方法:

protected final AuthorizedUrl chainRequestMatchersInternal(
      List<RequestMatcher> requestMatchers) {
   return new AuthorizedUrl(requestMatchers);
}
加入要求需要有某个角色的用户才可访问该url,则可以使用hasRole()方法:

//指定URL的快捷方式需要特定的角色。 如果您不想自动插入“ROLE_”,请参阅hasAuthority(String)。
public ExpressionInterceptUrlRegistry hasRole(String role) {
   return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
 
public ExpressionInterceptUrlRegistry access(String attribute) {
   if (not) {
      attribute = "!" + attribute;
   }
   interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
   return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
}
org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer#hasRole()方法:

private static String hasRole(String role) {
   Assert.notNull(role, "role cannot be null");
   if (role.startsWith("ROLE_")) {
      throw new IllegalArgumentException(
            "role should not start with 'ROLE_' since it is automatically inserted. Got '"
                  + role + "'");
   }
   return "hasRole('ROLE_" + role + "')";
}
不仅hasRole()方法AuthorizedUrl的其它方法也都会调用access()方法,这个方法内部会调用interceptUrl()方法。

//允许将多个RequestMatcher实例注册到ConfigAttribute实例的集合
private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers,
      Collection<ConfigAttribute> configAttributes) {
   for (RequestMatcher requestMatcher : requestMatchers) {
      REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
            requestMatcher, configAttributes));
   }
}
在知道authorizeRequests()方法的实现细节后,看看ExpressionUrlAuthorizationConfigurer是如何使用上述配置的。

它的configure()方法定义在父类AbstractInterceptUrlConfigurer中。

public void configure(H http) throws Exception {
   //将RequestMatcher与Collection<ConfigAttribute> map封装到ExpressionBasedFilterInvocationSecurityMetadataSource
   FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
   if (metadataSource == null) {
      return;
   }
   //最终还是封装成Filter起到拦截作用
   FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
         http, metadataSource, http.getSharedObject(AuthenticationManager.class));
   if (filterSecurityInterceptorOncePerRequest != null) {
      securityInterceptor
            .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
   }
   securityInterceptor = postProcess(securityInterceptor);
   http.addFilter(securityInterceptor);
   http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
      FilterInvocationSecurityMetadataSource metadataSource,
      AuthenticationManager authenticationManager) throws Exception {
   FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
   securityInterceptor.setSecurityMetadataSource(metadataSource);
   securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
   securityInterceptor.setAuthenticationManager(authenticationManager);
   securityInterceptor.afterPropertiesSet();
   return securityInterceptor;
}
在上面代码中可以看到最终目的是调用HttpSecurity的addFilter()方法加入了一个Filter,注意这个FilterSecurityInterceptor持有了AuthenticationManager对象,AuthenticationManager是在beforeConfigure()方法使用通过构造函数传入的AuthenticationManagerBuilder创建的。如果匹配到需要认证的请求会使用到这个AuthenticationManager。

@Override
protected void beforeConfigure() throws Exception {
   setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}
既然知道了真正起到拦截作用的是FilterSecurityInterceptor,下面分析一下它的具体实现。

7、FilterSecurityInterceptor
通过过滤器实现执行HTTP资源的安全性处理。

public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
   FilterInvocation fi = new FilterInvocation(request, response, chain);
   invoke(fi);
}
 
public void invoke(FilterInvocation fi) throws IOException, ServletException {
   if ((fi.getRequest() != null)
         && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
         && observeOncePerRequest) {
      //过滤器已经应用于此请求,用户希望我们观察每次请求处理一次,因此不要重新进行安全检查
      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
   }
   else {
      // 第一次调用此请求时,执行安全检查
      if (fi.getRequest() != null && observeOncePerRequest) {
         fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
      }
      InterceptorStatusToken token = super.beforeInvocation(fi);
      try {
         fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
      }
      finally {
         super.finallyInvocation(token);
      }
      super.afterInvocation(token, null);
   }
}
FilterSecurityInterceptor的invoke()方法执行具体拦截行为,具体是beforeInvocation、finallyInvocation、afterInvocation这三个方法,这三个方法定义在父类AbstractSecurityInterceptor中。

AbstractSecurityInterceptor将确保安全拦截器的正确启动配置。它还将实现对安全对象调用的正确处理,即:

从SecurityContextHolder获取Authentication对象。
通过针对SecurityMetadataSource查找安全对象请求,确定请求是否与安全或公共调用相关。
对于受保护的调用(存在用于安全对象调用的ConfigAttributes列表):
如果Authentication.isAuthenticated()返回false,或者alwaysReauthenticate为true,则根据配置的AuthenticationManager对请求进行身份验证。进行身份验证时,将SecurityContextHolder上的Authentication对象替换为返回值。

根据配置的AccessDecisionManager授权请求。

通过配置的RunAsManager执行任何run-as替换。

将控制权传递回具体子类,实际上将继续执行该对象。返回一个InterceptorStatusToken,以便在子类完成对象的执行后,其finally子句可以确保使用finallyInvocation(InterceptorStatusToken)重新调用AbstractSecurityInterceptor并正确整理。

具体的子类将通过afterInvocation(InterceptorStatusToken,Object)方法重新调用AbstractSecurityInterceptor。

如果RunAsManager替换了Authentication对象,则将SecurityContextHolder返回到调用AuthenticationManager之后存在的对象。

如果定义了AfterInvocationManager,则调用调用管理器并允许它替换将返回给调用者的对象。

对于公共调用(安全对象调用没有ConfigAttributes):如上所述,具体的子类将返回一个InterceptorStatusToken,随后在执行安全对象后将其重新呈现给AbstractSecurityInterceptor。当调用afterInvocation(InterceptorStatusToken,Object)时,AbstractSecurityInterceptor不会采取进一步的操作。
控制再次返回具体子类,以及应返回给调用者的Object。然后子类将该结果或异常返回给原始调用者。
7.1、授权检查beforeInvocation()方法
protected InterceptorStatusToken beforeInvocation(Object object) {
   Assert.notNull(object, "Object was null");
   final boolean debug = logger.isDebugEnabled();
   if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
      throw new IllegalArgumentException(
            "Security invocation attempted for object "
                  + object.getClass().getName()
                  + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
                  + getSecureObjectClass());
   }
   //受保护调用的列表
   Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
         .getAttributes(object);
   //如果没有需要保护调用的资源,则直接返回null
   if (attributes == null || attributes.isEmpty()) {
      if (rejectPublicInvocations) {
         throw new IllegalArgumentException(
               "Secure object invocation "
                     + object
                     + " was denied as public invocations are not allowed via this interceptor. "
                     + "This indicates a configuration error because the "
                     + "rejectPublicInvocations property is set to 'true'");
      }
      if (debug) {
         logger.debug("Public object - authentication not attempted");
      }
      publishEvent(new PublicInvocationEvent(object));
      return null; // no further work post-invocation
   }
   if (debug) {
      logger.debug("Secure object: " + object + "; Attributes: " + attributes);
   }
   //没有认证对象会发事件抛异常
   if (SecurityContextHolder.getContext().getAuthentication() == null) {
      credentialsNotFound(messages.getMessage(
            "AbstractSecurityInterceptor.authenticationNotFound",
            "An Authentication object was not found in the SecurityContext"),
            object, attributes);
   }
   //检查是否已认证,否则调用authenticationManager进行认证
   Authentication authenticated = authenticateIfRequired();
   // Attempt authorization
   try {
      //使用accessDecisionManager对当前请求授权
      this.accessDecisionManager.decide(authenticated, object, attributes);
   }
   catch (AccessDeniedException accessDeniedException) {
      publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
            accessDeniedException));
      throw accessDeniedException;
   }
   if (debug) {
      logger.debug("Authorization successful");
   }
 
   if (publishAuthorizationSuccess) {
      publishEvent(new AuthorizedEvent(object, attributes, authenticated));
   }
 
   // Attempt to run as a different user
   Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
         attributes);
   //默认使用NullRunAsManager返回null
   if (runAs == null) {
      if (debug) {
         logger.debug("RunAsManager did not change Authentication object");
      }
 
      // no further work post-invocation
      return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
            attributes, object);
   }
   else {
      if (debug) {
         logger.debug("Switching to RunAs Authentication: " + runAs);
      }
      SecurityContext origCtx = SecurityContextHolder.getContext();
      SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
      SecurityContextHolder.getContext().setAuthentication(runAs);
      // need to revert to token.Authenticated post-invocation
      return new InterceptorStatusToken(origCtx, true, attributes, object);
   }
}
//如果Authentication.isAuthenticated()返回false或者属性alwaysReauthenticate已设置为true,
//则检查当前身份验证令牌并将其传递给AuthenticationManager。
private Authentication authenticateIfRequired() {
   Authentication authentication = SecurityContextHolder.getContext()
         .getAuthentication();
   if (authentication.isAuthenticated() && !alwaysReauthenticate) {
      if (logger.isDebugEnabled()) {
         logger.debug("Previously Authenticated: " + authentication);
      }
      return authentication;
   }
   authentication = authenticationManager.authenticate(authentication);
   // We don't authenticated.setAuthentication(true), because each provider should do
   if (logger.isDebugEnabled()) {
      logger.debug("Successfully Authenticated: " + authentication);
   }
   SecurityContextHolder.getContext().setAuthentication(authentication);
   return authentication;
}
对用户请求的认证是使用的authenticationManager.authenticate(),关于这部分请参考https://blog.csdn.net/shenchaohao12321/article/details/87721655,认证成功后会使用accessDecisionManager.decide()方法完成用户访问的授权,下面分析授权流程。

public void decide(Authentication authentication, Object object,
      Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
   int deny = 0;
   //轮询所有已配置的AccessDecisionVoters,有一个投票通过及代表授权成功
   for (AccessDecisionVoter voter : getDecisionVoters()) {
      int result = voter.vote(authentication, object, configAttributes);
      if (logger.isDebugEnabled()) {
         logger.debug("Voter: " + voter + ", returned: " + result);
      }
      switch (result) {
      case AccessDecisionVoter.ACCESS_GRANTED:
         return;
      case AccessDecisionVoter.ACCESS_DENIED:
         deny++;
         break;
      default:
         break;
      }
   }
   if (deny > 0) {
      throw new AccessDeniedException(messages.getMessage(
            "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
   }
   // To get this far, every AccessDecisionVoter abstained
   checkAllowIfAllAbstainDecisions();
}
这个具体实现只是轮询所有已配置的AccessDecisionVoters,并在任何AccessDecisionVoter肯定投票时授予访问权限。 只有在拒绝投票且没有肯定投票的情况下才能拒绝访问。如果每个AccessDecisionVoter都弃权,则决定将基于isAllowIfAllAbstainDecisions()属性(默认为false)。

那么decisionVoters是什么时候设置到这个AccessDecisionManager对象中去的?

AccessDecisionManager的实例化是在上面讲过的AbstractInterceptUrlConfigurer的createFilterSecurityInterceptor()方法创建FilterSecurityInterceptor时就创建的,这里在回顾一下:

private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
      FilterInvocationSecurityMetadataSource metadataSource,
      AuthenticationManager authenticationManager) throws Exception {
   FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
   securityInterceptor.setSecurityMetadataSource(metadataSource);
   securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
   securityInterceptor.setAuthenticationManager(authenticationManager);
   securityInterceptor.afterPropertiesSet();
   return securityInterceptor;
}
所以创建AccessDecisionManager的过程就是在org.springframework.security.config.annotation.web.configurers.AbstractInterceptUrlConfigurer#getAccessDecisionManager方法中:

private AccessDecisionManager getAccessDecisionManager(H http) {
   if (accessDecisionManager == null) {
      accessDecisionManager = createDefaultAccessDecisionManager(http);
   }
   return accessDecisionManager;
}
private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
   AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
   return postProcess(result);
}
这里实例化的具体AccessDecisionManager是AffirmativeBased,它的构造函数需要一个List<AccessDecisionVoter>,我们先看这个List是如何创建的:

org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer#getDecisionVoters():

@Override
@SuppressWarnings("rawtypes")
final List<AccessDecisionVoter<? extends Object>> getDecisionVoters(H http) {
   List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
   WebExpressionVoter expressionVoter = new WebExpressionVoter();
   expressionVoter.setExpressionHandler(getExpressionHandler(http));
   decisionVoters.add(expressionVoter);
   return decisionVoters;
}
private SecurityExpressionHandler<FilterInvocation> getExpressionHandler(H http) {
   if (expressionHandler == null) {
      DefaultWebSecurityExpressionHandler defaultHandler = new DefaultWebSecurityExpressionHandler();
      AuthenticationTrustResolver trustResolver = http
            .getSharedObject(AuthenticationTrustResolver.class);
      if (trustResolver != null) {
         defaultHandler.setTrustResolver(trustResolver);
      }
      ApplicationContext context = http.getSharedObject(ApplicationContext.class);
      if (context != null) {
         String[] roleHiearchyBeanNames = context.getBeanNamesForType(RoleHierarchy.class);
         if (roleHiearchyBeanNames.length == 1) {
            defaultHandler.setRoleHierarchy(context.getBean(roleHiearchyBeanNames[0], RoleHierarchy.class));
         }
         String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
         if (grantedAuthorityDefaultsBeanNames.length == 1) {
            GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
            defaultHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
         }
         String[] permissionEvaluatorBeanNames = context.getBeanNamesForType(PermissionEvaluator.class);
         if (permissionEvaluatorBeanNames.length == 1) {
            PermissionEvaluator permissionEvaluator = context.getBean(permissionEvaluatorBeanNames[0], PermissionEvaluator.class);
            defaultHandler.setPermissionEvaluator(permissionEvaluator);
         }
      }
      expressionHandler = postProcess(defaultHandler);
   }
   return expressionHandler;
}
从上面代码得知,AffirmativeBased中的List<AccessDecisionVoter>是一个WebExpressionVoter,vote()方法如下:

public int vote(Authentication authentication, FilterInvocation fi,
      Collection<ConfigAttribute> attributes) {
   assert authentication != null;
   assert fi != null;
   assert attributes != null;
   WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
   if (weca == null) {
      return ACCESS_ABSTAIN;
   }
   EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,fi);
   ctx = weca.postProcess(ctx, fi);
   return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
         : ACCESS_DENIED;
}
关于授权表达式的解析请参考【Spring-Security源码分析】Spring安全表达式解析。

7.2、清理AbstractSecurityInterceptor的工作finallyInvocation()方法
安全对象调用完成后,清理AbstractSecurityInterceptor的工作。 无论安全对象调用成功返回(即应在finally块中完成),都应在安全对象调用之后和afterInvocation之前调用此方法。

protected void finallyInvocation(InterceptorStatusToken token) {
   //默认beforeInvocation()方法中返回的InterceptorStatusToken的isContextHolderRefreshRequired为false
   if (token != null && token.isContextHolderRefreshRequired()) {
      if (logger.isDebugEnabled()) {
         logger.debug("Reverting to original Authentication: "
               + token.getSecurityContext().getAuthentication());
      }
      SecurityContextHolder.setContext(token.getSecurityContext());
   }
}
7.3、检查从安全对象调用返回的Object,能够修改Object或抛出AccessDeniedException。
如果开启了@EnableGlobalMethodSecurity会为FilterSecurityInterceptor注入一个afterInvocationManager。

protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
   if (token == null) {
      // public object
      return returnedObject;
   }
   finallyInvocation(token); // continue to clean in this method for passivity
   if (afterInvocationManager != null) {
      // Attempt after invocation handling
      try {
         returnedObject = afterInvocationManager.decide(token.getSecurityContext()
               .getAuthentication(), token.getSecureObject(), token
               .getAttributes(), returnedObject);
      }
      catch (AccessDeniedException accessDeniedException) {
         AuthorizationFailureEvent event = new AuthorizationFailureEvent(
               token.getSecureObject(), token.getAttributes(), token
                     .getSecurityContext().getAuthentication(),
               accessDeniedException);
         publishEvent(event);
         throw accessDeniedException;
      }
   }
   return returnedObject;
}
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值