这里主要记录在日常中对知识的学习,通过结合笔记与自身理解的方式尝试写下总结
文章对细节可能不会一一介绍解释,内容仅作参考
复制代码
一、同源策略
同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
复制代码
对于 http://a.cat.com/src/one.html 进行同源检测:
http://a.cat.com/src2/one.html 成功
http://a.cat.com/src/two/xx.html 成功
https://a.cat.com/src/one.html 失败(协议不同)
http://b.cat.com/src/one.html 失败(子域名不同)
http://a.cat.com:9900/src/one.html 失败(端口不同)
复制代码
只要协议,域名,端口有任何一个的不同,就被当作是跨域
二、跨域的产生
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: <link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
复制代码
我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景
三、解决跨域方式
1. JSONP
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许
基于此原理,利用script元素的这个开放策略,网页可以通过动态创建script,再请求一个带参网址实现跨域通信来得到数据
前端代码实现:
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,提供参数返回后调用的函数
script.src = 'http://www.cat.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数 须是全局函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
复制代码
服务端返回代码如下(返回时即执行全局函数):
//这里的handleCallback是服务端接收到回调函数名参数后构造生成的
handleCallback({"status": true, "user": "admin"})
复制代码
有几点需要注意:
1.) JSONP仅支持GET请求
2.) 获取到的是JS执行代码 (面试时被问过
3.) 整体实现思路 (在上面
2. 跨域资源共享 CORS
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信
CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成 前端代码实现:
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);
}
}
复制代码
服务端代码实现:
//允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com")
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true")
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With")
复制代码
3. postMessage
可以先看一下概念和基础 - MDN:postMessage
postMessage是html5引入的API,允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。可以更方便、有效、安全的解决以下问题:
a.) 页面和其打开的新窗口的数据传递 (window.open
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递
用法:
// 发送数据
otherWindow.postMessage(message, targetOrigin, [transfer])
// otherWindow: 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
// message: 将要发送到otherWindow的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化
// targetOrigin: 指定哪些源的窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送。最好不要直接填"*"来代替一个明确的地址
// transfer: 是一串和message同时传递的Transferable对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权
复制代码
// 接收数据
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
// var origin = event.origin || event.originalEvent.origin;
var origin = event.origin
if (origin !== "http://xxx.xxx:9090") {
return;
}
// event.data 从其他窗口传过来的数据
console.log(event)
// do something
}
复制代码
举个栗子: 情景 - a页面中嵌套b页面,进行消息传递
// a页面 http://www.catone.com/a.html
<iframe id="iframe" src="http://www.cattwo.com/b.html"></iframe>
<script>
let iframe = document.getElementById('iframe')
iframe.onload = function() {
let data = {
say: 'hello'
}
// 向cattwo传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.cattwo.com');
}
// 接收cattwo返回的数据
window.addEventListener('message', function(e) {
console.log(`data from cattwo ---> ${e.data}`)
}, false)
</script>
复制代码
// b页面 http://www.cattwo.com/b.html
<script>
// 接收catone的数据
window.addEventListener('message', function(e) {
console.log(`data from catone ---> ${e.data}`)
let data = JSON.parse(e.data)
if (data) {
console.log(data)
data.good = 'bye'
// 处理后再发回catone
window.parent.postMessage(JSON.stringify(data), 'http://www.catone.com')
}
}, false)
</script>
复制代码
4. nginx
nginx是一个高性能的HTTP的反向代理服务器,也是一个通用的TCP/UDP代理服务器
他的作用有:解决跨域、请求过滤、配置gzip、负载均衡、作为静态资源服务器等
nginx的使用在这里就不详细讲了,建议可以去查阅一下相关的知识,在平时开发中还是挺有用的
实现思路:通过nginx配置开启一个代理服务器做跳板机,反向代理访问cattwo服务,并且顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录
#nginx
// 监听 www.catone.com:9090
server {
listen 9090;
server_name www.catone.com;
location / {
proxy_pass http://www.cattwo.com:8080; #反向代理
proxy_cookie_domain www.cattwo.com www.catone.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.cattwo.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
复制代码
前端代码示例:
let xhr = new XMLHttpRequest()
// 浏览器是否读写cookie
xhr.withCredentials = true
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.catone.com:9090?hello=world', true)
xhr.send()
复制代码
后台node代码示例:
var http = require('http')
var server = http.createServer()
var qs = require('querystring')
server.on('request', function(req, res) {
var params = qs.parse(req.url)
// 写入cookie
res.writeHead(200, {
'Set-Cookie': 'cat=cat;Path=/;Domain=www.domain2.com;HttpOnly'
})
res.write(JSON.stringify(params))
res.end()
})
server.listen('8080')
复制代码
5. webSocket
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现
这里使用Socket.io示例一下,如果有时间的话,原生还是可以看一下的
前端代码:
<input type="text" id="input">
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.cattwo.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.getElementsById('input').onblur = function() {
socket.send(this.value)
}
</script>
复制代码
后台node代码:
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');
// 监听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.');
});
});
复制代码
未完待续