注:本文完整示例地址
先来讲一个概念就是同源,同源指的是协议,端口,域名所有相同。javascript
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,若是缺乏了同源策略,则浏览器的正常功能可能都会受到影响。能够说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。css
同源策略是处于对用户安全的考虑,若是非同源就会受到如下限制:html
cookie不能读取前端
dom没法得到java
ajax请求不能发送git
可是事实是常常须要借助非同源来提供数据,因此就须要进行跨域请求。github
JSONP
是指JSON Padding
,JSONP
是一种非官方跨域数据交换协议,因为script
的src
属性能够跨域请求,因此JSONP
利用的就是浏览器的这个“漏洞”,须要通讯时,动态的插入一个script
标签。请求的地址通常带有一个callback
参数,假设须要请求的地址为http://localhost:666?callback=show
,服务端返回的代码通常是show(数据)
的JSON
数据,而show
函数偏偏是前台须要用这个数据的函数。JSONP
很是的简单易用,自动补全API利用的就是JSONP
,下面来看一个例子:web
// 前端请求代码 function jsonp (callback) { var script = document.createElement("script"), url = `http://localhost:666?callback=${callback}`; script.setAttribute("src", url); document.querySelector("head").appendChild(script); } function show (data) { console.log(`学生姓名为:${data.name},年龄为:${data.age},性别为${data.sex}`); } jsonp("show"); // 后端响应代码 const student = { name: "zp1996", age: 20, sex: "male" }; var callback = url.parse(req.url, true).query.callback; res.writeHead(200, { "Content-Type": "application/json;charset=utf-8" }); res.end(`${callback}(${JSON.stringify(student)})`);
JSONP
虽然说简单易用,可是有一个很大问题,那就是JSONP
只能进行get
请求ajax
CORS(跨域资源共享)是由W3C制定的跨站资源分享标准,可让AJAX
实现跨域访问。想要了解跨域的话,首先须要了解下简单请求:json
请求方式为GET
或者POST
倘若请求是POST
的话,Content-Type
必须为下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
不含有自定义头(相似于segmentfault自定义的头X-Hit
)
对于简单请求的跨域只须要进行一次http
请求:
function ajaxPost (url, obj, header) { return new Promise((resolve, reject) => { var xhr = new XMLHttpRequest(), str = '', keys = Object.keys(obj); for (var i = 0, len = keys.length; i < len; i++) { str += `${keys[i]}=${obj[keys[i]]}&`; } str = str.substring(0, str.length - 1); xhr.open('post', url); xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); if (header instanceof Object) { for (var k in header) xhr.setRequestHeader(k, header[k]); } xhr.send(str); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { resolve(xhr.responseText); } else { reject(); } } } }); } ajaxPost("http://localhost:666?page=cors", { name: "zp1996", age: 20, sex: "male" }) .then((text) => { console.log(text); }, () => { console.log("请求失败"); }); // 后端处理 var postData = ""; // 注释下,下面示例后台代码补充在此处 req.on("data", (data) => { postData += data; }); req.on("end", () => { postData = querystring.parse(postData); res.writeHead(200, { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json;charset=utf-8" }); if (postData.name === student.name && Number(postData.age) === student.age && postData.sex === student.sex ) { res.end(`yeah!${postData.name} is a good boy~`); } else { res.end("No!a bad boy~"); } });
打开控制台观察能够发现,Network
只是发出了一次请求,可是对于非简单请求来讲,须要两次http
请求,在真正的请求以前须要进行一次预请求,下图是进行一次预请求的请求/响应:
观察响应头,能够发现须要多出了两个响应头:
Access-Control-Allow-Headers
,用来指明在实际的请求中,可使用那些自定义的http
请求头。
Access-Control-Max-Age
,用来指定这次预请求的结果的有效期,在有效期内则不会发出预请求,有点像缓存的感受。
固然还有诸如好多这样的响应头,请你们自行搜索了解,这里就再也不过多介绍,下面来看下对于非简单请求跨域的代码处理:
// 前端请求代码 ajaxPost("http://localhost:666?page=cors", { name: "zp1996", age: 20, sex: "male" }, { "X-author": "zp1996" }) .then((text) => { console.log(text); }, () => { console.log("请求失败"); }); // 后端处理,补充在简单请求代码注释处 if (req.method === "OPTIONS") { res.writeHead(200, { "Access-Control-Max-Age": 3000, "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "X-author", "Content-Type": "application/json;charset=utf-8" }); res.end(); return void 0; }
既然能够利用script
的“漏洞”来进行JSONP
跨域,那么是否是也能够利用css
样式写能够进行跨域请求来进行跨域呢?答案确定是yes,利用css
还有一个好处那就是,当被注入攻击脚本时,css
尽管被注入,也不会引发什么大的安全问题,顶多也就是把页面的样式给改变,而js
被注入的话,cookie
就有可能被盗取等一系列安全问题出现。大牛已经将其作的很是完善,你们能够去star王集鹄(zswang)CSST,这里我就把我所理解给你们简单的分享下:
// 前端代码 const id = "csst", ele = document.querySelector(`#${id}`), head = document.querySelector("head"); function getStyle (ele, prop) { return getComputedStyle(ele, "").getPropertyValue(prop); } function loadCss (url) { return new Promise((resolve) => { const link = document.createElement("link"); link.setAttribute("rel", "stylesheet"); link.setAttribute("type", "text/css"); link.setAttribute("href", url); ele.addEventListener("webkitAnimationStart", function () { resolve(getStyle(ele, "content")); }); head.appendChild(link); }); } loadCss(`http://localhost:666?page=data.css&id=${id}`).then((data) => { console.log(data); }); // 后端代码 function cssData (id) { return ` @keyframes a{ from{ } to{ color: red; } } #${id} { content: "这种是很好,可是只能传输文本啊"; animation: a 2s; } `; } res.writeHead(200, { "Content-Type": "text/css" }); res.end(cssData(query.id));
经过代码能够看出这种实现方式是靠元素的content
来拿接收到的数据,因此传输的只能是文本。至于为何要返回动画?是由于不利用动画,没法来对css
脚本加载进行监测,也就没法进行回调(因为谷歌/火狐不支持link
的onload
和onreadychange
,因此利用animationstart
事件)。
window.postMessage 是一个安全的跨源通讯的方法。通常状况下,当且仅当执行脚本的页面使用相同的协议(一般都是 http)、相同的端口(http默认使用80端口)和相同的 host(两个页面的 document.domain 的值相同)时,才容许不一样页面上的脚本互相访问。 window.postMessage 提供了一个可控的机制来安全地绕过这一限制,当其在正确使用的状况下。
window.postMessage
解决的不是浏览器与服务器之间的交互,解决的是浏览器不一样的窗口之间的通讯问题,能够作的就是同步两个网页,固然这两个网页应该是属于同一个基础域名。
// 发送端代码 var domain = "http://localhost", index = 1, target = window.open(`${domain}/postmessage-target.html`); function send () { setInterval(() => { target.postMessage(`第${index++}次数据发送`, domain); }, 1000); } send(); // 接受端代码 <div id="test">没有数据过来啊<div> <script type="text/javascript"> var test = document.querySelector("#test"); window.addEventListener("message", e => { if (e.origin !== "http://localhost") { return void 0; } test.innerText = e.data; }); </script>
上述代码实现了向一个页面向另外一个发送数据,可是这么写每每有着一些“危险”,须要知道的是,postMessage
是向document
对象中,网络链接有时会很慢,可能会出现些问题,因此最好的方式是接受页面已经开始加载了,这时发送一个消息给发送端,发送端在开始向接收端发送数据。改进下:
// 发送端添加代码 window.addEventListener("message", (e) => { if (e.data === "ok") send(); else console.log(e.data); }); // 接受端的head里面加上script标签 <script type="text/javascript"> opener.postMessage("ok", opener.domain); </script>
window.name 的美妙之处:name 值在不一样的页面(甚至不一样域名)加载后依旧存在,而且能够支持很是长的 name 值(2MB)
这个方式我基本上没有用过,因此没有过多的发言权,你们想了解这个技术的话,能够经过怿飞(圆心):使用 window.name 解决跨域问题,圆心大神解释的很是透彻。
将子域和主域的document.domain
设为同一个主域
前提条件:
这两个域名必须属于同一个基础域名
并且所用的协议,端口都要一致,不然没法利用document.domain
进行跨域