跨域

一、什么是跨域?

当协议、子域名、主域名、端口号中任意一个不相同时,都算做不一样域,不一样域之间相互请求资源,就算做“跨域”。由于JavaScript出于安全考虑,有同源策略。

有一点必需要注意:跨域并非请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之因此会跨域,是由于受到了同源策略的限制,同源策略要求源相同才能正常进行通讯,即协议、域名、端口号都彻底一致。javascript

那么是出于什么安全考虑才会引入这种机制呢html

其实主要是用来防止 CSRF 攻击的。简单点说,CSRF 攻击是利用用户的登陆态发起恶意请求。前端

也就是说,没有同源策略的状况下,A 网站能够被任意其余来源的 Ajax 访问到内容。若是你当前 A 网站还存在登陆态,那么对方就能够经过 Ajax 得到你的任何信息。固然跨域并不能彻底阻止 CSRF。html5

而后咱们来考虑一个问题,请求跨域了,那么请求到底发出去没有? 请求必然是发出去了,可是浏览器拦截了响应。你可能会疑问明明经过表单的方式能够发起跨域请求,为何 Ajax 就不会。由于归根结底,跨域是为了阻止用户读取到另外一个域名下的内容,Ajax 能够获取响应,浏览器认为这不安全,因此拦截了响应。可是表单并不会获取新的内容,因此能够发起跨域请求。同时也说明了跨域并不能彻底阻止 CSRF,由于请求毕竟是发出去了。java

二、如何跨域

经常使用的跨域方法有JSONPCORSpostmessage等,web

(1) JSONP

JSONPJSON with padding的简写,是应用JSON的一种新方法,JSONP看起来和JSON差很少,只不过是被包含在函数调用的JSON,像这样:callback({name: 'nany'})express

JSONP跨域原理

经过<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请求是否失败并不容易

(2) CORS

CORS 须要浏览器和后端同时支持。IE 89 须要经过 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()
复制代码

CORS 的优缺点:

  • 使用简单方便,更为安全
  • 支持 POST 请求方式,
  • CORS是一种新型的跨域问题的解决方案,存在兼容问题,仅支持IE 10以上

(3) 降域(document.domain)

这种方式只能用于二级域名相同的状况下,好比a.test.comb.test.com 适用于该方式。

只须要给页面添加 document.domain = 'test.com' 表示二级域名都相同就能够实现跨域。 修改document.domain的方法只适用于不一样子域的框架间的交互。

(4) postMessage

window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可使用它来向其它的window对象发送消息,不管这个window对象是属于同源或不一样源,目前IE8+FireFoxChromeOpera等浏览器都已经支持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('验证经过')
    }
})
复制代码

(5) window.name

windowname属性有个特征:

在一个窗口(window)的生命周期内,窗口载入的全部的页面都是共享一个window.name,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的,并不会因新页面的载入而进行重置。

(6) Web Sockets

web sockets原理:

在JS建立了web socket以后,会有一个HTTP请求发送到浏览器以发起链接。取得服务器响应后,创建的链接会使用HTTP升级从HTTP协议交换为web sockt协议。

补一下脑:

CSRF攻击原理

CSRF(Cross site request forgery),即跨站请求伪造。咱们知道XSS是跨站脚本攻击,就是在用户的浏览器中执行攻击者的脚本,来得到其cookie等信息。而CSRF确是借用用户的身份,向web server发送请求,由于该请求不是用户本意,因此称为“跨站请求伪造”。

CSRF通常的攻击过程是,攻击者向目标网站注入一个恶意的CSRF攻击URL地址(跨站url),当(登陆)用户访问某特定网页时,若是用户点击了该URL,那么攻击就触发了,咱们能够在该恶意的url对应的网页中,利用 来向目标网站发生一个get请求,该请求会携带cookie信息,因此也就借用了用户的身份,也就是伪造了一个请求,该请求能够是目标网站中的用户有权限访问的任意请求。也可使用javascript构造一个提交表单的post请求。好比构造一个转帐的post请求。

因此CSRF的攻击分为了两步,首先要注入恶意URL地址,而后在该地址中写入攻击代码,利用 等标签或者使用Javascript脚本

CSRF防护

referer

由于伪造的请求通常是从第三方网站发起的,因此第一个防护方法就是判断referer 头,若是不是来自本网站的请求,就断定为CSRF攻击。可是该方法只能防护跨站的CSRF攻击,不能防护同站的CSRF攻击(虽然同站的CSRF更难)。

使用验证码

每个重要的post提交页面,使用一个验证码,由于第三方网站是没法得到验证码的。还有使用手机验证码,好比转帐是使用的手机验证码。

使用 token

每个网页包含一个web server产生的token,提交时,也将该token提交到服务器,服务器进行判断,若是token不对,就断定位CSRF攻击。

将敏感操做get改成post,而后在表单中使用token. 尽可能使用post也有利于防护CSRF攻击。

注明一下,整篇文章为学习笔记,多方参考总结,若有版权冲突,请留言,收到消息后会标明版权出处。

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息