跨域问题,对于网站开发的前后端而言是非常熟悉的,特别是前后端分离开发,跨域问题很普遍。那么对于跨域问题,你又了解多少呢?如果你对跨域跨域不是特别了解,或者开发中遇到了只是简单的百度,谷歌了一下解决办法,而不知道为什么这样解决,那么相信你读完这篇文章,会有收获的。

1. 产生跨域问题

要解决跨域问题,首先要有跨域问题,我们现在来把跨域问题制造出来。
准备两个项目:一个前端项目,一个后端项目。

后端项目api-server

后端项目端口设置为9090,对外提供了一个API接口如下:


/**
 * 用于测试跨域提供的后端接口
 * @author BobbyCao
 * @date 2022/8/20 23:29
 */
@Slf4j
@RestController
@RequestMapping("/api")
public class ApiController {

    @RequestMapping("/hello")
    public Result<String> sayHello() {
        log.debug("sayHello...");
        return Result.success("hello!");
    }

}

前端项目web-server

后端项目端口设置为9001,index.html页面如下(后端👨🏻‍💻一枚,前端代码边查边写,请见谅~):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>前后端项目跨域问题详解</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>

<body>

<!-- 使用ajax调用后端sayHello请求 -->
<button id=button_sayHello>sayHello</button>
<input id="msg_sayHello"/>
<br>

</body>

<script>

    <!-- 提取统一请求前缀 -->
    baseUrl = "http://localhost:9090/api";
    
    $("#button_sayHello").click(function () {
        $.ajax({
            url: baseUrl + '/hello',
            type: 'GET',
            dataType: 'json',
            success: function (res) {
                $("#msg_sayHello").val(res.data)
            }
        })
    })

</script>

</html>

产生跨域调用问题

首先我们直接访问后端的接口,是可以正常返回的
image

接下来我们通过前端项目页面来请求后端项目的接口,跨域问题就产生了
image.png

2. 产生跨域问题原因

2.1 同源策略(Same-origin policy)

同源策略是指在Web浏览器中,允许某个网页脚本访问另一个网页的数据,但前提是这两个网页必须有相同的协议、域名和端口号,一旦两个网站满足上述条件,这两个网站就被认定为具有相同来源。

以[http://www.example.com/dir/page.html]为例,下表中前三个与其同源,因为它们具有相同的协议、域名和端口号,只有路径不同。最后一个显示指定了端口,具体是否同源取决于浏览器的实现,而其他的三个要求都有不同的地方。

image.png

需要注意的是同源策略仅适用于脚本,这意味着某网站可以通过相应的HTML标签访问不同来源网站上的图片、CSS和动态加载脚本等资源。

2.2 开头跨域示例分析

因为我们的在[http://localhost:9001]去请求 [http://localhost:9090],它们的协议和域名相同,但是端口号不同,不同同源的,并且请求是通过AJAX发送的,符合同源策略的限制。

同源策略仅适用于脚本,对于通过HTML标签来访问其他网站的资源不会限制,这一点通过我们index.html中使用的<script>标签来引入其他域的js,使用<img>标签来引用其他域的图片就可以验证。
image.png
image.png

我们也使用现在使用比较多的fetch方式的调用来试一下,新增如下代码

<body>
<!-- 使用fetch调用后端sayHello请求 -->
<button id=button_sayHello_fetch>sayHello_fetch</button>
<input id="msg_sayHello_fetch"/>
<br>
</body>
<script>
    $("#button_sayHello_fetch").click(function(){
        fetch(baseUrl + "/hello").then( res =>{
            return res.json();
        }).then(dto => {
            $("#msg_sayHello_fetch").val(dto.data)
        })
    })
</script>

请求依然会跨域问题
image.png

2.3 产生跨域问题原因小结

同源策略的限制

  • 浏览器实行同源策略
  • 我们的请求是fetch或xhr(XMLHttpRequest)类型的

请求本身是跨域的

是因为我们的请求本身是跨域的,才会收到浏览器同源策略的限制,如果请求是同源的,那么就不会出现这个问题了。

3. 如何解决跨域问题

我们知道了产生跨域的原因,那么如何解决跨域问题,就明朗了很多。

  • 不发送fetch或xhr请求 ---> JSONP
  • 禁止浏览器实行跨源限制或让浏览器通过某种机制可以发送跨域请求 ---> CORS
  • 避免请求跨域 ---> 使用代理服务器

4. 禁止浏览器实行跨源限制

既然是浏览器因为同源策略限制了我们,那我们是否能解除浏览器的这个限制呢,答案是可以的,如Safari浏览器设置如下

image.png

Chrome浏览器执行以下命令也可解除限制,--user-data-dir需要换成你自己的

open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/bobbycao/share/ChromeDevUserData

image.png

虽然说不让浏览器做限制可以解决,但是这种方式没有什么意义,总不能让每个用户都来对自己的浏览器做配置吧。

5. JSONP

JSONP(JSON with Padding)是JSON的一种“使用模式”,可以让网页从别的网域获取资料。其原理是利用<script>标签向服务器请求JSON数据,这种做法不受同源策略限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

5.1 api-server提供支持JSONP的接口

    @GetMapping(value = "/jsonp", produces = "application/javascript")
    public String jsonp(String callback) {
        Result<String> result = Result.success("hello jsonp!");
        String jsonStr = JSONUtil.toJsonStr(result);
        return callback + "("+ jsonStr +")";
    }

5.2 web-server编写发送JSONP请求

</body>
<!-- 发送jsonp请求 -->
<button id=button_jsonp>jsonp</button>
<input id="msg_jsonp"/>
<br>
</body>

<script>
    $("#button_jsonp").click(function(){
        $.ajax({
            url: baseUrl + '/jsonp',
            // JSONP只支持get方式
            type: 'GET',
            dataType: 'jsonp',
            // 默认参数为callback,可以修改
            jsonp: "callback",
            success: function(res) {
                $("#msg_jsonp").val(res.data)
            }
        })
    })
</script>

5.3 测试JSONP请求非同源资源

image.png
请求成功了,通过控制台可以看到,jsonp请求的type是script,也就验证了上面的说法。参数名callback是默认约定的(可以进行修改,但要前后端保持一致),而callback参数的值是js回调函数的函数名,我们返回的json数据,作为回调函数的参数返回前端。这样就巧妙的避免了跨域问题。

5.4 使用fastjson实现后端接口支持JSONP

  1. 引入pom依赖
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

  1. api-server新增JsonpConfig配置类
  • 方式1
/**
 * 支持JSONP配置类
 * @author BobbyCao
 * @date 2022/8/20 23:42
 */
@Configuration
public class JsonpConfig {


    @Bean
    public FastJsonpResponseBodyAdvice fastJsonpResponseBodyAdvice() {
        // 默认处理参数名为callback和jsonp的请求,可以使用构造函数指定
        return new FastJsonpResponseBodyAdvice();
    }

    @Bean
    public FastJsonpHttpMessageConverter4 fastJsonpHttpMessageConverter4() {
        return new FastJsonpHttpMessageConverter4();
    }

}

  • 方式2
/**
 * 支持JSONP配置类
 * @author BobbyCao
 * @date 2022/8/20 23:42
 */
@Configuration
public class JsonpConfig {

    @Bean
    public JSONPResponseBodyAdvice jsonpResponseBodyAdvice() {
        return new JSONPResponseBodyAdvice();
    }

    @Bean
    public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
        return new FastJsonHttpMessageConverter();
    }

}
  1. controller接口适配

已/api/hello接口为例,使用方式1配置,后端接口不需要做任何修改,如果使用方式2,需要添加@ResponseJSONP注解,如下:

    @ResponseJSONP(callback = "jsonp")
    @RequestMapping("/hello")
    public Result<String> sayHello() {
        log.debug("sayHello...");
        return Result.success("hello!");
    }
  1. web-server编写jsonp请求后端/api/hello接口
<body>
<button id=button_sayHello_fastJsonp>sayHello_fastJsonp</button>
<input id="msg_sayHello_fastJsonp"/>
<br>
</body>

<script>
    $("#button_sayHello_fastJsonp").click(function () {
        $.ajax({
            url: baseUrl + '/hello',
            type: 'GET',
            dataType: 'jsonp',
            jsonp: "jsonp",
            success: function (res) {
                $("#msg_sayHello_fastJsonp").val(res.data)
            }
        })
    })
</script>
  1. 测试成功如下

image.png

5.5 JSONP的弊端

  • 只支持GET请求方式,不支持POST等方式
  • 服务器端需要改动代码支持(如果调用的不是自己的项目,无法改动服务器端代码)

所以现在很少有人使用JSONP,不是一个理想的方案。

6. 跨域资源共享CORS

6.1 CORS概念介绍

跨域资源共享(Cross-origin resource sharing,缩写:CORS),用于让网页的受限资源能够被其他域名的页面访问的一种机制。同源策略默认禁止某些“跨域”请求,尤其是Ajax请求。CORS定义了一种浏览器和服务器之间能互相确认是否足够安全已至于能使用跨域请求。CORS需要浏览器和服务器同时支持。目前,所有主流浏览器都支持该功能。也就是说CORS通信的过程,在浏览器端,都是由浏览器自动完成的,需要服务器端进行支持。

6.2 两种请求

浏览器在发送跨域请求的时候会判断是简单请求还是非简单请求,不同类型的请求,浏览器处理的方式是不同的。

6.2.1 简单请求

简单请求需要满足以下两个条件

  1. 请求方法是HEAD、GET、POST之一

  2. HTTP的请求头中不能超出以下范围
    -- Accept
    -- Accept-Language
    -- Content-Language
    -- Last-Event-ID
    -- Content-Type:为application/x-www-form-urlencoded、multipart/form-data、text/plain之一

6.2.2 非简单请求

不符合简单请求两点要求的都是非简单请求

6.3 简单请求的基本流程

对于简单请求,浏览器将会在请求头中添加一个Origin字段,值是来源(协议+域名+端口)的信息, 服务器根据这个值,来决定是否允许这次请求。我们举例来进行说明,假如用户访问[http://www.webserver.com],页面尝试跨域请求从[http://www.apiserver.com]获取数据。浏览器将向[http://www.apiserver.com]发出跨域请求,如下所示。

  1. 浏览器将添加Origin请求头,值为页面的域信息
Origin: http://www.webserver.com
  1. [http://www.apiserver.com]的服务器发送以下三个响应之一
  • 响应中的Access-Control-Allow-Origin头表示允许来自Origin的请求。例如:
Access-Control-Allow-Origin: http://www.webserver.com
  • 响应中带有通配符的Access-Control-Allow-Origin头,指示允许来自所有域的请求:
Access-Control-Allow-Origin: *
  • 如果服务器不允许跨域请求,则会出现错误页面
  1. 如果[http://www.apiserver.com]服务器允许请求中携带Cookie信息,将会添加Access-Control-Allow-Credentials相应头,需要注意的是,这种情况下,Access-Control-Allow-Origin不能指定为通配符*
Access-Control-Allow-Origin: http://www.webserver.com
Access-Control-Allow-Credentials: true
  1. 如果想要[http://www.apiserver.com]客户端端拿到除Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma六个简单的响应标头外,必须使用Access-Control-Expose-Headers并里进行指定
Access-Control-Expose-Headers: X-Custom-Header

6.4 非简单请求的基本流程

对于非简单请求,浏览器会发起一个额外的“预检”请求(preflight),以确定是否有权执行操作。浏览器只有得到服务器的允许,才会发出正确的xhr或fetch请求。还是以[http://www.webserver.com]页面对[http://www.apiserver.com]发送PUT类型的跨域操作为例:

  1. 浏览器将发送预检请求
OPTIONS /
Origin: http://www.webserver.com
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: PUT

除了Origin字段,预检请求还会包括两个特殊头字段:
Access-Control-Request-Method该字段表示,浏览器的CORS请求会用到哪些HTTP方法上,这里是PUT。
Access-Control-Request-Headers该字段表示浏览器CORS请求会额外发送的头字段信息,这里是content-type。

  1. 如果[http://www.apiserver.com]运行该预检请求,将会做出包含以下头的响应后,浏览器将发出实际请求。
Access-Control-Allow-Origin: http://www.webserver.com
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: PUT

Access-Control-Allow-Methods表示服务器支持跨域请求的方法。
Access-Control-Allow-Headers表示服务器支持跨域请求的所有头信息字段。

发出的实际请求,就像简单请求一样,会带上一个Origin头字段,服务器也会有一个Access-Control-Allow-Origin头字段。

  1. 如果[http://www.apiserver.com]不接受该预检请求,将错误地响应 OPTIONS请求,并且浏览器不会发出实际请求。

  2. 如果想对本次预检请求进行缓存可以使用Access-Control-Max-Age响应头进行设置,在缓存时间内,不再发出预检请求。

Access-Control-Max-Age: 3600

6.5 服务器对CORS支持

主要使用Filter来一步一步对CORS的工作流程来进行验证。

6.5.1 使用Filter实现

  1. 处理简单请求

对于一开始的跨域请求报错,在我们知道了CORS机制之后再回过来看,发现浏览器已经帮助我们添加了[Origin: http://localhost:9001],而且报错信息也很明显没有Access-Control-Allow-Origin响应头。
image.png
创建一个Filter实现如下

/**
 * @author BobbyCao
 * @date 2022/8/21 00:42
 */
@Component
public class CrossOriginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:9001");

        chain.doFilter(request, response);
    }

}

再次测试sayHello接口,得到了正确的响应,并且和CORS简单请求的处理方式一致
image.png
我们Access-Control-Allow-Origin设置为通配符*,也得到了正确的响应
image.png

  1. 处理携带Cookie的简单请求

api-server新增一个获取请求Cookie的接口如下

    @GetMapping("/with/cookie")
    public Result<String> withCookie(HttpServletRequest request) {
        log.debug("withCookie...");
        Cookie[] cookies = request.getCookies();
        StringJoiner joiner = new StringJoiner(";");
        if (Objects.nonNull(cookies)) {
            for (Cookie cookie : cookies) {
                joiner.add(cookie.getName() + "=" + cookie.getValue());
            }
        }
        return Result.success("withCookie: " + joiner);
    }

web-server新增携带Cookie的请求

<boy>
<button id="button_whitCookie">whitCookie</button>
<input id="msg_whitCookie"/>
<br>
</body>

<script>
    $("#button_whitCookie").click(function () {
        $.ajax({
            type: "GET",
            url: baseUrl + "/with/cookie",
            xhrFields: {
                // 携带Cookie需指定withCredentials为true
                withCredentials: true
            },
            success: function (res) {
                console.log(res)
                $("#msg_whitCookie").val(res.data)
            }
        });
    })
</script>

使用浏览器小工具写入Cookie信息
image.png
发送请求,报错如下,说明了携带Cookie时Access-Control-Allow-Origin不能设置为*
image.png
我们将Access-Control-Allow-Origin修改为[http://localhost:9001]报错如下,说明携带凭证时Access-Control-Allow-Credentials必须为true
image.png
在Filter中添加Access-Control-Allow-Credentials为true的设置,请求得到了正确的处理

@Component
public class CrossOriginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:9001");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");

        chain.doFilter(request, response);
    }
}

image.png

  1. 处理需要获取自定义响应头的简单请求

api-server新增一个接口如下

    @GetMapping("/expose/headers")
    public Result<Void> exposeHeaders(HttpServletResponse response) {
        response.setHeader("X-Custom-Header", "BobbyCao");
        log.info("exposeHeaders...");
        return Result.success();
    }

web-server新增获取响应头的请求

<boy>
<button id="button_exposeHeaders">exposeHeaders</button>
<input id="msg_exposeHeaders"/>
<br>
</boy>
<script>
    $("#button_exposeHeaders").click(function () {
        $.ajax({
            url: baseUrl + "/expose/headers",
            type: "GET",
            complete: function (xhr) {
                // 获取相关Http Response header
                var responseHeader = xhr.getResponseHeader("X-Custom-Header");
                console.log(responseHeader)
                $("#msg_exposeHeaders").val(responseHeader)
            }

        })
    })
</script>

测试请求如下

image.png

响应头中有X-Custom-Header但是我们并没有获取到,Controller添加Access-Control-Expose-Headers头设置,可以正常获取

    @GetMapping("/expose/headers")
    public Result<Void> exposeHeaders(HttpServletResponse response) {
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "X-Custom-Header");
        response.setHeader("X-Custom-Header", "BobbyCao");
        log.info("exposeHeaders...");
        return Result.success();
    }

image.png

  1. 发送非简单请求

api-server新增一个PUT接口如下

    @PutMapping("/put/json")
    public Result<User> putJson(@RequestBody User user) {
        log.debug("jsonp...");
        return Result.success(user);
    }

web-server新增发送PUT请求

<boy>
<button id="button_putJson">putJson</button>
<input id="msg_putJson_name"/>
<input id="msg_putJson_age"/>
<br>
</boy>
<script>
    $("#button_putJson").click(function () {
        $.ajax({
            url: baseUrl + "/put/json",
            type: "PUT",
            contentType: "application/json;charset=utf-8",
            data: JSON.stringify({username: "bobby", age: 18}),
            success: function (res) {
                console.log(res)
                $("#msg_putJson_name").val(res.data.username)
                $("#msg_putJson_age").val(res.data.age)
            }
        })
    })
</script>

测试结果如下,可以看到浏览器发送了一条预检请求,虽然状态码是200,但是还是报错了(这里看到的第二条xhr类型的报错请求,实际上并没有发送到服务器端),因为请求头中Access-Control-Request-Method指定了需要发送PUT类型的跨域请求,但是响应头中并没有Access-Control-Allow-Methods为PUT的响应头

image.png
image.png

在Filter中添加Access-Control-Allow-Methods为PUT的配置

@Component
public class CrossOriginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:9001");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "PUT");

        chain.doFilter(request, response);
    }

}

请求仍然报错,因为请求头中的Access-Control-Allow-Headers需要再跨域请求中携带的请求头,响应头中并没有Access-Control-Allow-Headers为content-type的响应头

image.png

在Filter中添加Access-Control-Allow-Headers的配置,预检请求得到了正确的响应,PUT请求也请求成功了

image.png
image.png

  1. 缓存预检请求

每次都发送预检请求,将会给服务器增加不必要的压力,在Filter中进行预检命令缓存配置

@Component
public class CrossOriginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://localhost:9001");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "PUT");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "content-type");
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");

        chain.doFilter(request, response);
    }

}


多次请求PUT接口,可以看到只发送了一次预检请求(记得不要勾选禁止缓存)
image.png

  1. 最终版Filter

api-server新增一个获取自定义请求头的接口如下

    @GetMapping("/with/header")
    public Result<String> withHeader(@RequestHeader("X-Custom-Header1") String header1,
                                     @RequestHeader("X-Custom-Header2") String header2) {
        log.debug("withHeader...");
        return Result.success("withHeader " + header1 + " " + header2);
    }

进行测试报错如下

image.png

也是响应头中的Access-Control-Allow-Headers没有请求头Access-Control-Request-Headers中包含的所有值,类似的Access-Control-Allow-Origin、Access-Control-Allow-Methods也不够灵活,我们做如下修改使其可以更加灵活的使用

package cn.caofanqi.apiserver.web.filter;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * @author BobbyCao
 * @date 2022/8/21 00:42
 */
@Component
public class CrossOriginFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;


        /*
         * 带cookie的时候,origin必须是全匹配,不能使用通配符*, 而且对外系统直接使用*也不安全;
         * 在实际使用中,可以获取请求头中的Origin,判断是否允许跨域访问,做不不同的设置;
         * 我们这里,直接将Origin中的值取出,设置到Access-Control-Allow-Origin中
         */
        String origin = httpServletRequest.getHeader(HttpHeaders.ORIGIN);
        if (StringUtils.hasText(origin)) {
            httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
        }

        // 允许跨域请求携带Cookie信息
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");

        // 设置允许跨域请求的方法
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*");

        // 从Access-Control-Request-Headers中获取获取请求会额外发送的头字段
        String headers = httpServletRequest.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
        if (StringUtils.hasText(headers)) {
            httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, headers);
        }

        // 设置预检请求缓存时间
        httpServletResponse.addHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");

        // 获取请求的方法
        String requestMethod = httpServletRequest.getMethod();
        // 如果是预检请求,这里直接进行返回
        if (HttpMethod.OPTIONS.toString().equals(requestMethod)) {
            httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
            return;
        }

        chain.doFilter(request, response);
    }

}

测试所有的跨域请求,都没有发生跨域问题

image.png

6.5.2 Spring对CORS的支持

Spring为我们提供了一个@CrossOrigin注解来对CORS进行支持,可以添加到类上,也可以添加到方法上

image.png

6.5.3 使用Nginx实现

一般我们的应用服务器之前,都会架设Nginx,我们也可以在Nginx上做CORS的支持

  1. 配置host

为我们的后端服务api-server配置一个host
image.png

  1. Nginx配置
    server{
        listen       80;
        server_name  apiserver.com;

        location / {

            proxy_pass   http://127.0.0.1:9090/;

            add_header Access-Control-Allow-Origin $http_origin;
            add_header Access-Control-Allow-Credentials true;
            add_header Access-Control-Allow-Methods *;
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            add_header Access-Control-Max-Age 3600;
          
            if ($request_method = OPTIONS) {
                return 204;
            }

        }
    }

image.png

  1. 需要修改web-server的调用路径为我们配置的
    image.png

7. 代理服务器

不管是JSONP还是CORS都是需要服务器来进行支持的,使用代理服务器,是一种隐藏跨域的方式。使用代理服务器进行请求转发,在浏览器看来,请求都是从同一个域发出的,所以不会有跨域问题。

7.1 使用Nginx作为代理服务器

  1. 配置host

image.png

  1. Nginx配置
server{
	listen 80;
	server_name webserver.com;

	location / {
		proxy_pass http://localhost:9001/;
	}

	location /apiserver/ {
		proxy_pass http://localhost:9090/api/;
	}

}

image.png

  1. 修改web-server的baseUrl

image.png

  1. 测试请求
    这里我们访问的就不是[http://localhost:9001/index.html]了,而是[http://webserver.com/index.html],进行测试,可以看到所有的请求都成功了,而且都不是跨域请求。因为在浏览器只能看到所有的请求都是从[http://webserver.com]发出去的

image.png



项目源码:
https://github.com/caofanqi/apiserver
https://github.com/caofanqi/webserver


参考资料:
https://en.wikipedia.org/wiki/Same-origin_policy
https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
https://en.wikipedia.org/wiki/JSONP
https://github.com/alibaba/fastjson/wiki/FastJsonpHttpMessageConverter4_CN-(%E5%B7%B2%E5%BA%9F%E5%BC%83)
https://www.php.cn/manual/view/35521.html