同源策略

协议 域名 端口 都一样就是同源

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要机制

跨源网络访问

同源策略控制了不同源之间的交互,例如在使用XMLHttpRequest或标签时则会收到同源策略的约束。这些交互通常分为三类:

1.通常允许跨域写操作(Cross-origin writes)。例如链接(Links),重定向以及表单提交.特定给少数的HTTP请求需要添加preflight

2.通常允许跨域资源嵌入(Cross-origin embedding)

以下是可能嵌入跨域的资源的一些示例

1 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到
2 标签嵌入CSS。由于CSS的松散的语法规定,CSS的跨域需要一个设置正确的Content-Type消息头。不同的浏览器有不同限制
3 嵌入图片,支持的图片格式包括PNG,JPEG,GIF,BMP,SVG。。
4 video和audio嵌入多媒体资源
5 的插件
6@font-face引入的字体,一些浏览器允许跨域字体(Cross-origin fonts),一些需要同源字体(same-origin fonts)
7和载入的任何资源

3.通常不允许跨域读操作(Cross-origin reads)。但常可以通过内嵌资源来巧妙的进行读取访问。丽日可以读取嵌入图片的高度和宽度,调用内嵌脚本的方法

如何组织跨域访问

  1. 组织跨域写操作,只要检测请求中的一个不可测的标记(CSRF token)即可,这个标记被称为Cross-Site Request Forgery(CSRF)标记。必须使用这个标记来阻止页面的跨站读操作。
  2. 阻止资源的跨站读取,需要保证该资源是不可嵌入的。阻止嵌入行为是必须的,因为嵌入资源通常向其暴露信息。
  3. 阻止跨站嵌入,需要确保你的资源不能是以上列出的可嵌入资源格式。多数情况下浏览器都不会遵守 Conten-Type 消息头。例如,如果您在HTML文档中指定

限制范围(如果非同源,共有三种行为受到限制)

cookie LocalStorage不支持跨域 (无法读取)

DOM元素也有同源策略 (无法获得)

AJAX也不支持跨域 (AJAX的请求无法发送)

实现跨域

1 JSONP

只能发送get请求,不支持post put delete
它的基本思想是,网页通过添加一个script元素,向服务器请求JSON数据,这种做法不受同源策略限制
服务器收到请求后,将数据放在一个指定名字的回调函数里传回来.
不安全,XSS攻击 不采用使用

   function jsonp({
url,
params,
cb
}) {
return new Promise((resolce, reject) => {
let script = document.createElement("script")
window[cb] = function(data) {
resolce(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: "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su",
params: {
wd: 'w'
},
cb: 'show'
}).then(data => {
console.log(data)
})

2 空iframe加form

const requestPost = ({url, data}) => {
// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
console.log('post success')
})

form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()

// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})

3 CORS (常用)

服务端解决的方案,不兼容

与前端没有关系,在服务器上设置,主要是设置http的头部

​跨域资源共享CORS详解​

浏览器将CORS请求分为两类:简单请求和非简单请求

只要同时满足以下两个条件就是简单请求

(1)请求方式是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头部信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限三个值application/x-www-form-urlencoded,multipart/form,text/plain

凡是不同时满足上面的两个条件的都属于非简单请求

1 简单请求

后端

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
static async cors (ctx) {
const query = ctx.request.query
// *时cookie不会在http请求中带上
ctx.set('Access-Control-Allow-Origin', '*')
ctx.cookies.set('tokenId', '2')
ctx.body = successBody({msg: query.msg}, 'success')
}
}
module.exports = CrossDomain

前端

前端什么也不用干,就是正常的发送请求,如果需要带cookie的话,前后端都要设置一下。

axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

2 非简单请求

非简单请求会发出一次预检测请求,返回码是204,预检测才会发出真正请求,这才返回200.这里通过前端发送请求的时候增加一个额外的headers来触发非简单请求

setHeader('Access-Control-Allow-Origin', origin); //允许哪个源访问我
setHeader('Access-Control-Allow-Headers', 'name'); //允许携带哪个头访问我
setHeader('Access-Control-Allow-Methods', 'PUT'); //允许哪个方法访问我
setHeader('Access-Control-Allow-Credentials', true); //允许携带cookie
setHeader('Access-Control-Max-Age', 6000); //预检存活时间
setHeader('Access-Control-Expose-Headers', 'name'); //允许给前端返回的头

4 postMessage 页面之间相互通信

H5引入一个全新的API:跨文档通信API

这个API为window对象新增一个window.postMessage方法,允许跨窗口通信,无论这两个窗口是否同源

举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以

   let opp=window.open('http://aaa.com','title');
opp.postMessage('Hello World','http://aaa.com');

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin)即"“协议+域名+端口”.

也可设为*,表示不限制域名,向所有的窗口发送

子窗口向父窗口发送消息的写法类似.

window.opener.postMessage('Nice to see you','http://bbb.com');

父窗口和子窗口都可以通过message事件,监听对方的消息.

window.addEventListener('message',function(e){
console.log(e.data)
},false)

message事件的事件对象event,提供以下三个属性.

1.event.source:发送消息的窗口

2.event.origin:消息发向的网址

3.event.data:消息的内容

发送消息方:​​http://localhost:9099/#/crossDomain​

<template>
<div>
<button @click="postMessage">给http://crossDomain.com:9099发消息</button>
<iframe name="crossDomainIframe" src="http://crossdomain.com:9099"></iframe>
</div>
</template>

<script>
export default {
mounted () {
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://crossdomain.com:9099') {
// 来自http://crossdomain.com:9099的结果回复
console.log(e.data)
}
})
},
methods: {
// 向http://crossdomain.com:9099发消息
postMessage () {
const iframe = window.frames['crossDomainIframe']
iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
}
}
}
</script>

接收信息方 ​​http://crossdomain.com:9099​

<template>
<div>
我是http://crossdomain.com:9099
</div>
</template>

<script>
export default {
mounted () {
window.addEventListener('message', (e) => {
// 这里一定要对来源做校验
if (e.origin === 'http://localhost:9099') {
// http://localhost:9099发来的信息
console.log(e.data)
// e.source可以是回信的对象,其实就是http://localhost:9099窗口对象(window)的引用
// e.origin可以作为targetOrigin
e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟,这就是你想知道的结果:${document.getElementById('app') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin);
}
})
}
}
</script>

跨域详解_服务器

5 document.domain

这个方法可以解决Cookie的跨域问题

Cookie是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享Cookie。

举例子:A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页可以共享Cookie

document.domain='example'

现在,A网页通过脚本设置一个Cookie

document.cookie='test1=hello';

B网页就可以读到这个Cookie

let allCookie=document.cookie

注意,这种方法直适用于Cookie和iframe窗口。

另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,​​比如example.com​

Set-Cookie:key=value;domain=.example.com;path=/

这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie

6 ​​window.name​

这个属性的特点,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。

父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入 window.name属性.

window.name=data

接着,子窗口跳回一个与主窗口同域的网址

location='http://parent.url.com/xxx.html';

然后,主窗口就可以读取子窗口的 window.name了

let data=document.getElementById('myFrame').contentWindow.name;

这种方法的优点是,容量大,可以放置很长的字符串,缺点是必须监听子窗口window.name属性的变化,影响网页性能

原理

a和b是同域的,​​http://localhost:4001​​ c是独立的 http://localhost:4000
a获取c的数据
a先引用c c把值放到window.name,把a引用的地址改到b

7 WebSocket

WebSocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案

WebSocket和HTTP都是应用层协议,都是基于TCP协议

但是WebSocket是一种双向通信协议,在建立连接之后,WebSocket的server与client都能主动向对方发送或接收数据

同时,WebSocket在建立连接时需要借助HTTP协议,连接建立好了之后client与server之间的双向通信就与HTTP无关

原生WebSocket API使用起来不方便(主要是兼容性问题,​​我们使用Socket.io​​,它很好封装了WebSocket接口,提供了更灵活,简单的接口。

//前端代码:
<div>
user input:<input type="text"></div>
<script src="./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>

//Nodejs socket后台:
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.');
});
});

8 location.hash

路径后面的hash值可以用来通信
目的a想访问c
a给c传一个hash值 c收到hash值后 c把hash值传递给b
b将结果放到a的hash值中

9 Nginx 代理

后端的活和CORS一样