403跨域错误 CORS 解决汇总 No 'Access-Control-Allow-Origin' header is present on the requested resource
CORS:全称"跨域资源共享"(Cross-origin resource sharing)。
跨域是由浏览器同源策略引起的,是指页面请求的接口地址,必须与页面的 url 地址(即请求接口的 页面 url)处于同域上(即域名,端口,协议相同)。这是为了防止某域名下的接口被其他域名下的网页非法调用,是浏览器对 JavaScript 施加的安全限制。
所谓同源是指"协议+域名+端口"三者相同,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。这里就只讲常见的几种解决防法。
无浏览器参与的,不存在跨域问题!
跨域解决方案
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 不能包含自定义头信息
简单请求处理流程
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-Credentials:true
表示是否允许发送 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)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些请求方法和自定义的头信息字段。只有得到肯定答复,浏览器才会发出正式的请求,否则就报错。
浏览器自动发出一个"预检"请求
"预检"请求用的请求方法是 OPTIONS,除了 Origin字段,"预检"请求的头信息还包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器使用的请求方法
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器会额外发送的头信息字段
服务器可设定的字段值
服务器收到"预检"请求以后,检查了Origin、Access-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.'); }); });
共 0 条评论