[TOC]

同源策略

浏览器的同源策略 (Same Origin Policy)

image.png

同源策略是网站安全的基石,https://a.com只能存取自己网站的资源,不允许网站https://b.com来存取。
目的是为了保证用户信息的安全,防止恶意的网站窃取数据。

判断

只要url当中的协议scheme、域名domain、端口port都一样,就会被视为就会被视为同源 (same-origin),
其他则是不同源。

image.png

http://domain-a.com → 不同源.scheme 不同
https://domain-a.com/mike → 同源
https://news.domain-a.com → 不同源.domain 不同
https://domain-a.com:81 → 不同源.port 不同
https://domain-b.com → 不同源.domain 不同

限制范围

1、在某些情況下跨来源是被允许的,比如跨来源嵌入。
script、link、img、iframe、video、audio、font-face等等。

2、如非同源,跨来源读取通常被禁止。

  • Cookie、LocalStorage 和 IndexDB 无法读取。
  • DOM 无法获得。
  • AJAX 请求不能发送。

Cookie跨域

Cookie的同源策略

image.png

只要 domain 跟 Path 与Cookie 上的一样就会被视为同源。若经过一些设定才会判断 scheme 要是 http 或 https。

// 加了 Secure 会限定此 Cookie 只能以 https 传送
Set-Cookie: id=1234567; domain=hello.com; Secure

下面这样设置,domain为一级域名,二级域名和三级域名不用做任何设置,都可以读取这个Cookie

Set-Cookie: key=value; domain=.example.com; path=/

跨域携带Cookie

跨域请求如何携带cookie

withCredentials

XMLHttpRequest.withCredentials 属性是一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。在同一个站点下使用withCredentials属性是无效的。

1、设置Cookie

不同域下的XmlHttpRequest响应,不论其Access-Control-header 设置什么值,都无法为它自身站点设置cookie值,除非它在请求之前将withCredentials设为true

同IP不同端口

比如127.0.0.1:3000请求127.0.0.1:8000

ctx.cookies.set("user", "forguo", {
    domain: '127.0.0.1',  // 写cookie所在的域名,默认主域名不带.,也就是子域名不生效
    path: '/',       // 写cookie所在的路径
    maxAge: 10 * 60 * 1000, // cookie有效时长 10分钟
    expires: new Date('2022-03-15'),  // cookie失效时间
    httpOnly: true,  // 是否只用于http请求中获取
    overwrite: false // 是否允许重写
});

image.png

查看浏览器 Application> Cookie可以看到Cookie信息已经存储成功
image.png

此时,接口返回的Response Headers会设置Cookie

Set-Cookie: user=forguo; path=/; expires=Fri, 11 Mar 2022 06:21:27 GMT; domain=127.0.0.1; httponly

设置好前后端配置后,我们通过跨端口从 http://127.0.0.1:3003http://127.0.0.1:3009 发起一个请求就会携带上Cookie数据。

域名端口都不同

以上是同域名不同端口,如果是不同域名和端口,需要设置**samesite****none**,但是设置为none有一个要求,就是必须**secure**属性为true,也就是必须使用**https**
而且不能是nginxproxy_pass之后的api,否则会有以下报错,

Cannot send secure cookie over unencrypted connection

这样设置过后的cookie,实际是存在于接口所在的domain,下次请求接口会带上该cookie数据

ctx.cookies.set("user", params.user, {
    // domain: '127.0.0.1',  // 写cookie所在的域名
    // path: '/',       // 写cookie所在的路径
    maxAge: 60 * 60 * 1000 * 24 * 7, // cookie有效时长 7天
    expires: new Date(Date.now() + 60 * 60 * 1000 * 24 * 7), // cookie失效时间
    httpOnly: false,  // 是否只用于http请求中获取(设置为true的话,客户端在控制台就获取不到)
    overwrite: false,  // 是否允许重写
    secure: true, // ++新增
    sameSite: 'none', // ++新增
});

proxy_pass过后的api
image.png

成功设置cookie
image.png
成功携带cookie
image.png
此时Cookie存放于接口所在域名,同时下次请求该域名下的接口,会带上该Cookie

2、携带Cookie

1、前端添加withCredentials

在前端请求的时候设置request对象的属性withCredentialstrue

// axios添加withCredentials
axios({
    method: "get",
    withCredentials: true, // ++ 携带cookie数据
    url: "http://127.0.0.1:3009/user/info",
}).then((res) => {
    console.log(res);
});

2、服务端设置Access-Control-Allow-Origin

此时,我们从 http://127.0.0.1:3003/http://127.0.0.1:3009/user/info 去发起一个请求,发现报错
意思是需要设置header的Access-Control-Allow-Origin属性:

image.png

携带了Cookie,但是报错CORS error
image.png
跨域发送 Cookie 还要求 **Access-Control-Allow-Origin**不允许使用通配符*(跨域设置Cookie会报错),而且只能指定单一域名:可以使用**ctx.headers.origin**

const cors = async (ctx, next) => {
    // 设置跨域
    ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
    // ctx.set('Access-Control-Allow-Headers', 'X-Auth-Token, Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
    // ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
    // 允许携带cookie
    // ctx.set('Access-Control-Allow-Credentials', 'true');
    if (ctx.method === 'OPTIONS') {
        ctx.body = 200;
    } else {
        const start = new Date();
        await next();
        const ms = new Date() - start;
        console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
    }
};

如果是下面这个错误,就说明配置的Origin和当前前端所在Origin不是同一个
that is not equal to the supplied origin.

image.png

3、服务端设置Access-Control-Allow-Credentials

重复1的请求,发现还是报错
意思是Access-Control-Allow-Credentials这个属性应该设置为true
当然如果前端没有设置withCredentials,是不会携带Cookie的,也不会有这个错误的
image.png

const cors = async (ctx, next) => {
    // 设置跨域
    ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
    // ctx.set('Access-Control-Allow-Headers', 'X-Auth-Token, Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
    // ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
    // 允许携带cookie
    ctx.set('Access-Control-Allow-Credentials', 'true');
    if (ctx.method === 'OPTIONS') {
        ctx.body = 200;
    } else {
        const start = new Date();
        await next();
        const ms = new Date() - start;
        console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
    }
};

再去发起1的请求,会发现请求成功,并且返回了cookie数据
image.png

接口处理是解析了Cookie内容,并返回

.get('/info',async ctx => {
    let user = {};
    if (ctx.headers.cookie) {
        // req.headers.cookie: user=forguo
        user = ctx.headers.cookie.split("=")[1];
    }
    ctx.body = {
        data: {
            user,
            cookie: ctx.headers.cookie,
        },
        code: 200,
        serverTime: Date.now(),
    };
})

Ajax跨域

同源政策规定,ajax请求只能发给同源的网址,否则就报错。

解决方案

  • JSONP
  • 服务器代理
  • CORS
  • WebSocket

Jsonp

实现一个简易jsonp:https://github.com/f2e-learning/daily-code/tree/master/cors

  • 优点:简单适用,老式浏览器全部支持,服务器改造非常小。
  • 缺点:只支持get请求。

基本思路是:向网页动态插入