Web开发中跨域请求问题

1,313 阅读7分钟

什么是跨域请求

同源策略

同源策略]是指在Web浏览器中,允许某个网页脚本访问另一个网页的数据,但前提是这两个网页必须有相同的URI、主机名和端口号,一旦两个网站满足上述条件,这两个网站就被认定为具有相同来源。此策略可防止某个网页上的恶意脚本通过该页面的文档对象模型访问另一网页上的敏感数据。

​ 同源策略对Web应用程序具有特殊意义,因为Web应用程序广泛依赖于HTTP cookie来维持用户会话,所以必须将不相关网站严格分隔,以防止丢失数据泄露。

​ 值得注意的是同源策略仅适用于脚本,这意味着某网站可以通过相应的HTML标签[2]访问不同来源网站上的图像CSS动态加载脚本等资源。而跨站请求伪造就是利用同源策略不适用于HTML标签的缺陷。

​ 下述哪些URL与URLwww.example.com/dir/page.ht… 属于相同来源:

跨域请求

​ 在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域,只要有1个条件不满足就是跨域请求。

​ 由于同源策略,一些跨域的请求(特别是Ajax)常常会被浏览器所禁止的,这在前后端分离的Web开发中是不可避免的问题。比如前端应用需要请求后台服务获取数据用来在前台展示,由于涉及跨域,请求会直接被浏览器禁止。本地后台服务接口为http://localhost:8080/api/city?cityName=shenzhen,前端代码是单独的html文件jquery_get.html。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>jQuery Http GET</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
    </script>
</head>
<body>
    <button>发送GET请求并获取返回结果</button>
    <p>结果数据:</p>
    <div id="app"></div>
    <script>
        $(document).ready(function(){
           $("button").click(function(){
                $.ajax({
                    type: "GET",
                    url: "http://localhost:8088/api/city",
                    data: {cityName: "shenzhen"},
                    dataType: "json",
                    success: function(data){
                        var html = '<ul>';
                        for(var v in data){
                            html += '<li>' + v + ':' + data[v] + '</li>'; 
                        }
                        html += '</ul>';
                        $('#app').html(html); 
                        alert("Get Succeed!");
                    },
                    error: function(data){
                        alert("Get Failed!");
                    }
                });
            });
       });
   </script>
</body>
</html>

默认情况下出现跨域访问错误:

Access to XMLHttpRequest at 'http://localhost:8080/api/city?cityName=shenzhen' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

如何实现跨域请求

JSONP

​ 由于同源策略,一般来说位于server1.example.com的网页无法与 server2.example.com的服务器沟通,而HTML]的 。用JSONP抓到的资料并不是JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析。

​ JSOP需要前后端配合完成,而且安全性不够好,目前已经较少采用,更多是使用CORS。

跨域资源共享(CORS)

​ 跨域资源共享定义了一种方式,为的是浏览器和服务器之间能互相确认是否足够安全以至于能使用跨域请求。比起纯粹的同源请求,这将更为自由和功能性的,但比纯粹的跨域请求更为安全。

​ 整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

CORS跨域请求的流程:

  1. 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。
  2. 服务器收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含 Access-Control-Allow-origin 字段,若配置过域名,则返回 Access-Control-Allow-origin + 对应配置规则里的域名的方式
  3. 浏览器根据接受到的 响应头里的 Access-Control-Allow-origin 字段做匹配,若无该字段,说明不允许跨域,从而抛出一个错误;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器接受该响应;若不同源,则说明该域名不可跨域,浏览器不接受该响应,并抛出一个错误。

​ CORS需要浏览器和服务器同时支持。对于Spring Boot应用,给Controller增加@CrossOrigin注解即可,如下所示。

//@CrossOrigin  //解决跨域访问问题-整个类级别
@RestController
public class CityRestController {
    @Autowired
    private CityService cityService;
    
    @CrossOrigin  //解决跨域访问问题-方法级别
    @RequestMapping(value = "/api/city", method = RequestMethod.GET)
    public City findOneCity(@RequestParam(value = "cityName", required = true) String cityName) {
        return cityService.findCityByName(cityName);
    }
}

浏览器发出的请求头 Request Headers:

GET /api/city?cityName=shenzhen HTTP/1.1
Host: localhost:8080     //服务端(响应方)地址
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Origin: null 	 		//客户端(请求方)所在域
Sec-Fetch-Site: cross-site		//说明当前是跨域访问
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

浏览器收到的响应头 Response Headers:

HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *  //@CrossOrigin注解产生的效果,说明任何域都可访问该接口
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 06 Sep 2020 16:05:53 GMT

服务器代理

​ 浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。比如将前端部署在Nginx下,Nginx作为调用后端服务的代理,这样前端的所有请求都直接指向Nginx地址,而由Nginx去具体请求后端服务。这样对于前端来说,它所有的请求都是在本域下发起的,就没有跨域的问题了。

​ 通过使用nginx反向代理,让前端调用自己所在域的接口(例如前端为http://localhost:8099,设置前端js代码访问http://localhost:8099/api/city?cityName=shenzhen)这样对浏览器而言不存在跨域访问的问题。通过配置反向代理将http://localhost:8099/api接口都指向http://localhost:8080/api,反向代理服务器作为中介完成数据传递,也就解决了跨域访问的问题。

​ 由于前端是本地的html文件,首先修改nginx的配置文件conf/nginx.conf,将http://localhost:8099映射到本地的html文件:

    server {
        listen       8099;
        server_name  localhost;
        location / {
            root   E:/dataVue/src;
            index  jquery_get.html;
        }
    }

​ 然后修改前端的html文件,改为调用同一个域下的接口(问题描述中第18行代码)

  url: "http://localhost:8099/api/city"

​ 最后在nginx配置文件中增加接口代理配置

    server {
        listen       8099;
        server_name  localhost;
        location / {
            root   E:/dataVue/src;
            index  jquery_get.html;
        }
        location ^~ /api {
             proxy_pass  http://localhost:8080/api/;
        }
    }

配置完成后,浏览器发出请求的请求头为:

GET /api/city?cityName=shenzhen HTTP/1.1
Host: localhost:8099
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin     #同域访问
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8099/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

浏览器收到的响应头为:

HTTP/1.1 200
Server: nginx/1.18.0
Date: Sun, 06 Sep 2020 16:34:43 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

如果没有配置反向代理,则有Request Headers:

GET /api/city?cityName=shenzhen HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
Origin: http://localhost:8099
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8099/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

并报如下错误:

Access to XMLHttpRequest at 'http://localhost:8080/api/city?cityName=shenzhen' from origin 'http://localhost:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

参考资料:

彻底理解浏览器的跨域

前后端分离情况下的跨域问题