• Ukieweb

    佳的博客

    曾梦想仗剑天涯,后来工作忙没去。

403跨域错误 CORS 解决汇总 No 'Access-Control-Allow-Origin' header is present on the requested resource

CORS:全称"跨域资源共享"(Cross-origin resource sharing)。

跨域是由浏览器同源策略引起的,是指页面请求的接口地址,必须与页面的 url 地址(即请求接口的 页面 url)处于同域上(即域名端口协议相同)。这是为了防止某域名下的接口被其他域名下的网页非法调用是浏览器JavaScript 施加的安全限制

所谓同源是指"协议+域名+端口"三者相同,如果缺少了同源策略,浏览器很容易受到 XSSCSFR 等攻击。这里就只讲常见的几种解决防法。


浏览器参与的,不存在跨域问题!


跨域解决方案

1、 通过 jsonp 跨域

2、 document.domain + iframe跨域

3、 location.hash + iframe

4、 window.name + iframe跨域

5、 postMessage 跨域

6、 跨域资源共享(CORS)

7、 nginx 代理跨域

8、 nodejs 代理跨域

9、 WebSocket 协议跨域

1. JSONP

古老的 JSONP,他发送的不是 ajax 请求,而是利用了 script 标签加载机制

但是只支持get,而且存在安全问题。可以说,实际当中没人用,所以不做探讨

2. CORS 跨域解决方案

2014年1月16号 CORS 作为 http 协议扩充部分正式发布,主要定义了客户端服务端沟通机制,也就是所谓的协议。

特点是 :

前端无需做任何设置,跟平时发送 ajax 请求并无差异(目前几乎所有浏览器都支持CORS,若 IE 版本需 >10)

主要是靠服务端进行配置。而且是对各种请求方法、各种数据请求类型都是完美支持的。

浏览器CORS 请求分成类:简单请求(simple request)和 非简单请求(预检请求)not-so-simple request)。

2.1 CORS的简单请求

只要 同时满足 以下 三大条,就属于简单请求

(1) 请求方法是以下三种方法之一

  • HEAD

  • GET

  • POST

(2)Content-Type:只限于三个值: 

  • application/x-www-form-urlencoded

  • multipart/form-data

  • text/plain

(3) HTTP 不能包含自定义头信息

image.png

简单请求处理流程

1. 浏览器直接发出 CORS 请求:就是在头信息之中,增加一个Origin字段。(浏览器自动添加)

2. 服务器根据 Origin 的值,查看 Access-Control-Allow-Origin,决定是否同意这次请求(服务器需要在resp设定头信息 字段 Access-Control-Allow-XXXX

> 若 Origin 指定的源,不在许可范围内:

服务器返回一个正常的 HTTP 回应,浏览器发现这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。

>若  Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。


服务器可设定的字段值

  • 必须Access-Control-Allow-Origin: http://api.bob.com

    • 表示允许的域名

    • * 表示受任意域名的请求

    • 具体的域名

  • Access-Control-Allow-Credentialstrue

    • 表示是否允许发送 Cookie,

    • 默认情况 false :Cookie 不包括在CORS请求之中。

    • 若:设为 true ;浏览器的 withCredentials 属性也要 true

  • Access-Control-Expose-Headers: FooBar

    • 包含 额外的 头信息

    • 如这里的  FooBar 头信息

2.2 CORS的非简单请求 - 预检请求

除了简单请求,都是非简单请求,如:

  • 请求方法是: PUT 或 DELETE

  • Content-Type 字段的类型:application/json

非简单请求的 CORS 请求,会在正式通信之前增加一次HTTP查询请求(OPTIONS),称为"预检"请求(preflight)。

浏览器先询问服务器当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些请求方法自定义的头信息字段只有得到肯定答复,浏览器才会发出正式的请求否则就报错


image.png


浏览器自动发出一个"预检"请求

"预检"请求用的请求方法OPTIONS除了 Origin字段,"预检"请求的头信息包括两个特殊字段

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器使用的请求方法

(2)Access-Control-Request-Headers

该字段是一个逗号分隔字符串,指定浏览器会额外发送的头信息字段


服务器可设定的字段值

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-Method Access-Control-Request-Headers 字段以后,确认允许跨源请求,就可以做出回应

  • 必须 Access-Control-Allow-Origin: 和 简单请求一样

  • Access-Control-Allow-Credentials:  和简单请求一样

  • 必须 Access-Control-Allow-Methods: GET, POST, PUT

    • 逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法

    • 返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

  • Access-Control-Allow-Headers: X-Custom-Header

    • 逗号分隔字符串,表明服务器支持所有额外头信息字段,不限于浏览器在"预检"中请求的字段。

    • 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers字段是必需的

  • Access-Control-Max-Age: 1728000

    • 本次预检请求有效期,单位为(1728000 秒 为 20 天)

    • 允许缓存该条回应20天,缓存期间不用再发预检请求

3. nginx代理跨域

1、 nginx 配置解决 iconfont 跨域

浏览器跨域访问 js、css、img 等常规静态资源被同源策略许可,但 iconfont 字体文件(eot|otf|ttf|woff|svg)例外,此时可在 nginx 的静态资源服务器中加入以下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}

2、 nginx 反向代理接口跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。

nginx具体配置:

#proxy服务器

server{
       listen80;
       server_name www.test.com;
 
       location/ {
              proxy_pass http://localhost:8081/;
              #proxy_set_header Host $host:80;
              #proxy_set_header X-Real-IP $remote_addr;
              #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
              add_header Access-Control-Allow-Origin $http_origin;
              add_header Access-Control-Allow-Methods *;
              add_header Access-Control-Allow-Headers $http_access_control_request_headers;
              add_header Access-Control-Max-Age 60000;
              add_header Access-Control-Allow-Credentials true;
              
              if($request_method = OPTIONS){
                     return200;
              }
    }
}


4. nodejs 代理跨域

node 中间件实现跨域代理原理大致与nginx 相同,都是通过启用一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中 cookie 中域名,实现当前域的 cookie写入,方便接口登录认证。

方法 一: 非 vue 框架的跨域(2次跨域)

利用 node + express + http-proxy-middleware 搭建一个 proxy 服务器。


不多讲: 参考视频 https://www.bilibili.com/video/BV1we411W7Ai?seid=11626236958902666107


方法 二: vue 框架的跨域(2次跨域)

在开发环境下,由于 vue 渲染服务接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置 headers 跨域信息了。

webpack.config.js部分配置:

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些https服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
        }],
        noInfo: true
    }
}


5.  WebSocket协议跨域

WebSocket protocol 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是 server push 技术的一种很好的实现

原生 WebSocket API 使用起来不太方便,我们使用 Socket.io,它很好地封装了 webSocket 接口,提供了更简单、灵活的接口,也对不支持 webSocket 的浏览器提供了向下兼容。

前端

<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect', function() {
    // 监听服务端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg); 
    });
    // 监听服务端关闭
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.'); 
    });
});
document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

后台

var http = require('http');
var socket = require('socket.io');
// 启http服务
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });
    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});






参考文档

mozilla 跨源资源共享(CORS)

前端常见跨域解决方案(全)



1
0
下一篇:以爱之名

0 条评论

老佳啊

85后,大专学历,中原人士,家里没矿。

由于年轻时长的比较帅气,导致在别人眼里,我一直不谈恋爱的原因是清高,实则是自己的小自卑。最大的人生目标就是找一个相知相爱相容的人,共度余生。

和人相处时如果能感受到真诚,会非常注重彼此的关系,对别人没有什么心机,即使有利益冲突,一般也会以和为贵,因为在这个世界上,物质的东西,从来不会吸引到我。

特别迷恋那些大山大水,如果现在还能隐居,可能早就去了。对那些宏伟的有底蕴的人文景观比较不感冒。

从事于IT行业,却一直对厨房念念不忘,由于身材魁梧,总觉得自己上辈子是个将军,可惜这辈子没当兵,也不会打架。