同源策略
浏览器的同源策略 (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。
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属性是无效的。
设置Cookie
用同源的前端页面去请求接口 如果是非同源前端则需要带上withCredentials ,才能够成功保存Cookie 数据, Cors 配置Origin也不能设置为* 了,跨域使用ctx.headers.origin
如果在发送来自其他域的XMLHttpRequest 请求之前,未设置withCredentials 为true ,那么就不能为它自己的域设置cookie 值。而通过设置withCredentials 为true获得的第三方cookies ,将会依旧享受同源策略,因此不能被通过document.cookie 或者从头部相应请求的脚本等访问。
设置**httpOnly: false** ** document.cookie却可以访问**
不同域下的XmlHttpRequest 响应,不论其Access-Control- header 设置什么值,都无法为它自身站点设置cookie 值,除非它在请求之前将withCredentials 设为true 。
同源请求
在**Request Header** 中有一条 **Cookie: user=forguo** ,因为属于同源所以会自己携带上**Cookie** 信息
查看浏览器 Application > Cookie 可以看到Cookie 信息已经存储成功 ![image.png](https://img-blog.csdnimg.cn/img_convert/04ecb0dc3b304a430deb62d501b95fec.png#clientId=u4925323a-4f05-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=329&id=PMW5F&margin=[object Object]&name=image.png&originHeight=329&originWidth=1119&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26640&status=done&style=none&taskId=ub2e348a9-b204-4838-a289-8efaaffd9df&title=&width=1119)
服务端设置Cookie
ctx.cookies.set("user", "forguo", {
domain: '127.0.0.1',
path: '/',
maxAge: 10 * 60 * 1000,
expires: new Date('2022-03-15'),
httpOnly: true,
overwrite: false
});
此时,接口返回的Response Headers 会设置Cookie
Set-Cookie: user=forguo; path=/; expires=Fri, 11 Mar 2022 06:21:27 GMT; domain=127.0.0.1; httponly
携带Cookie
1、前端添加withCredentials
在前端请求的时候设置request 对象的属性withCredentials 为true
axios({
method: "get",
withCredentials: true,
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会报错),可以使用*ctx.headers.origin
const cors = async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
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](https://img-blog.csdnimg.cn/img_convert/e3f33c957dc1598cdbfd8ab6fa53c42c.png#clientId=u31e4d5d9-f672-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=187&id=ua6ac0953&margin=[object Object]&name=image.png&originHeight=187&originWidth=613&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26636&status=done&style=none&taskId=ua42ef69b-1950-4cdc-94e7-a12b4c3e785&title=&width=613)
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-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) {
user = ctx.headers.cookie.split("=")[1];
}
ctx.body = {
data: {
user,
cookie: ctx.headers.cookie,
},
code: 200,
serverTime: Date.now(),
};
})
Ajax跨域
同源政策规定,ajax 请求只能发给同源的网址,否则就报错。
解决方案
Jsonp
实现一个简易jsonp:https://github.com/wforguo/daily-code/tree/master/cors
- 优点:简单适用,老式浏览器全部支持,服务器改造非常小。
- 缺点:只支持get请求。
基本思路是:向网页动态插入
web端代码
function jsonpRequest () {
let script = document.createElement('script');
window.handleFn = function (res) {
console.log(res);
document.getElementsByTagName('head')[0].removeChild(script);
}
let url = `http://127.0.0.1:3003/common/wechat/sdk?callback=handleFn`;
script.setAttribute('src', url);
document.getElementsByTagName('head')[0].appendChild(script);
}
jsonpRequest();
服务端代码
router
.get('/jsonp',async ctx => {
let method = ctx.request.method || 'GET';
let params = {};
if (method === 'GET') {
params = ctx.request.query;
}
if (method === 'POST') {
params = ctx.request.body;
}
try {
if (params.callback) {
let res = params.callback + '(' + JSON.stringify({
data: {
now: Date.now(),
},
code: 200,
}) + ')';
ctx.response.type = 'application/javascript;charset=utf-8';
ctx.body = res;
} else {
ctx.response.type = 'application/json;charset=utf-8';
ctx.body = {
data: {
now: Date.now(),
},
code: 200,
};
}
} catch (e) {
console.log(e);
throw e;
}
})
使用代理
nginx代理
server {
# 转发api到node服务
location /api/ {
proxy_pass http://127.0.0.1:3333/api/;
}
}
node.js代理
const proxy = require('koa2-proxy-middleware');
const options = {
targets: {
'/api/(.*)': {
target: 'http://test02.com/',
changeOrigin: true,
},
}
}
app.use(
proxy(options)
);
const bodyparser = require('koa-bodyparser')
app.use(bodyparser({
enableTypes: ['json', 'form', 'text']
}))
Cors
跨源资源分享(Cross-Origin Resource Sharing),通过相应的请求头与响应头来实现跨域资源访问。
参数
- Access-Control-Allow-Origin
如果将Access-Control-Allow-Origin 的值设置为*,则会接受所有域的请求。这时的客户端不需要任何配置即可进行跨域访问。
- Access-Control-Allow-Credentials
与Access-Control-Allow-Origin 相配套的,还有一个叫Access-Control-Allow-Credentials 的响应头,如果设置为true 则表明服务器允许该请求内包含cookie 信息。
同时,在客户端,还需要在ajax 请求中设置withCredentials 属性为true 。
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
config.headers.common['Authorization'] = 'Bearer ' + token;
config.withCredentials = true;
return config;
}, err => {
return Promise.reject(err);
});
配置
完整的cors 配置
const cors = async (ctx, next) => {
ctx.set('Access-Control-Allow-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');
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`);
}
};
WebSocket
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
复杂请求与简单请求?
|