浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
前后端分离的模式下,前后端的域名是不一致的,所以就会出现跨域问题。
而浏览器为了安全问题一般都会限制跨域访问,也就是不允许跨域请求资源。所以出现了浏览器的同源策略。
同源策略:指协议、域名、端口都要相同,其一不同都会出现跨域。
同源策略限制的几种行为:
- 无法读取Cookie、LocalStorage和IndexDB*(IndexDB是浏览器提供的本地数据库,可以被网页脚本创建和操作,允许存贮大量数据,提供查找接口,能建立索引。)*
- 无法获得DOM和JS对象
- 不能发送AJAX请求
url | 说明 | 是否允许通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js | 同一域名下 | 允许 |
http://www.a.com/a/a.js http://www.a.com/b/b.js | 同一域名下不同文件夹 | 允许 |
http://www.a.com:8000/a.js http://www.a.com/b.js | 同一域名下不同端口 | 不允许 |
http://www.a.com/a.js http://192.168.10.11/b.js | 域名和该域名对应ip | 不允许 |
http://www.a.com/a.js http://index.a.com/b.js http://a.com/c.js | 主域相同,子域不同 | 不允许 |
http://www.a.com/a.js http://www.b.com/b.js | 不同域名 | 不允许 |
http://www.a.com/a.js https://www.a.com/b.js | 同域名,不同协议 | 不允许 |
jsonp的原理是利用 <script>标签没有跨域限制,通过<script>中的src属性,发送带有callback参数的get请求,服务端将接口返回拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
jsonp解决跨域问题的缺点是: 只能发送get一种请求。
- 原生js实现
<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script>
服务端返回如下(返回时即执行全局函数)
handleCallback({"success": true, "user": "admin"})
- vue的axios实现
this.$http = axios; this.$http.jsonp('http://www.domain2.com:8080/login', { params: {}, jsonp: 'handleCallback' }).then((res) => { console.log(res); })
后端node.js代码
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
- jquery ajax实现
$.ajax({ url: 'http://www.domain2.com:8080/login', type: 'get', dataType: 'jsonp', // 请求方式为jsonp jsonpCallback: "handleCallback", // 自定义回调函数名 data: {} });
2.CORS(跨域资源共享)方法
CORS(Cross-origin resource sharing)是一个W3C标准,它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了ajax的同源策略。CORS需要浏览器和服务器同时支持。目前,CORS在现代浏览器都支持(IE不能低于10)。
它有以下几个特点:
- CORS通信过程是由浏览器自己完成的,不需要用户参与。
- 对于开发者来说CORS通信和同源的ajax通信是一样的,代码也完全一致。
- 在使用CORS通信的过程中,一旦发起了ajax跨域请求,会自动添加一些附加的头信息,有时候还会多附加一次请求,用户不会感知到。
- 实现CORS的关键在于服务器,只要服务器实现了,就可以进行CORS通信。
浏览器将cors跨域请求分为简单请求和非简单请求。
- 简单请求:一次请求
- 非简单请求:两次请求,在发送数据之前会发一次请求用于“预检”,只有预检通过了才再发送第二次请求用于数据传输。
只要同时满足以下两个条件,就属于简单请求。
(1)请求方法是以下三种方法之一:(如果请求方法是put / deletde等肯定是非简单请求)
- 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,(如果你发送的application/json格式的数据,那么肯定是非简单请求,vue的axios默认的请求体信息格式是json的,ajax默认是urlencoded的。)
简单请求:服务器设置响应头:Access-Control-Allow-Origin = ‘域名’ 或 ‘*’ (必选)
cors请求设置的响应头字段:
字段 | 解释 | 是否必选 |
---|---|---|
Access-Control-Allow-Origin | 它的值是请求时Origin字段的值或‘ * ’,‘ * ’表示接受任意域名的请求 | 必选 |
Access-Control-Allow-Credentials | 它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求中。 | 可选 |
Access-Control-Control-Expose-Headers | CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段。如果想拿到其他字段,就必须在这个字段里面指定。 | 可选 |
非简单请求:
非简单请求时,首先发送“预检”请求,如果“预检成功”,则发送真实数据。
1.预检请求
允许请求方式则需服务器设置响应头:Access-Control-Request-Method (必选)
response.addHeader("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE");
允许请求头则需设置响应头:Access-Control-Request-Headers (可选)
response.addHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With,
Content-Type,Accept,jssesionId,appId,sign,nonceStr");
2.预检请求的回应
服务器收到“预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
HTTP 回应中,除了关键的是Access-Control-Allow-Origin字段,其他CORS相关字段如下:
字段 | 解释 | 是否必选 |
---|---|---|
Access-Control-Allow-Methods | 它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。 | 必选 |
Access-Control-Allow-Headers | 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。 | - |
Access-Control-Allow-Credentials | 它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求中。(与简单请求含义相同) | 可选 |
Access-Control-Max-Age | 用来指定本次预检请求的有效期,单位:秒 | 可选 |
CORS跨域实例:
前端设置:
- 原生ajax:
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 // 前端设置是否带cookie xhr.withCredentials = true; xhr.open('post', 'http://www.domain2.com:8080/login', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } };
- jQuery的ajax:
$.ajax({ ... xhrFields: { withCredentials: true // 前端设置是否带cookie }, crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie ... });
服务端设置:
- nodejs代码
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 数据块接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
/*
* 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
* 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
*/
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');
按照我理解的改了点,其他解决跨域方法看链接↓9种常见的前端跨域解决方案(详解)_他夏了夏天吖的博客-CSDN博客_前端跨域解决方案
四、总结
- jsonp(只支持get请求,支持老的IE浏览器),适合加载不同域名的js、css、img等静态资源
- CORS(支持所有类型的HTTP请求,但不支持IE10以下的),适合做ajax各种跨域请求
- Nginx代理跨域和nodejs中间件跨域原理相似,都是搭建一个服务器,直接在服务器端请求HTTP接口,适合前后端分离的前端项目调后端接口。
- document.domain+iframe适合主域名相同,子域名不同的跨域请求。
- postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10以上