同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。同源指:协议、域名、端口号必须一致。html
同源策略控制了不一样源之间的交互,例如在使用XMLHttpRequest 或 标签时则会受到同源策略的约束。这些交互一般分为三类:前端
下面为容许跨域资源嵌入的示例,即一些不受同源策略影响的标签示例:vue
<script src="..."></script>
标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。<link rel="stylesheet" href="...">
标签嵌入CSS。因为CSS的松散的语法规则,CSS的跨域须要一个设置正确的Content-Type
消息头。不一样浏览器有不一样的限制: IE, Firefox, Chrome, Safari 和 Opera。<img>
嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG<video>
和 <audio>
嵌入多媒体资源。<object>
, <embed>
和 <applet>
的插件。@font-face
引入的字体。一些浏览器容许跨域字体( cross-origin fonts),一些须要同源字体(same-origin fonts)。<frame>
和<iframe>
载入的任何资源。站点可使用X-Frame-Options
消息头来阻止这种形式的跨域交互。利用script标签不受跨域限制而造成的一种方案。node
// index.html
function jsonp({url, param, cb}){
return new Promise((resolve, reject)=>{
let script = document.createElement('script')
window[cb] = function(data){
resolve(data);
document.body.removeChild(script)
}
params = {...params, cb}
let arrs = [];
for(let key in params){
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: {wd: 'haoxl'},
cb: 'show'
}).then(data=>{
console.log(data)
})
复制代码
//server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res){
let {wd,cb} = req.query
console.log(wd)
res.end(`${cb}('hello')`)
})
app.listen(3000)
复制代码
缺点:只支持get请求,不支持post、put、delete等;不安全,容易受[xss][18]攻击。webpack
跨域资源共享标准新增了一组 HTTP 首部字段,容许服务器声明哪些源站有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生反作用的 HTTP 请求方法(特别是 GET 之外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否容许该跨域请求。服务器确认容许以后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也能够通知客户端,是否须要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。nginx
<!--index.html-->
<body>
Nice to meet you
</body>
复制代码
<script>
let xhr = new XMLHttpRequest;
// 强制前端设置必须带上请示头cookie
document.cookie = 'name=haoxl'
xhr.withCredentials = true
xhr.open('GET','http://localhost:4000/getData', true);
// 设置自定义请求头
xhr.setRequestHeader('name','haoxl')
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status < 300 || xhr.status === 304){
console.log(xhr.response);
//获取后台传来的已改变name值的请示头
console.log(xhr.getResponseHeader('name'));
}
}
}
xhr.send()
</script>
复制代码
// server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000)
复制代码
// server2.js
let express = require('express');
let app = express();
let whiteList = ['http://localhost:3000']
app.use(function(req, res, next){
let origin = req.headers.origin;
if(whiteList.includes(origin)){
//设置那个源能够访问我,参数为 * 时,容许任何人访问,可是不能够和 cookie 凭证的响应头共同使用
res.setHeader('Access-Control-Allow-Origin', origin);
//容许带有name的请求头的能够访问
res.setHeader('Access-Control-Allow-Headers','name');
// 设置哪些请求方法可访问
res.setHeader('Access-Control-Allow-Methods', 'PUT');
// 设置带cookie请求时容许访问
res.setHeader('Access-Control-Allow-Credentials', true);
// 后台改了前端传的name请示头后,再传回去时浏览器会认为不安全,因此要设置下面这个
res.setHeader('Access-Control-Expose-Headers','name');
// 预检的存活时间-options请示
res.setHeader('Access-Control-Max-Age',3)
// 设置当预请求发来请求时,不作任何处理
if(req.method === 'OPTIONS'){
res.end();//OPTIONS请示不作任何处理
}
}
next();
});
app.put('/getData', function(req, res){
console.log(req.headers)
res.setHeader('name','hello');
res.end('hello world');
}
app.get('/getData', function(){
res.end('Nice to meet you')
})
app.use(express.static(__dirname));
app.listen(3000)
复制代码
对于两个不一样页面的脚本,只有当执行它们的页面位于具备相同的协议(一般为https),端口号(443为https的默认值),以及主机 (两个页面的模数
Document.domain
设置为相同的值) 时,这两个脚本才能相互通讯。window.postMessage()
方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。web
window.postMessage()
方法被调用时,会在全部页面脚本执行完毕以后(e.g., 在该方法以后设置的事件、以前设置的timeout 事件,etc.)向目标窗口派发一个MessageEvent
消息。express
语法:otherWindow.postMessage(message, targetOrigin, [transfer])
;npm
message属性有:json
案例:a.html 给b.html发消息
// a.html
<iframe src="http://localhost:4000/b.html" id="frame" onload="load()"></iframe>
<script>
function load(params){
let frame = document.getElementById('frame');
//获取iframe中的窗口,给iframe里嵌入的window发消息
frame.contentWindow.postMessage('hello','http://localhost:4000')
// 接收b.html回过来的消息
window.onmessage = function(e){
console.log(e.data)
}
}
</script>
复制代码
// b.html
<script>
//监听a.html发来的消息
window.onmessage = function(e){
console.log(e.data)
//给发送源回消息
e.source.postMessage('nice to meet you',e.origin)
}
</script>
复制代码
页面可能会因某些限制而改变他的源。脚本能够将
document.domain
的值设置为其当前域或其当前域的超级域。若是将其设置为其当前域的超级域,则较短的域将用于后续源检查。
a和b是同域的http://localhost:3000, c是独立的http://localhost:4000。 a经过iframe引入c,c把值放到window.name,再把它的src指向和a同域的b,而后在iframe所在的窗口中便可取出name的值。
// a.html
<iframe src="http://localhost:4000/c.html" onload="load()"></iframe>
<script>
let first = true
function load(){
if(first){
let iframe = document.getElementById('iframe');
// 将a中的iframe再指向b
iframe.src='http://localhost:3000/b.html';
first = false;
}else{
//在b中则可获得c给窗口发的消息
console.log(iframe.contentWindow.name);
}
}
</script>
复制代码
// c.html
<script>
window.name = 'nice to meet you'
</script>
复制代码
//server.js
let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(4000);
复制代码
window.location
只读属性,返回一个Location
对象,其中包含有关文档当前位置的信息。**window.location : 全部字母必须小写!**只要赋给 location 对象一个新值,文档就会使用新的 URL 加载,就好像使用修改后的 URL 调用了window.location.assign() 同样。须要注意的是,安全设置,如 CORS(跨域资源共享),可能会限制实际加载新页面。
案例:a、b同域,c单独一个域。a如今想访问c:a经过iframe给c传一个hash值,c收到hash值后再建立一个iframe把值经过hash传递给b,b将hash结果放到a的hash值中。
// a.html
<iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
<script>
//接收b传来的hash值
window.onhashchange = function(){
console.log(location.hash)
}
</script>
复制代码
// c.html
//接收a传来的hash值
console.log(location.hash)
//建立一个iframe,把回复的消息传给b
let iframe = document.createElement('iframe');
iframe.src='http://localhost:3000/b.html#idontloveyou';
document.body.appendChild(iframe);
复制代码
//b.html
<script>
//a.html引的c, c又引的b,因此b.parent.parent便是a
window.parent.parent.location.hash = location.hash
</script>
复制代码
window.domain
:获取/设置当前文档的原始域部分。 案例:解决一级域与二级域之间通讯。 模拟时须要建立两个不一样域的域名用来测试,打开C:\Windows\System32\drivers\etc 该路径下找到 hosts 文件,在最下面建立一个一级域名和一个二级域名。改成:
127.0.0.1 www.haoxl.com
127.0.0.1 test.haoxl.com
复制代码
预设a.html = www.haoxl.com, b.html = test.haoxl.com
// a.html
<iframe src="http://test.haoxl.com" onload="load()"></iframe>
<script>
function load(){
//告诉页面它的主域名,要与b.html的主域名相同,这样才可在a中访问b的值
document.domain = 'haoxl.com'
function load(){
// 在a页面引入b页面后,直接经过下面方式获取b中的值
console.log(frame.contentWindow.a);
}
}
</script>
复制代码
// b.html
document.domain = 'haoxl.com'
var a = 'hello world'
复制代码
WebSocket对象提供了用于建立和管理 WebSocket 链接,以及能够经过该链接发送和接收数据的 API。它是基于TCP的全双工通讯,即服务端和客户端能够双向进行通信,而且容许跨域通信。基本协议有
ws://
(非加密)和wss://
(加密)
//socket.html
let socket = new WebSocket('ws://localhost:3000');
// 给服务器发消息
socket.onopen = function() {
socket.send('hello server')
}
// 接收服务器回复的消息
socket.onmessage = function(e) {
console.log(e.data)
}
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//npm i ws
// 设置服务器域为3000端口
let wss = new WebSocket.Server({port:3000});
//链接
wss.on('connection', function(ws){
// 接收客户端传来的消息
ws.on('message', function(data){
console.log(data);
// 服务端回复消息
ws.send('hello client')
})
})
复制代码
Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个
IMAP/POP3/SMTP
服务器。
案例:在nginx根目录下建立json/a.json,里面随便放些内容
// client.html
let xhr = new XMLHttpRequest;
xhr.open('get', 'http://localhost/a.json', true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304 ){
console.log(xhr.response);
}
}
}
复制代码
// server.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
复制代码
// nginx.conf
location / {// 表明输入/时默认去打开root目录下的html文件夹
root html;
index index.html index.htm;
}
location ~.*\.json{//表明输入任意.json后去打开json文件夹
root json;
add_header "Access-Control-Allow-Origin" "*";
}
复制代码
NodeJS 中间件 http-proxy-middleware 实现跨域代理,原理大体与 nginx 相同,都是经过启一个代理服务器,实现数据的转发,也能够经过设置 cookieDomainRewrite 参数修改响应头中 cookie 中的域名,实现当前域的 cookie 写入,方便接口登陆认证。
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.proxy2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些 https 服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 能够为 false,表示不修改
}],
noInfo: true
}
}
复制代码
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>nginx跨域</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写 cookie
xhr.withCredentials = true;
// 访问 http-proxy-middleware 代理服务器
xhr.open('get', 'http://www.proxy1.com:3000/login?user=admin', true);
xhr.send();
</script>
</body>
</html>
复制代码
// 中间代理服务器
var express = require("express");
var proxy = require("http-proxy-middleware");
var app = express();
app.use(
"/",
proxy({
// 代理跨域目标接口
target: "http://www.proxy2.com:8080",
changeOrigin: true,
// 修改响应头信息,实现跨域并容许带 cookie
onProxyRes: function(proxyRes, req, res) {
res.header("Access-Control-Allow-Origin", "http://www.proxy1.com");
res.header("Access-Control-Allow-Credentials", "true");
},
// 修改响应信息中的 cookie 域名
cookieDomainRewrite: "www.proxy1.com" // 能够为 false,表示不修改
})
);
app.listen(3000);
复制代码
// 服务器
var http = require("http");
var server = http.createServer();
var qs = require("querystring");
server.on("request", function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写 cookie
res.writeHead(200, {
"Set-Cookie": "l=a123456;Path=/;Domain=www.proxy2.com;HttpOnly" // HttpOnly:脚本没法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen("8080");
复制代码