跨域

同源策略

含义

A 网页设置的Cookie,B网页不能打开,除非这两个网页同源。所谓同源是指

  • 协议相同
  • 域名相同
  • 端口相同

举个来说,http://www.abc.com/index这个网址,协议是http://,域名是www.abc.com,端口是80(默认端口可以省略)

  • http://www.abc.com/index2 : 同源
  • https://www.abc.com/index : 不同源(协议不同)
  • http://www.abcde.com/index :不同源(域名不同)
  • http://www.abc.com:81/index :不同源(端口不同)

目的

同源策略的目的,是为了保证用户的安全,防止恶意的网站窃取数据。

限制范围

目前如果非同源,共有三种行为收到限制。

  • CookieLocalStorageIndexDB 无法读取
  • DOM 无法获得
  • AJAX请求不能发送

跨域请求的安全问题

​ 通常,浏览器会对上面提到的跨域请求作出限制。浏览器之所以要对跨域请求作出限制,是出于安全方面的考虑,因为跨域请求有可能被不法分子利用来发动 CSRF攻击。

CSRF攻击:
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被成为:one click attack/session riding,缩写为:CSRF/XSRF。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。

CSRF 攻击的原理大致描述如下:有两个网站,其中A网站是真实受信任的网站,而B网站是危险网站。在用户登录了收信人的A网站时,本地会存储A网站相关的Cookie,并且浏览器也维护这一个Session会话。这时,如果用户在没有登出A网站的情况下访问危险网站B,那么危险网站B就可以模拟发出一个对A网站的请求(跨域请求)对A网站进行操作,而在A网站的角度来看是并不知道请求是由B网站发出来的(Session和Cookie均为A网站的),这时便成功发动一次CSRF 攻击。

因此,大多数浏览器都会跨域请求作出限制,这是从浏览器层面上的对 CSRF 攻击的一种防御,但是需要注意的是在复杂的网络环境中借助浏览器来防御 CSRF 攻击并不足够,还需要从服务端或者客户端方面入手防御。详细可以参考这篇文章浅谈CSRF攻击方式

跨域正确打开方式

服务端

const Koa = require('koa'); 
const route = require('koa-route');
const app = new Koa();
const fs = require('fs');
const main = async ctx=>{
        ctx.response.body = await fs.createReadStream('./1.json')
}

const add = async ctx=>{
    await ctx.req.addListener('data', (data) => { // 有数据传入的时候
        console.log(data,'aa')
    });
}
app.use(route.get('/',main))
app.use(route.post('/add'),add)
app.listen(3300)

上面代码可以看到我们的服务端口是3300

客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="div">
        
    </div>
    <button class="button">---</button>
    <button class="add">++</button>
</body>
</html>
<script src="./node_modules/axios/dist/axios.min.js"> </script>
<script>
    let div = document.getElementById('div')
    let button = document.getElementsByClassName('button')[0];
    let add = document.getElementsByClassName('add')[0];
    let fn = ()=>{
        axios.get('http://192.168.10.40:3300/').then(res=>{
        let {data} = res;
        let str = ``;
        data.forEach(item=>{
            str+=`<p style="color:'red';fonst-size:'30px'">${item.title}</p><p style="color:'aqua'">${item.content}</p>`
        })
        div.innerHTML = str;
    })
    }
    fn()
    button.onclick = function(){
        let index = 1 
        axios.get(`http://192.168.10.40:3300/delete?index=${index}`).then(res=>{
            console.log(res,'aaa')
            let {data} = res;
            data == 1&& fn()
        })
    }
</script>

当我们执行这段代码时,控制台会报错Access to XMLHttpRequest at 'http://---:3300/' from origin 'http://---:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.,因为两个地址的端口不一样,所以会造成跨域

这个错误就是提示跨域

解决跨域的方式

jsonp

jsonp是一种跨域通信的手段,特德原理其实很简单:

  • 首先是利用script标签的src属性来实现跨域。
  • 通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数之后在返回,实现服务端向客户端通信。
  • 由于使用script 标签的src属性,因此只支持get方法

服务端

const Koa = require('koa'); 
const route = require('koa-route');
const app = new Koa();
const fs = require('fs');
const main = async ctx=>{
    let _data = await fs.readFileSync('./1.json','utf-8')
    let {query} = ctx.request;
    ctx.body = `${query.callback}(${JSON.stringify(_data)})`
}

const add = async ctx=>{
    await ctx.req.addListener('data', (data) => { // 有数据传入的时候
        console.log(data,'aa')
    });
}
app.use(route.get('/',main))
app.use(route.post('/add'),add)
app.listen(3300)

客户端

    function jsonp({url,params,callback}) {
        return new Promise((resolve,reject)=>{
            let _script = document.createElement('script');
            window[callback] = function(data) {
                resolve(JSON.parse(data))
            }
            params = {...params,callback};
            let _arr = [];
            for(let key in params){
                // 属性值与属性名以等号方式拼接在一起
                _arr.push(`${key}=${params[key]}`)
            }
            _script.src = `${url}?${_arr.join('&')}`;
            document.body.appendChild(_script);
            
        })
    }
    jsonp({
        url:'http://192.168.10.40:3300/',
        params:{},
        callback:'show'
    }).then(res=>{
        console.log(res,'数据')
    })

cors

cors 与 jsonp的区别

  • jsonp只能实现get请求,而cors支持所有类习惯的HTTP请求.
  • 使用cors,开发者可以使用普通的XMLHttpRequest发起请求和获取数据,比起jsonp有更好的错误处理。

cors对于浏览器发过来的AJAX跨域七个球分为两种

  • 简单请求
  • 非简单请求
简单请求

所谓简单请求就是HEAD,get,post三种请求方式

浏览器会在header信息里卖弄多加一个字段:

key:origin
value: 协议+域名+端口

服务器根据对象里的origin值来决定是否同意这次请求

如果请求通过

请求通过后,服务器会在header里多增加几个字段:

Access-Control-Allow-Origin: https://localhost:8080
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Token

上面的头信息之中,有三个与cors请求相关的字段,都以Access-Control-开头。

  1. Access-Control-Allow-Origin:
    该字段是必须的。他的值要么是请求时origin字段的值,要么时一个*,标识接收任意域名的请求
  2. Access-Control-Allow-Credentials:
    该字段可选。他的值是一个布尔值,标识是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务武器不要浏览器发送Cookie,删除该字段即可
  3. Access-Control-Expose-Headers:该字段可选。cors请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面制定。
非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在 证是通信之前,增加一次HTTP查询请求,成为预检请求

    axios.put('http://192.168.10.40:3300/').then(res=>{
        
    })

上面代码中,HTTP请求的方法是PUT

浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的信息

"预检"请求用的请求方法是OPTTIONS,标识这个请求时用来查询的。头信息里面,关键字段时origin,标识请求来自哪个源。除了origin字段,"预检"请求的头信息包括两个特殊的字段

  1. Access-Control-Request-Method:该字段时必须的,用来列出浏览器的CORS请求会用到那些HTTP方法,上列是PUT
  2. Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。
预检请求的回应

浏览器收到"预检"请求以后,检查了originAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨域请求,就可以做出回应。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为*,表示统一任意跨源请求。

Access-Control-Allow-Origin: *

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误。

Access-Control-Allow-Methods

该地段必须,它的值是逗号分隔的一个字符串,表名服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers地段是必须的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段

Access-Control-Allow-Credentials

与简单请求时的含义相同

Access-Control-Max-Age

该字段可选,用来制定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应20天(1728000秒),在此期间,不用发出另一条预检请求。

浏览器的正常请求和回应

一旦浏览器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

服务端


    const Koa = require('koa'); 
   const route = require('koa-route');
   const app = new Koa();
   const fs = require('fs');
   const main = async ctx=>{
       ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:5500"); 这里是关键
       ctx.response.body = await fs.createReadStream('./1.json');
   }
   app.use(route.get('/',main))
   app.listen(3300)

posted @ 2019-06-06 19:52  我会放电啪啪  阅读(200)  评论(0编辑  收藏  举报