# 同源策略
同源策略/SOP(Same origin policy)是一种约定,是浏览器最核心基本的安全功能,缺少了同源策略,很容易受到XSS、CSFR等攻击。所谓同源是指 "协议+域名+端口" 三者相同。
同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 Js对象无法获得
- AJAX 请求不能发送
注意
- 如果不配置'Access-Control-Allow-Headers':'content-type',也会产生跨域问题,在application/json时,而application/x-www-form-urlencoded不受影响
- post请求不管是form表单还是json格式,都可以去读取请求体和url中拼接的数据,两者如果同时存在也不会报错
- 注意跨域不同报错
# 跨域解决方案
通过jsonp跨域- document.domain + iframe跨域
- location.hash + iframe
- window.name + iframe跨域
- postMessage跨域
跨域资源共享(CORS)nginx代理跨域- nodejs中间件代理跨域
WebSocket协议跨域
# 通过jsonp跨域
通常为了减轻web服务器的负载,把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,可以通过动态创建script,再请求一个带参网址实现跨域通信。
- 原生实现:
if(req.url.includes("t1")){
var t1={
k1:1000
}
var tm =req.url.split("?")[1].split("=")[1];
console.log(tm)
t1=JSON.stringify(t1)
var t2=(`${tm}(${t1})`)
res.end(t2)
}
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://localhost:9000/t1?callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
console.log(res)
alert(JSON.stringify(res));
}
//script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
//handleCallback({"status": true, "user": "admin"})
- jquery ajax:
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
- vue.js:
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 = qs.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...');
jsonp缺点:只能实现get一种请求。
# document.domain + iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
- 父窗口:(http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
- 子窗口:(http://child.domain.com/b.html)
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
# location.hash + iframe跨域
实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。
- a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 向b.html传hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 开放给同域c.html的回调方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
- b.html:(http://www.domain2.com/b.html)
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
- c.html:(http://www.domain1.com/c.html)
<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
# window.name + iframe跨域
window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
- a.html:(http://www.domain1.com/a.html)
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
alert(data);
});
proxy.html:(http://www.domain1.com/proxy.... 中间代理页,与a.html同域,内容为空即可。
b.html:(http://www.domain2.com/b.html)
<script>
window.name = 'This is domain2 data!';
</script>
总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
# postMessage跨域
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow:其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。 targetOrigin:通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个 URI。
window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
从广义上讲,一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。接收消息的窗口可以根据需要自由处理此事件。传递给 window.postMessage() 的参数(比如 message)将通过消息事件对象暴露给接收消息的窗口。
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
页面和其打开的新窗口的数据传递多窗口之间消息传递- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
# iframe嵌入通信
父组件给子组件发消息 iframeID.contentWindow.postMessage() 子组件给父组件爱你发消息 window.parent.postMessage() || top.postMessage()
<h1>父页面</h1>
<!-- <iframe id="iframe" src="http://127.0.0.1:8848/word/demo1/index2.html"></iframe> -->
<iframe id="iframe" src="./index2.html"></iframe>
<script>
const iFrame = document.getElementById('iframe')
//需要等到iframe中的子页面加载完成后才发送消息,否则子页面接收不到消息
iFrame.onload = function(){
// <!-- iFrame.contentWindow获取到iframe的window对象 -->
iFrame.contentWindow.postMessage('父页面发送的消息','*');
}
window.addEventListener('message',e=>{
//<!-- 对消息来源origin做一下过滤,避免接收到非法域名的消息导致的xss攻击 -->
// if(e.origin===''){
console.warn(e)
console.log(e.origin) //子页面URL,这里是http://b.index.com
console.log(e.source) // 子页面window对象,全等于iframe.contentWindow
console.log(e.data) //子页面发送的消息
// }
},false)
</script>
<html>
<button id='child'>child</button>
</html>
<script>
// 有发送就有接收,与postMessage配套使用的就是message事件
window.addEventListener('message',e=>{
//<!-- 对消息来源origin做一下过滤,避免接收到非法域名的消息导致的xss攻击 -->
// if(e.origin==='http://a.index.com'){
console.log(e.origin) //父页面URL,这里是http://a.index.com
console.log(e.source) // 父页面window对象,全等于window.parent/window.top
console.log(e.data) //父页面发送的消息
// }
},false)
const child =document.getElementById('child')
child.onclick =function(){
window.parent.postMessage('子页面发送的消息','*')
}
</script>
# 演示全代码
<!-- A页面 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<button onclick="openHtml()">打开B页面</button>
<button onclick="send()">发送消息</button>
</body>
<script>
var win ;
function send(){
// 可以配置具体允许哪个端口去获取信息
// 如http://127.0.0.1:5501
win.postMessage("hide",'*');
}
function openHtml(){
// 可以跨域场景,也可以同源页面
// win = window.open("http://127.0.0.1:8848/word/demo2/index.html")
win = window.open("http://127.0.0.1:5501/index.html")
}
</script>
</html>
<!-- B页面 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
<script>
window.addEventListener('message',function(e){
console.log(e.data);
},false);
</script>
</html>
# 跨域资源共享(CORS)withCredentials(浏览器凭证信息)
- Access-Control-Allow-Origin: 指定哪些源(origin)可以访问资源。如果设置为*,表示接受任何源的请求。如果设置为特定的URL,则只有来自该URL的请求被允许。
- Access-Control-Allow-Methods: 列出允许跨源请求使用的HTTP方法(如GET、POST、PUT、DELETE等)。
- Access-Control-Allow-Headers: 指定在跨源请求中允许携带的自定义HTTP头部字段。如果设置为*,表示接受任何头部字段。
- Access-Control-Allow-Credentials: 指示是否允许发送Cookie。当设置为true时,表示请求可以携带认证信息(如Cookies和HTTP认证信息)。
- Access-Control-Allow-Max-Age: 指定预检请求(preflight request)的结果能够被缓存多久。预检请求是在发送实际请求之前,浏览器自动发送的一个OPTIONS请求,用于检查服务器是否允许跨源请求。
- Access-Control-Expose-Headers: 允许服务器指定哪些头部字段可以被前端JavaScript访问。默认情况下,CORS请求中,只有简单的响应头部(如Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)可以被前端访问。
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。
# 前端设置:
- 原生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
...
});
- vue框架
a. axios设置:
axios.defaults.withCredentials = true
# 服务端设置:
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...');
注意
服务端配置 Access-Control-Allow-Origin: '*' 或者前端的origin 这样接口能访问。但是不能获得cookie。还需要设置
Access-Control-Allow-Credentials:true 才能获得前端的cookie(注意:设为true时,Access-Control-Allow-Origin不能为'*',而是具体的某个域名)
# nginx代理跨域
- nginx配置解决iconfont跨域 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *;
}
- nginx反向代理接口跨域 跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
# Nodejs中间件代理跨域
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
1、 非vue框架的跨域(2次跨域) 利用node + express + http-proxy-middleware搭建一个proxy服务器。
- 前端代码示例:
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();
- 中间件服务器:
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
// 代理跨域目标接口
target: 'http://www.domain2.com:8080',
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookie
onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
res.header('Access-Control-Allow-Credentials', 'true');
},
// 修改响应信息中的cookie域名
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
- Nodejs后台同(六:nginx)
2、 vue框架的跨域(1次跨域) 利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于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
}
}
# 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.');
});
});
# 简单跨域和带预检跨域
CORS把HTTP请求分成两类,不同类别按不同的策略进行跨域资源共享协商。
- 简单跨域请求。如果一个请求被认为是“简单请求”,那么浏览器将不会发送预检请求。
1). 请求是GET、HEAD或者POST,并且当请求方法是POST时,Content-Type必须是application/x-www-form-urlencoded, multipart/form-data或着text/plain中的一个值。(json会发起预检)
2). 请求中没有自定义HTTP头部。
对于简单跨域,浏览器要做的就是在HTTP请求中添加Origin Header,将JS所在域填充进去,向其他域的服务器请求资源。服务器端收到请求后,根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header。浏览器收到响应后,查看Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给Js,否则忽略此次响应。
TIP
在CORS(跨来源资源共享)策略中,简单请求的条件包括:
- 请求方法只能是HEAD、GET或POST。
- 不能包含自定义的HTTP请求头(除了几个特定的字段,如Accept、Accept-Language、Content-Language、Content-Type等)。
- 如果包含Content-Type头,其值只能是application/x-www-form-urlencoded、multipart/form-data或text/plain。
如果一个GET请求不满足上述条件,比如包含了自定义的HTTP请求头,或者Content-Type被设置为了其他值(尽管对于GET请求来说,通常不会设置Content-Type头),那么这个GET请求就不再是简单请求,浏览器会在实际发送GET请求之前,先发送一个OPTIONS请求作为预检请求,以检查服务器是否允许该跨域GET请求。
因此,GET请求在特定条件下(即不满足简单请求的条件时)也会发送预检请求。这是CORS策略为了确保跨域请求的安全性而设计的一种机制。
- 带预检(Preflighted)的跨域请求:这一机制确实有助于增强跨域请求的安全性,因为它允许服务器在允许跨域请求之前对请求进行安全检查。
1). 除GET、HEAD和POST(json格式会预检)以外的HTTP方法。
2). 请求中出现自定义HTTP头部。
带预检(Preflighted)的跨域请求需要浏览器在发送真实HTTP请求之前先发送一个OPTIONS的预检请求,检测服务器端是否支持真实请求进行跨域资源访问,真实请求的信息在OPTIONS请求中通过Access-Control-Request-Method 和Access-Control-Request-Headers 描述,此外与简单跨域请求一样,浏览器也会添加Origin Header。服务器端接到预检请求后,根据资源权限配置,在响应头中放入Access-Control-Allow-Origin 、Access-Control-Allow-Methods和Access-Control-Allow-Headers ,分别表示允许 跨域资源请求的域、请求方法和请求头 。此外,服务器端还可以加入Access-Control-Max-Age ,允许浏览器在指定时间内,无需再发送预检请求进行协商,直接用本次协商结果即可。浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。
XMLHttpRequest支持通过withCredentials属性实现在跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)。浏览器将携带Cookie Header的请求发送到服务器端后,如果服务器没有响应Access-Control-Allow-Credentials,那么浏览器会忽略掉这次响应。
这里讨论的HTTP请求是指由Ajax XMLHttpRequest对象发起的,所有的CORS HTTP请求头都可由浏览器填充,无需在XMLHttpRequest对象中设置。以下是CORS协议规定的HTTP头,用来进行浏览器发起跨域资源请求时进行协商:
Origin。HTTP请求头,任何涉及CORS的请求都必需携带。
Access-Control-Request-Method。HTTP请求头,在带预检(Preflighted)的跨域请求中用来表示真实请求的方法。
Access-Control-Request-Headers。HTTP请求头,在带预检(Preflighted)的跨域请求中用来表示真实请求的自定义Header列表。
Access-Control-Allow-Origin。HTTP响应头,指定服务器端允许进行跨域资源访问的来源域。可以用通配符*表示允许任何域的JavaScript访问资源,但是在响应一个携带身份信息(Credential)的HTTP请求时,Access-Control-Allow-Origin必需指定具体的域,不能用通配符。
Access-Control-Allow-Methods。HTTP响应头,指定服务器允许进行跨域资源访问的请求方法列表,一般用在响应预检请求上。
Access-Control-Allow-Headers。HTTP响应头,指定服务器允许进行跨域资源访问的请求头列表,一般用在响应预检请求上。
Access-Control-Max-Age。HTTP响应头,用在响应预检请求上,表示本次预检响应的有效时间。在此时间内,浏览器都可以根据此次协商结果决定是否有必要直接发送真实请求,而无需再次发送预检请求。
Access-Control-Allow-Credentials。HTTP响应头,凡是浏览器请求中携带了身份信息,而响应头中没有返回Access-Control-Allow-Credentials: true的,浏览器都会忽略此次响应。
总结:只要是带自定义header的跨域请求,在发送真实请求前都会先发送OPTIONS请求,浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。所以复杂请求肯定会两次请求服务端
//前端请求
// index.html
axios.defaults.baseURL = 'http://localhost:3000';
axios.get("/users", {headers:{'X-Token':'jilei'}})
//nodejs
else if (method == "OPTIONS" && url == "/api/users") {
res.writeHead(200, {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Headers": "X-Token,Content-Type",
"Access-Control-Allow-Methods": "PUT"
});
res.end();
}
如果要携带cookie信息,则请求变为credential请求
// index.js
// 预检options中和/users接口中均需添加
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 设置cookie
res.setHeader('Set-Cookie', 'cookie1=va222;')
// index.html
// 观察cookie存在
console.log('cookie',req.headers.cookie)
// ajax服务
axios.defaults.withCredentials = true
//[如果原生的ajax xhr.open("get",url,true);xhr.withCredentials = true;]
# 原生ajax加token
var xhr = window.XMLHttpRequest ? new XMLHttpRequest()
: ActiveXObject("microsoft.XMLHttp")
xhr.open("get",url,true);
xhr.setRequestHeader("Authorization",1111)//需要加在open后面
xhr.send();
xhr.onreadystatechange= () =>{
if(xhr.readyState == 4){
if(xhr.status == 200){
console.log(xhr)
var data =JSON.parse(xhr.responseText) ;
let add=document.getElementsByClassName("add")[0];
let ul=document.createElement("ul");
for(let i in data){
let li=document.createElement("li")
console.log(data[i])
li.innerHTML=JSON.stringify(data[i]) ;
ul.appendChild(li)
}
add.appendChild(ul)
}
}
}
if(method==="GET"&url==="/json"){
const json =[{name:"zhangsan",age:18,job:"student"},{name:"lisi",age:28,job:"teacher"}]
res.writeHead(200, {
"Access-Control-Allow-Origin": "http://127.0.0.1:8848",
'Content-Type': 'application/json'
});
res.end(JSON.stringify(json));
}else if(method==="OPTIONS"&url==="/json"){
res.writeHead(200, {
"Access-Control-Allow-Origin": "http://127.0.0.1:8848",
"Access-Control-Allow-Headers": "Authorization,Content-Type",
"Access-Control-Allow-Methods": "PUT",
"Access-Control-Max-Age":3600
});
res.end();
}
# devserver配置跨域
借助webpack,可以解决在开发环境下的跨域问题,如vue.config.js中配置devserver
- 前端端口9000 后端端口3000
# .env.development 文件名
# 开发环境配置
# ENV = 'development'
# VUE_APP_BASE_API = /dev-api
const port = process.env.port || process.env.npm_config_port || 9000 // 端口
......
devServer:{
port: port,
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
'/dev-api': {
target: `http://localhost:3000`,//这是请求的真正目标地址(后端地址)
changeOrigin: true,
pathRewrite: {
'^/dev-api': '' // 思路是如果是开发环境,就给所有要代理的接口统一加上前缀,然后代理请求时再统一通过rewrite去掉
}
}
}
}
- 前台请求代码
//nodejs的地址:localhost://3000/1.json
fetch('http://localhost:9000/dev-api/1.json').then(res=>res.json()).then(res=>{
console.log(res)
}).catch(e=>{
console.log(e)
})
- 可配置动态地址来适配请求
devServer: {
host: '0.0.0.0',
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:8080`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
},
disableHostCheck: true
},
devserver配置跨域参考 (opens new window) postMessage (opens new window)