同源策略
浏览器的同源策略 (Same Origin Policy)
同源策略是网站安全的基石,https://a.com只能存取自己网站的资源,不允许网站https://b.com来存取。
目的是为了保证用户信息的安全,防止恶意的网站窃取数据。
判断
只要url当中的协议scheme、域名domain、端口port都一样,就会被视为就会被视为同源 (same-origin),
其他则是不同源。
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的同源策略
只要 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
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 // 是否允许重写
});
查看浏览器 Application
> Cookie
可以看到Cookie
信息已经存储成功
此时,接口返回的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:3003向 http://127.0.0.1:3009 发起一个请求就会携带上Cookie
数据。
域名端口都不同
以上是同域名不同端口,如果是不同域名和端口,需要设置**samesite**
为**none**
,但是设置为none有一个要求,就是必须**secure**
属性为true,也就是必须使用**https**
。
而且不能是nginx
的proxy_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
成功设置cookie
成功携带cookie
此时Cookie
存放于接口所在域名,同时下次请求该域名下的接口,会带上该Cookie
2、携带Cookie
1、前端添加withCredentials
在前端请求的时候设置request
对象的属性withCredentials
为true
// 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
属性:
携带了Cookie
,但是报错CORS error
跨域发送 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.
3、服务端设置Access-Control-Allow-Credentials
重复1的请求,发现还是报错
意思是Access-Control-Allow-Credentials
这个属性应该设置为true
当然如果前端没有设置withCredentials
,是不会携带Cookie的,也不会有这个错误的
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
数据
接口处理是解析了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请求。
基本思路是:向网页动态插入