有一点必需要注意:跨域并非请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之因此会跨域,是由于受到了同源策略的限制,同源策略要求源相同才能正常进行通讯,即协议、域名、端口号都彻底一致。javascript
那么是出于什么安全考虑才会引入这种机制呢?html
其实主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登陆态发起恶意请求。前端
也就是说,没有同源策略的状况下,A 网站能够被任意其余来源的 Ajax 访问到内容。若是你当前 A 网站还存在登陆态,那么对方就能够经过 Ajax 得到你的任何信息。固然跨域并不能彻底阻止 CSRF。html5
而后咱们来考虑一个问题,请求跨域了,那么请求到底发出去没有? 请求必然是发出去了,可是浏览器拦截了响应。你可能会疑问明明经过表单的方式能够发起跨域请求,为何 Ajax 就不会。由于归根结底,跨域是为了阻止用户读取到另外一个域名下的内容,Ajax 能够获取响应,浏览器认为这不安全,因此拦截了响应。可是表单并不会获取新的内容,因此能够发起跨域请求。同时也说明了跨域并不能彻底阻止 CSRF,由于请求毕竟是发出去了。java
经常使用的跨域方法有JSONP
,CORS
,postmessage
等,web
JSONP
是JSON with padding
的简写,是应用JSON
的一种新方法,JSONP
看起来和JSON
差很少,只不过是被包含在函数调用的JSON
,像这样:callback({name: 'nany'})
。express
经过<script>
标签引入一个js
文件,这个js
文件载成功后会执行咱们在url
参数中指定的函数,而且会把咱们须要的json
数据做为参数传入,jsonp
是须要服务器端配合的。json
前端:后端
<script>
function getPrice(data){
console.log(data);
}
</script>
<script
type="text/javascript"
src="http://sdffw.b2b.com/getSupplyPrice?callback=getPrice&bcid=47296567">
</script>
复制代码
后端:跨域
const url = require('url');
require('http').createServer((req, res) => {
const data = {};
const callback = url.parse(req.url, true).query.callback ;
res.writeHead(200)
res.end(`${callback}(${JSON.stringify(data)})`)
*// 服务器收到请求后,解析参数,*
*// 将callback(data)以字符串的形式返还数据,前端页面会将callback(data)做为js执行*
*// 调用jsonpCallback(data)函数。*
}).listen(3000, '127.0.0.1');
复制代码
callback
是先后台约定的查询参数,服务器端返回一个能执行的js
文件,这个js
文件是调用callback
对应的参数值即getPrice
执行,而且返回对应的数据,咱们能够在getPrice
方法里面来处理返回的数据,最终返回的结果以下:
getPrice({
"data":{"priceType":"0","unit":"斤"},
"message":"价格获取成功!!!",
"state":"1"
})
复制代码
在开发中可能会遇到多个JSONP
请求的回调函数名是相同的,这时候就须要本身封装一个JSONP
,如下是简单实现:
function jsonp(url, jsonpCallback, success) {
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript'
window[jsonpCallback] = function(data) {
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
console.log(value)
})
复制代码
JSONP
跨域不像下面的CORS
跨域那样受同源政策的影响,并且兼容性也比较好,但JSONP
跨域也有其缺点,主要表如今:
GET
请求而不支持POST
等其它类行的 HTTP
请求。HTTP
请求这种状况,不能解决不一样域的两个页面或 iframe
之间进行数据通讯的问题。JSONP
从其余域中加载代码执行,若是该域不安全而且夹带一些恶意代码,会存在安全隐患 要肯定JSONP
请求是否失败并不容易CORS
须要浏览器和后端同时支持。IE 8
和 9
须要经过 XDomainRequest
来实现。
浏览器会自动进行 CORS
通讯,实现 CORS
通讯的关键是后端。只要后端实现了 CORS
,就实现了跨域。服务端设置 Access-Control-Allow-Origin
就能够开启 CORS
。 该属性表示哪些域名能够访问资源,若是设置通配符则表示全部网站均可以访问资源。
虽然设置 CORS
和前端没什么关系,可是经过这种方式解决跨域问题的话,会在发送请求时出现两种状况,分别为简单请求和复杂请求。
以 Ajax
为例,当知足如下条件时,会触发简单请求
使用下列方法之一:
GET
HEAD
POST
Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可使用 XMLHttpRequest.upload 属性访问。
复制代码
那么很显然,不符合以上条件的请求就确定是复杂请求了。
对于复杂请求来讲,首先会发起一个预检请求,该请求是option
方法的,经过该请求来知道服务端是否容许跨域请求。对于预检请求来讲,若是你使用过Node
来设置CORS
的话,可能会遇到过这么一个坑。
如下以 express 框架举例:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials'
)
next()
})
复制代码
该请求会验证你的Authorization
字段,没有的话就会报错。
当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果也永远是报错的。由于预检请求也会进入回调中,也会触发next()
方法,由于预检请求并不包含Authorization
字段,因此服务端会报错。
想解决这个问题很简单,只须要在回调中过滤option
方法便可。
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
复制代码
POST
请求方式,CORS
是一种新型的跨域问题的解决方案,存在兼容问题,仅支持IE 10
以上这种方式只能用于二级域名相同的状况下,好比a.test.com
和 b.test.com
适用于该方式。
只须要给页面添加 document.domain = 'test.com'
表示二级域名都相同就能够实现跨域。 修改document.domain
的方法只适用于不一样子域的框架间的交互。
window.postMessage(message,targetOrigin)
方法是html5
新引进的特性,可使用它来向其它的window
对象发送消息,不管这个window
对象是属于同源或不一样源,目前IE8+
、FireFox
、Chrome
、Opera
等浏览器都已经支持window.postMessage
方法。
调用postMessage
方法的window
对象是指要接收消息的那一个window
对象,该方法的第一个参数message
为要发送的消息,类型只能为字符串;第二个参数targetOrigin
用来限定接收消息的那个window
对象所在的域,若是不想限定域,可使用通配符*
。
须要接收消息的window
对象,但是经过监听自身的message
事件来获取传过来的消息,消息内容储存在该事件对象的data
属性中。
// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com') {
console.log('验证经过')
}
})
复制代码
window
的name
属性有个特征:
在一个窗口(window)
的生命周期内,窗口载入的全部的页面都是共享一个window.name
,每一个页面对window.name
都有读写的权限,window.name
是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。
web sockets原理:
在JS建立了web socket
以后,会有一个HTTP
请求发送到浏览器以发起链接。取得服务器响应后,创建的链接会使用HTTP
升级从HTTP
协议交换为web sockt
协议。
CSRF(Cross site request forgery)
,即跨站请求伪造。咱们知道XSS是跨站脚本攻击,就是在用户的浏览器中执行攻击者的脚本,来得到其cookie
等信息。而CSRF
确是借用用户的身份,向web server
发送请求,由于该请求不是用户本意,因此称为“跨站请求伪造”。
CSRF
通常的攻击过程是,攻击者向目标网站注入一个恶意的CSRF
攻击URL
地址(跨站url)
,当(登陆)用户访问某特定网页时,若是用户点击了该URL
,那么攻击就触发了,咱们能够在该恶意的url
对应的网页中,利用 来向目标网站发生一个get
请求,该请求会携带cookie
信息,因此也就借用了用户的身份,也就是伪造了一个请求,该请求能够是目标网站中的用户有权限访问的任意请求。也可使用javascript
构造一个提交表单的post
请求。好比构造一个转帐的post
请求。
因此CSRF
的攻击分为了两步,首先要注入恶意URL
地址,而后在该地址中写入攻击代码,利用 等标签或者使用Javascript
脚本
由于伪造的请求通常是从第三方网站发起的,因此第一个防护方法就是判断referer
头,若是不是来自本网站的请求,就断定为CSRF
攻击。可是该方法只能防护跨站的CSRF
攻击,不能防护同站的CSRF
攻击(虽然同站的CSRF
更难)。
每个重要的post
提交页面,使用一个验证码,由于第三方网站是没法得到验证码的。还有使用手机验证码,好比转帐是使用的手机验证码。
每个网页包含一个web server
产生的token
,提交时,也将该token
提交到服务器,服务器进行判断,若是token
不对,就断定位CSRF
攻击。
将敏感操做get
改成post
,而后在表单中使用token
. 尽可能使用post
也有利于防护CSRF
攻击。
注明一下,整篇文章为学习笔记,多方参考总结,若有版权冲突,请留言,收到消息后会标明版权出处。