如何为不同的 URL 堆叠 CORS 过滤器?

Posted

技术标签:

【中文标题】如何为不同的 URL 堆叠 CORS 过滤器?【英文标题】:How do I stack CORS filters for different URLs? 【发布时间】:2019-04-29 02:29:28 【问题描述】:

我有一个 Spring Boot 2 REST API 作为我的 React 应用程序的后端。网站和 API 托管在不同的子域上(分别为 https://example.comhttps://api.example.com),所以我设置了一个 CORS 过滤器来支持这一点:

@Bean
public FilterRegistrationBean corsFilter() 
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration apiCorsConfig = new CorsConfiguration();
    apiCorsConfig.addAllowedOrigin("https://example.com");
    apiCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    apiCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    apiCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/**", apiCorsConfig);

    FilterRegistrationBean filterRegistrationBean =
        new FilterRegistrationBean<>(new CorsFilter(source));
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return filterRegistrationBean;

这很好用。


我现在已经实现了 Spring SAML 来支持 SAML 作为对 API 进行身份验证的替代方法。所有 SAML 端点都放在/saml/** 下。 SAML 身份验证请求可以来自任何来源,因此我需要打开这些端点以允许来自任何来源的 CORS 请求。

我尝试了以下方法:

@Bean
public FilterRegistrationBean corsFilter() 
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration apiCorsConfig = new CorsConfiguration();
    apiCorsConfig.addAllowedOrigin("https://example.com");
    apiCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    apiCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    apiCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/**", apiCorsConfig);

    CorsConfiguration samlCorsConfig = new CorsConfiguration();
    samlCorsConfig.addAllowedOrigin(CorsConfiguration.ALL);
    samlCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    samlCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    samlCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/saml/**", samlCorsConfig);

    FilterRegistrationBean filterRegistrationBean =
        new FilterRegistrationBean<>(new CorsFilter(source));
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return filterRegistrationBean;

但是,这不起作用,当未知来源尝试访问 /saml/** 端点时,会返回 403 Invalid CORS request


我想这里的问题是更通用的 /** 模式优先,即使有更具体的 ant-pattern /saml/** 匹配请求。

我不想明确配置所有其他端点以允许网站来源,但希望它成为与任何其他 CORS 配置不匹配的请求的包罗万象。


我怎样才能允许任何来源访问/saml/**,同时仍然只允许https://example.com 访问所有其他端点/**

【问题讨论】:

我认为你可以用@CrossOrigin('*') 注释你的控制器类,它使用了@RequestMapping('/saml') 没有明确的 SAML 控制器,因为这些端点由 Spring SAML 处理。 该答案解决了多个预定义的允许来源保护单个路径模式,这与我的问题不同。 CORS with spring-boot and angularjs not working的可能重复 【参考方案1】:

您可以扩展UrlBasedCorsConfigurationSource 并覆盖getCorsConfiguration

public class CustomeUrlBasedCorsConfigurationSource extend UrlBasedCorsConfigurationSource

        @Override
        @Nullable
        public CorsConfiguration getCorsConfiguration(HttpServletRequest request) 
            String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
            if(this.pathMatcher.match("/saml/**",lookupPath))
              return this.corsConfigurations.get("/saml/**");
             
            return super.getCorsConfiguration(request);
       

您的配置应该类似于:

@Bean
public FilterRegistrationBean corsFilter() 
    CustomeUrlBasedCorsConfigurationSource source = new CustomeUrlBasedCorsConfigurationSource();

    CorsConfiguration apiCorsConfig = new CorsConfiguration();
    apiCorsConfig.addAllowedOrigin("https://example.com");
    apiCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    apiCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    apiCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/**", apiCorsConfig);

    CorsConfiguration samlCorsConfig = new CorsConfiguration();
    samlCorsConfig.addAllowedOrigin(CorsConfiguration.ALL);
    samlCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    samlCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    samlCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/saml/**", samlCorsConfig);

    FilterRegistrationBean filterRegistrationBean =
        new FilterRegistrationBean<>(new CorsFilter(source));
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return filterRegistrationBean;

【讨论】:

感谢您的回答,这应该可以解决问题。但是,我不喜欢将端点配置复制到单独的类中。一个想法是有一个UrlBasedCorsConfigurationSource 实现,它按照添加的顺序遍历不同的CorsConfigurations(而不是像UrlBasedCorsConfigurationSource 那样以未定义的顺序循环遍历条目集)。然后,您可以先添加更具体的CorsConfigurations,如果没有找到是否会回退到最一般的/** 好吧,我会被诅咒的。情况已经如此,因为CorsConfigurations 被添加到保持其顺序的LinkedHashMap。然后只需按正确的顺序添加CorsConfigurations。感谢您帮我找到这个!【参考方案2】:

CorsConfigurations 是通过将它们添加到 UrlBasedCorsConfigurationSource 内的 LinkedHashMap 来注册的。这意味着订单被保留,只需按正确的顺序添加CorsConfigurations:

@Bean
public FilterRegistrationBean corsFilter() 
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration samlCorsConfig = new CorsConfiguration();
    samlCorsConfig.addAllowedOrigin(CorsConfiguration.ALL);
    samlCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    samlCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    samlCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/saml/**", samlCorsConfig);

    CorsConfiguration apiCorsConfig = new CorsConfiguration();
    apiCorsConfig.addAllowedOrigin("https://example.com");
    apiCorsConfig.addAllowedHeader(CorsConfiguration.ALL);
    apiCorsConfig.addAllowedMethod(CorsConfiguration.ALL);
    apiCorsConfig.setMaxAge(600L);
    source.registerCorsConfiguration("/**", apiCorsConfig);

    FilterRegistrationBean filterRegistrationBean =
        new FilterRegistrationBean<>(new CorsFilter(source));
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return filterRegistrationBean;

【讨论】:

以上是关于如何为不同的 URL 堆叠 CORS 过滤器?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 工作台,如何为日期创建过滤器以安排组

用于 localhost 和暂存 url 的 Cors 过滤器

Quartz Composer - 如何为Core Image过滤器指定目标大小?

如何为事务管理编写自定义过滤器

如何为一个特定的 servlet 应用过滤器?

如何为 QTableWidget 创建过滤器?