spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决

Posted Victor _Lv

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决相关的知识,希望对你有一定的参考价值。

一、配置方式

  在 Spring 框架下解决 CORS 问题,前面试了两种方法,发现在一种场景下,HTTP Response header 始终未应答 Access-Control-Allow-Origin:*

  (1)第一种方式,通过在 Controller 层增加 @CrossOrigin 注解。

@CrossOrigin
@RestController
@RequestMapping("/file")
public class FileController {
		...
}

  (2)第二种方式,利用 Spring 的 WebMvcConfigurer 中的 addCorsMappings


import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 支持跨域资源共享-CORS 配置
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        /**
         * addMapping: /** 表示所有路径及子路径下的 HTTP 应答都进行 Access-Control 标头包装
         * allowedOrigins: response header 中增加 Access-Control-Allow-Origin: * (表示允许所有 Origin 来源的跨域请求)
         * allowedMethods:response header 中增加 Access-Control-Allow-Methods: * (表示允许所有 HTTP Method)
         * allowedHeaders:response header 中增加 Access-Control-Allow-Headers: *
         * maxAge:response header 中增加 Access-Control-Max-Age: 1800 (表示建议浏览器缓存预检【Options请求】结果 1800s,可以降低服务端处理预检请求的压力)
         *
         * 配置解释参考:https://cloud.tencent.com/developer/article/1513418
         */
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .maxAge(1800);
    }
}

二、问题踩坑

  这两种方式测下来发现都有一个问题,就是如果 Origin 和 请求的 Url 地址是同源的( HTTP Method + host + port 完全一致则认为同源),则 Spring 框架并不会在 Response Header 中应答 Access-Control-Allow-Origin: *“同源访问时Spring不会返回Access-Control-Allow-Origin标头”,这个下的判断翻了下源码和Google了下还没找到理论依据,只是通过测试下的判断。Tips:翻看源码发现第一种加 @CrossOrigin 注解的方式跟第二种通过WebMvcConfigurer .addCorsMappings()配置, Spring 内部实现,其实是走的同一套,所以两种方式都会碰到同样的问题。

  然后正好我们有个场景,是内部的前端 Ajax 跨域调用到这个服务(假设叫 S1 服务,对应服务地址为 https://hello.com/file )上来,并且域名用的是一样的都是 hello.com(假设其他服务请求地址为:hello.com/server),但是这个域名支持 httpshttp两种方式访问。结果发现,用 httpsOrigin: https://hello.com/server的 Origin header 下访问https://hello.com/file 是会返回 Access-Control-Allow-Origin: *,但是换成 http 下去请求 https://hello.com/file就出问题了,并没有返回 Access-Control-Allow-Origin: *,导致前端Ajax请求被浏览器因 CORS 问题而挡掉。

三、原因分析

  按照刚才的判断: “同源访问时Spring不会返回Access-Control-Allow-Origin标头”,理论上Origin: https://hello.com/server 下请求 https://hello.com/file 是同源,不会出现 Access-Control-Allow-Origin: *,但偏偏刚好相反,origin = http & url = https 这个搭配出不来 Access-Controlorigin = https & url = https 这个搭配却出来了 Access-Control
  后来回想了下,也注意到了 Response 内容里的 Server: nginx/1.13.5,突然想起来 nginx 这类外部网关,会把外部进来的 https 请求解密解包,然后以 http 的方式转发给内网服务。
  这就解释通了上面的相反现象,origin = http & url = https 这种搭配下,因为 url 中的 https 经过 nginx 处理成 http 了,到了 Spring 层实际是个 http 请求,所以 Spring 判断其实是同源,于是没有应答 Access-Control-Allow-Origin: *;而origin = https & url = https 这种搭配,恰恰因为 nginx 处理成 http,到了 Spring 层判断时发现 origin 的是 https 地址,而 url 是 http,不同源,所以才有Access-Control
  当然,以上坑也只有在内部同域名下Ajax访问才有出现。提供给到外部服务时,一般两边域名就不一样,无论是 https / http 与否,Spring 肯定都会判断是 cross origin,所以都会有Access-Control

四、解决方案

  解决方案很简单,就是换一种方式,能够强制给 response header 加上Access-Control-Allow-Origin: *,不管 同源还是 Cross Origin 与否。
  可以自己在 Controller 方法上手工给 HttpServletResponse增加 header:

response.setHeader("Access-Control-Allow-Origin", "*");

这种办法很傻很原始,而且每个方法都得写一遍,更关键的是,针对 OPTIONS 预检请求(浏览器针对跨域请求在 POST 之前一般会先自动发 OPTIONS 预检请求,详细解释见 CORS跨域资源共享(一):模拟跨域请求以及结果分析,理解同源策略)也得加个方法处理 response,否则OPTIONS请求就会因为跨域而被 Block 掉。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@WebFilter(filterName = "CorsFilter")
@Configuration
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin","*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        chain.doFilter(req, res);
    }

参考文献:

  1. CORS跨域资源共享(一):模拟跨域请求以及结果分析,理解同源策略

  2. SpringBoot-实现CORS跨域原理及解决方案

以上是关于spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决的主要内容,如果未能解决你的问题,请参考以下文章

spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决

spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决

spring 控制器无法从 $http 角度检索数据

添加 CORS 过滤器后未找到 Access-Control-Allow-Origin 标头

Spring Boot CORS 配置不接受授权标头

将@EnableConfigServer注解放入spring boot后未启用Swagger