导语: 在平常的开发过程当中,跨域是一个使人头疼的事情,也在跨域的过程当中学到了很多知识,也吃了很多跨域的亏,一直没有时间整理这部分的知识点,如今就我脑海中的跨域知识以及结合开发过程当中遇到的坎进行一个系统的总结。javascript
说到跨域,就不得不提游览器的同源安全策略。按照MDN所说,同源策略限制了从同一个源加载的文档或者脚本如何和来自另外一个源的文档和脚本等资源进行交互,这是阻止恶意文件程序攻击的一个重要安全机制。css
同源就是协议(http\https),域名(www.taobao.com)和端口号(80,8080)都相同,这才称之为同源,与之相对的皆是不一样源,是跨域的范围了。html
这里列一个表格来讲明一下:vue
假设这里有一个url为http://www.example.com/a.html
, 想要请求如下地址,默认端口为80。java
序号 | url | 是否同源 | 缘由 |
---|---|---|---|
1 | http://www.example.com/b.html |
是 | 协议、域名、端口相同,只是文件路径不一样 |
2 | https://www.example.com/c.html |
否 | 协议不一样(http\https) |
3 | http://store.example.com/c.html |
否 | 子域域名不一样(www\store) |
4 | http://www.example.com:81/c.html |
否 | 端口不一样(80/81) |
5 | https://shop.example.com:82/c.html |
否 | 协议、域名、端口都不一样 |
注意:有时候w3c的标准到了ie这里就行不通了。因此根据ie游览器的标准,有两个不一样点:一是授信范围,也就是高度互信的两个域名,不受同源限制;二是端口不一样的两个域名,不受同源限制。node
据我所知,跨域有如下几种方法:jquery
采用这种方法,是因为html标签src
属性不受同源限制,下面就封装一个jsonp方法来进行跨域资源请求。ios
function jsonp({url,callback}) {
let script = document.createElement('script');
script.src = `${url}&callback=${callback}`;
document.head.appendChild(script);
}
复制代码
一个简单的案例:nginx
先用node开一个3000端口,做为服务端,启动node server.js
。git
// 保存为server.js
const http = require('http');
const url = require('url');
const queryString = require('querystring');
const data = JSON.stringify({
title: 'hello,jsonp!'
})
const server = http.createServer((request, response) => {
let addr = url.parse(request.url);
if (addr.pathname == '/jsonp') {
let cb = queryString.parse(addr.query).callback;
response.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' })
response.write(cb + '('+ data +')');
} else {
response.writeHead(403, { 'Content-Type': 'text/plain;charset=utf-8' })
response.write('403');
}
response.end();
})
server.listen(3000, () => {
console.log('Server is running on port 3000!');
})
复制代码
最后请求返回内容。
jsonp({
url: 'http://localhost:3000/jsonp?from=1',
callback: 'getData',
})
function getData(res) {
console.log(res); // {title: "hello,jsonp!"}
}
复制代码
<script src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"></script>
$(function () {
$.ajax({
url: 'http://localhost:3000/jsonp?from=1',
type: 'get',
dataType: 'jsonp',
success: function(res) {
console.log(res); // {title: "hello,jsonp!"}
}
})
})
复制代码
CORS(Cross -Origin Resource Sharing),跨域资源共享,是一个W3C标准,在http的基础上发布的标准协议。
CORS须要游览器和服务器同时支持,解决了游览器的同源限制,使得跨域资源请求得以实现。它有两种请求,一种是简单请求,另一种是非简单请求。
知足如下两个条件就属于简单请求,反之非简单。
1)请求方式是GET
、POST
、HEAD
; 2)响应头信息是Accept
、Accept-Language
、Content-Language
、Last-Event-ID
、Content-Type
(只限于application/x-www-form-urlencoded
、multipart/form-data
、text/plain
);
简单请求有三个CORS字段须要加在响应头中,前面部分都是以Access-Control
开头:
1.Access-Control-Allow-Origin
,这个表示接受哪些域名的请求,若是是*号,那就是任何域名均可以请求; 2.Access-Control-Allow-Credentials
,这个表示是否容许携带cookie,默认是false,不容许携带;
若是设置为true
, 要发送cookie,容许域就必须指定域名方法;客户端http请求必须设置:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
复制代码
3.Access-Control-Expose-Headers
,这个表示服务器可接受的响应头字段,若是客户端发送过来请求携带的Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
,还有自定义请求头字段。
例如:请求一个本地get接口。
本地服务端开启访问限制,打开server.js
,输入如下内容:设置容许域名为http://localhost:8089
,容许访问请求方式是POST
请求。
const http = require('http');
const url = require('url');
const queryString = require('querystring');
const data = JSON.stringify({
title: 'hello,jsonp!'
})
const dataCors = JSON.stringify({
title: 'hello,cors!'
})
const server = http.createServer((request, response) => {
let addr = url.parse(request.url);
response.setHeader("Access-Control-Allow-Origin", 'http://localhost:8089');
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
response.setHeader("Access-Control-Allow-Methods","POST");
response.setHeader("Content-Type", "application/json;charset=utf-8");
if (addr.pathname == '/jsonp') {
let cb = queryString.parse(addr.query).callback;
response.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' })
response.write(cb + '('+ data +')');
} else if (addr.pathname == '/test'){
if (request.method == 'POST') {
response.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' })
response.write(dataCors);
} else {
response.writeHead(404, { 'Content-Type': 'text/plain;charset=utf-8' })
}
} else {
response.writeHead(403, { 'Content-Type': 'text/plain;charset=utf-8' })
response.write('403');
}
response.end();
})
server.listen(3000, () => {
console.log('Server is running on port 3000!');
})
复制代码
express框架设置以下:
app.all("*", function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-control-Allow-Headers", "X-Auth");
res.header("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS,HEAD,FETCH");
res.header("Access-control-max-age", 60*60*24); //测试经过
next();
})
复制代码
若是使用get访问http://localhost:3000/test
这个接口地址,那么就会报错404。
httpReq这个方法在这篇文章查看。
async function getReqData() {
let data = await httpReq({
type: 'get',
url: 'http://localhost:3000/test',
data: null,
})
console.log(data);
}
getReqData();
复制代码
若是是post访问,就会返回正常内容。
async function getReqData() {
let data = await httpReq({
type: 'post',
url: 'http://localhost:3000/test',
data: null,
})
console.log(data);
}
getReqData();
复制代码
就是除了简单请求的几种方法外,好比说PUT
请求、DELETE
请求,这种都是要发一个预检请求的,而后服务器容许,才会发送真正的请求。
非简单请求有如下几个字段须要传递:
1.Access-Control-Allow-Methods
,值是以逗号分隔,好比:GET,POST,DELETE
; 2.Access-Control-Allow-Headers
,值是默认字段或者自定义字段,例如:X-Auth-Info
; 3.Access-Control-Allow-Credentials
,是否携带cookie信息; 4.Access-Control-Max-Age
,表明预检请求的有效期限,单位是秒。
例如:如下这个put请求,服务端设置一个put请求接口,这里使用axios来请求。
// 设置返回信息
const dataUpdate = JSON.stringify({
title: 'update success!'
})
// 设置容许
response.setHeader("Access-Control-Allow-Methods","POST,PUT");
response.setHeader("Access-Control-Allow-Credentials",false);
response.setHeader("Access-Control-Max-Age", 60*60*24);
if (addr.pathname == '/update'){
if (request.method == 'PUT') {
response.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8' })
response.write(dataUpdate);
} else {
response.writeHead(404, { 'Content-Type': 'text/plain;charset=utf-8' })
}
}
复制代码
客户端请求,返回内容。
<script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
async function saveInfo () {
let data = await axios.put('http://localhost:3000/update', {
title: 'far',
body: 'bar',
userId: 121,
})
console.log(data);
}
saveInfo();
复制代码
这种方法只适用于子域不一样时候的跨域请求,可使用document.domain
来设置。
好比:map.domaintest.org
子域名指向根域名domaintest.org
,可使用下面的设置。
if (document.domain != 'domaintest.org') {
document.domain = 'domaintest.org';
}
复制代码
例如:
async function saveInfo () {
let data = await httpReq({
type: 'get',
url: 'http://map.domaintest.org:8089/ky.html',
data: null,
})
console.log(data);
}
saveInfo();
if (document.domain != 'domaintest.org') {
document.domain = 'domaintest.org';
}
复制代码
看状况,使用谷歌请求,不加这段也能够成功请求到子域的页面内容。
这个post Message能够安全的实现跨源通讯,适用于父页面和子页面或是两个不一样页面之间的请求,iframe的状况就适合用这个。
父页面经过postMessage('<msg>','<url>')
,子页面接收消息,而且返回消息到父页面,父页面监听message
事件接收消息。
例如:http://map.domaintest.org:8089/parent.html
发送消息给子页面http://map.domaintest.org:8089/son.html
,子页面返回消息。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>父级页面</title>
</head>
<body>
<button id="btn">发送消息</button>
<iframe id="child" src="http://map.domaintest.org:8089/son.html" width="100%" height="300"></iframe>
<script> let sendBtn = document.querySelector('#btn'); sendBtn.addEventListener('click', sendMsg, false); function sendMsg () { window.frames[0].postMessage('getMsg', 'http://map.domaintest.org:8089/son.html'); } window.addEventListener('message', function (e) { let data = e.data; console.log('接收到的消息是:'+ data); }) </script>
</body>
</html>
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>子页面</title>
</head>
<body>
<h2>窗口</h2>
<p>我是另一个窗口!</p>
<script> window.addEventListener('message', function (e) { if (e.source != window.parent) return; window.parent.postMessage('我是来自子页面的消息!', '*'); }, false) </script>
</body>
</html>
复制代码
有时候可使用代理的方式来发送跨域请求,好比axios的跨域代理,nodejs跨域代理,nginx跨域代理。下面介绍常见的几种跨域代理设置方法。
建立实例创建代理:
import axios from 'axios';
var server = axios.create({
baseURL: 'https://domain.com/api/',
timeout: 1000,
});
复制代码
在config/index.js
中设置:
module.exports = {
dev: {
//...
proxyTable: {
'/api': {
target: 'http://10.0.100.7:8081', //设置调用接口域名和端口号别忘了加http
changeOrigin: true,
pathRewrite:{
"^/api":""
}
}
},
}
复制代码
接口调用时:
this.axios.get('/api/user',{
params: {
userId:'1'
}
}).then(function (res) {
console.log(res);
}).catch(function (err) {
console.log(err);
})
复制代码
按照代理代理包
npm i -S http-proxy
复制代码
设置跨域
var http = require('http');
var url=require('url');
var path=require('path');
var httpProxy = require('http-proxy');
//服务端口
var PORT = 8080;
//接口前缀
var API_URL='api';
//真正的api地址
var API_DOMAIN='http://www.example.com/';
//建立一个代理服务器
var proxy = httpProxy.createProxyServer({
target: API_DOMAIN,
});
//代理出错则返回500
proxy.on('error', function(err, req, res){
res.writeHead(500, {
'content-type': 'text/plain'
});
res.end('server is error!');
});
//创建一个本地的server
var server = http.createServer(function (request, response) {
var pathname = url.parse(request.url).pathname;
var realPath = path.join("./", pathname);
var ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';
//判断若是是接口访问,则经过proxy转发
console.log(pathname);
console.log(API_URL);
if(pathname.indexOf(API_URL) > 0){
console.log(request.url.substring(4,request.url.length));
request.url=request.url.substring(4,request.url.length)
proxy.web(request, response);
return;
}
});
server.listen(PORT);
console.log("Server runing at port: " + PORT + ".");
复制代码
设置cors
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
add_header 'Access-Control-Max-Age' 64800;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
}
}
复制代码
或者是反向代理
server {
listen 80; #监听80端口
server_name localhost; # 当前服务的域名
location / {
proxy_pass http://localhost:81;
proxy_redirect default;
}
location /apis { #添加访问目录为/apis的代理配置
rewrite ^/apis/(.*)$ /$1 break;
proxy_pass http://localhost:82;
}
}
复制代码