[SpringSecurity5.6.2源码分析十一]:CorsFilter

前言

  • 跨域:两个域名的(协议、域名/ip、端口)有任意一个不同即视为跨域
  • 跨域资源共享(Cors):即浏览器允许访问其他跨域的资源
  • 而CorsFilter就是SpringSecurity用来处理Cors的过滤器

1. CorsConfigurer

  • CorsConfigurer是CorsFilter对应的配置类,其中就只有一个重要方法
    • configure(…)

1.1 configure(…)

  • configure(…)源码很简单,主要是调用了getCorsFilter(…)方法
@Override
public void configure(H http) {
   ApplicationContext context = http.getSharedObject(ApplicationContext.class);
   // 创建CorsFilter
   CorsFilter corsFilter = getCorsFilter(context);
   Assert.state(corsFilter != null, () -> "Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
         + CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
   http.addFilter(corsFilter);
}
  • getCorsFilter(…):
    • 容器中若是有CorsFilter直接返回
    • 容器中若是有CorsConfigurationSource,那就为其包装为CorsFilter返回
private CorsFilter getCorsFilter(ApplicationContext context) {
   if (this.configurationSource != null) {
      return new CorsFilter(this.configurationSource);
   }
   //从容器中获得CorsFilter过滤器
   boolean containsCorsFilter = context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
   if (containsCorsFilter) {
      return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
   }
   //获取Cors匹配容器
   boolean containsCorsSource = context.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
   if (containsCorsSource) {
      CorsConfigurationSource configurationSource = context.getBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME,
            CorsConfigurationSource.class);
      return new CorsFilter(configurationSource);
   }
   boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, context.getClassLoader());
   if (mvcPresent) {
      return MvcCorsFilter.getMvcCorsFilter(context);
   }
   return null;
}

1.2 CorsConfiguration

  • CorsConfigurationSource是根据请求获取CorsConfiguration,也就Cors规则
  • 简单的看下里面的规则
    /**
     * Cors配置默认的允许的请求方式
     */
    private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
          Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));

    /**
     * 默认允许全部
     */
    private static final List<String> DEFAULT_PERMIT_ALL = Collections.singletonList(ALL);

    /**
     * 允许的来源
     */
    @Nullable
    private List<String> allowedOrigins;

    /**
     * 允许的请求方式
     */
    @Nullable
    private List<String> allowedMethods;

    /**
     * 允许的请求方式
     */
    @Nullable
    private List<HttpMethod> resolvedMethods = DEFAULT_METHODS;

    /**
     * 跨域请求允许携带的请求头
     */
    @Nullable
    private List<String> allowedHeaders;

    /**
     * 不懂
     */
    @Nullable
    private List<String> exposedHeaders;

    /**
     * 应该是客户端是否允许发送Cookie
     */
    @Nullable
    private Boolean allowCredentials;

    /**
     * 预检查请求的有效期
     */
    @Nullable
    private Long maxAge;

2. CorsFilter

  • 作为SpringSecurity处理Cors的过滤器,其源码很少
  • 就是从CorsConfigurationSource中获取CorsConfiguration,然后把CorsProcessor丢进CorsProcessor判断是否跨域请求以及对跨域请求的处理
public class CorsFilter extends OncePerRequestFilter {
   private final CorsConfigurationSource configSource;

   private CorsProcessor processor = new DefaultCorsProcessor();
   ...
   @Override
   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
         FilterChain filterChain) throws ServletException, IOException {

      CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
      boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
      if (!isValid || CorsUtils.isPreFlightRequest(request)) {
         return;
      }
      filterChain.doFilter(request, response);
   }
   ...
}

2.1 DefaultCorsProcessor

  • CorsProcessor是Spring-Web包下的,其实现也只有一个DefaultCorsProcessor
  • 我们看下其入口方法,主要就是非跨域请求直接放行,不是的话就调用handleInternal(…)方法
    @Override
    @SuppressWarnings("resource")
    public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
          HttpServletResponse response) throws IOException {

       //添加一些响应头
       Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY);
       if (!varyHeaders.contains(HttpHeaders.ORIGIN)) {
          response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
       }
       if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) {
          response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
       }
       if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) {
          response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
       }

       // 不是跨域请求直接放行
       if (!CorsUtils.isCorsRequest(request)) {
          return true;
       }

       if (response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) != null) {
          logger.trace("Skip: response already contains "Access-Control-Allow-Origin"");
          return true;
       }

       // 是否是跨域请求
       boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
       // 如果是预检查请求,但是又没有Cors数据源,就直接返回false
       if (config == null) {
          if (preFlightRequest) {
             rejectRequest(new ServletServerHttpResponse(response));
             return false;
          }
          else {
             return true;
          }
       }

       //  如果是预检查请求检查是否支持,其他类型的直接返回True
       return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
    }
  • 我们再看下handleInternal(…)方法:如果是预检查(跨域)请求检查是否支持,其他类型的直接返回True
    • 本质上就是在满足跨域规则的情况下,添加对应的响应头
    protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
          CorsConfiguration config, boolean preFlightRequest) throws IOException {

       String requestOrigin = request.getHeaders().getOrigin();

       // 检查来源
       // 获得请求来源
       String allowOrigin = checkOrigin(config, requestOrigin);
       if (allowOrigin == null) {
          logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
          rejectRequest(response);
          return false;
       }

       HttpHeaders responseHeaders = response.getHeaders();

       // 检查请求方式
       // 拿到此次预检查请求代表的真正请求的请求方式
       HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
       List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
       // 检查请求方式是否允许,为空就代表不支持
       if (allowMethods == null) {
          logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
          rejectRequest(response);
          return false;
       }

       // 检查请求头
       // 拿到预检查请求对应的的真正请求会携带的请求头
       List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
       // 检查请求头是否允许,为空就代表不支持
       List<String> allowHeaders = checkHeaders(config, requestHeaders);
       if (preFlightRequest && allowHeaders == null) {
          logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
          rejectRequest(response);
          return false;
       }

       // 告诉客户端允许来自这的请求
       responseHeaders.setAccessControlAllowOrigin(allowOrigin);

       // 是预检查请求, 告诉客户端允许这种请求方式
       if (preFlightRequest) {
          responseHeaders.setAccessControlAllowMethods(allowMethods);
       }

       if (preFlightRequest && !allowHeaders.isEmpty()) {
          responseHeaders.setAccessControlAllowHeaders(allowHeaders);
       }

       if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
          responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
       }

       // 估计是允许Cookie
       if (Boolean.TRUE.equals(config.getAllowCredentials())) {
          responseHeaders.setAccessControlAllowCredentials(true);
       }

       // 是预检查请求, 设置预检查请求有效期
       if (preFlightRequest && config.getMaxAge() != null) {
          responseHeaders.setAccessControlMaxAge(config.getMaxAge());
       }

       response.flush();
       return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值