我们先从一个问题来引入接下来的内容,先看下以下代码
const koa = require('koa');
const Router = require('koa-router');
let server = new koa();
let router = new Router();
server.listen(8080);
server.use(router.routes());
// cookie循环秘钥
server.keys = [
'sdf7as9d8f7asd7f9sdfa9s',
'sdfasd6fgjhgjgdgjsfgsf5',
'nk54h3k2klj78kh89kh5kh3',
];
router.get('/api/user', async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "*");
ctx.cookies.set('username', 'daryl', {
signed: true,
maxAge: 86400 * 30,
})
ctx.body = {username: 'daryl', age: 18};
});
我们开启了一个服务器,当请求/api/user
接口的时候,我们设置跨域,并且设置了一个cookie
,key
为username
,value
为daryl
当我们直接在浏览器中请求这个接口,我们发现可以将cookie
设置成功,响应头中有set-cookie
响应头,并在application
中可以看到已经设置成功
但是当我们使用ajax
请求去调用接口,此时cookie
无论如何也设置不上
客户端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="btn_get">get</button>
<script>
// 直接fetch请求
let btn_get = document.getElementById('btn_get');
btn_get.addEventListener("click", () => {
fetch('http://localhost:8080/api/user',
)
.then(response => {
return response.json();
})
// 请求的数据
.then(data => {
console.log(data);
}).catch(err => console.log(err));
});
</script>
</body>
</html>
我们可以看到,在响应头中也有set-cookie
字段,但是application
中就是设置不成功
这是什么原因?实际上是因为当我们使用ajax
去请求接口时,是发生了跨域的,而设置cookie
是不允许跨域设置的,浏览器也不会跨域发送cookie
如果想要能够跨域设置cookie
或发送cookie
,需要服务端和ajax
同时配置,才能允许跨域设置cookie
服务端需要添加一个Access-Control-Allow-Credentials
响应头,客户端ajax
请求时需要设置withCredentials
属性为true
(使用fetch的配置是{ credentials: 'include' }
,不同的库有不同的设置方法)
我们加上这两个设置,看能不能设置成功。
router.get('/api/user', async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "*");
ctx.set("Access-Control-Allow-Credentials", true);
ctx.cookies.set('username', 'daryl', {
signed: true,
maxAge: 86400 * 30,
})
ctx.body = {username: 'daryl', age: 18};
});
btn_get.addEventListener("click", () => {
fetch('http://localhost:8080/api/user',
{
credentials: 'include'
}
)
.then(response => {
return response.json();
})
// 请求的数据
.then(data => {
console.log(data);
}).catch(err => console.log(err));
});
我们来看看效果
直接报跨域错误了,咋回事,我们明明设置了Access-Control-Allow-Origin
为*
啊。
记住,这是因为一旦当我们设置了Access-Control-Allow-Credentials
请求头,我们Access-Control-Allow-Origin
就不能直接设置为*
了,需要指定明确的源,因此这里我们需要设置为http://127.0.0.1:5500
(客户端的url
是这个)
router.get('/api/user', async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
ctx.set("Access-Control-Allow-Credentials", true);
ctx.cookies.set('username', 'daryl', {
signed: true,
maxAge: 86400 * 30,
})
ctx.body = {username: 'daryl', age: 18};
});
我们再来看看效果
跨域错误没有了,但是在set-cookie
头的这里出现了一个黄色的感叹号,application
里面cookie
也没有设置成功
这个警告是什么?
这个警告是告诉我们cookie
的SameSite
属性当前默认是Lax
,如果我们需要跨站点访问cookie
,需要将其设置为None
这个SameSite
用来限制第三方 Cookie
,可以设置三个值
Strict
Strict
最为严格,完全禁止第三方Cookie
,跨站点时,任何情况下都不会发送Cookie
。换言之,只有当前网页的URL
与请求目标一致,才会带上Cookie
。这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个
GitHub
链接,用户点击跳转就不会带有GitHub
的Cookie
,跳转过去总是未登陆状态。Lax
Lax
规则稍稍放宽,大多数情况也是不发送第三方Cookie
,但是导航到目标网址的Get
请求除外。导航到目标网址的
GET
请求,只包括三种情况:链接,预加载请求,GET
表单。详见下表。请求类型 示例 正常情况 设置Lax后 链接 <a href="..."></a>
发送 Cookie 发送 Cookie 预加载 <link rel="prerender" href="..."/>
发送 Cookie 发送 Cookie GET 表单 <form method="GET" action="...">
发送 Cookie 发送 Cookie POST 表单 <form method="POST" action="...">
发送 Cookie 不发送 iframe <iframe src="..."></iframe>
发送 Cookie 不发送 AJAX $.get("...")
发送 Cookie 不发送 Image <img src="...">
发送 Cookie 不发送 None
Chrome
计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite
属性,将其设为None
。不过,前提是必须同时设置Secure
属性(Cookie
只能通过HTTPS
协议发送),否则无效。下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None
下面的设置有效。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
因此,如果我们想要跨站点发送cookie
,需要设置SameSite=None
,并且同时设置Secure
属性(即配置https
)
我们就来配置下
const koa = require('koa');
const Router = require('koa-router');
const fs = require('fs');
const https = require('https');
const sslify = require('koa-sslify').default;
var options = {
key: fs.readFileSync('./private_key.pem'), //私钥文件路径
cert: fs.readFileSync('./ca-cert.pem') //证书文件路径
};
let server = new koa();
server.use(sslify());
let router = new Router();
server.use(router.routes());
https.createServer(options, server.callback()).listen(8080, (err) => {
if (err) {
console.log('服务启动出错', err);
} else {
console.log('guessWord-server运行在' + 8080 + '端口');
}
});
// cookie循环秘钥
server.keys = [
'sdf7as9d8f7asd7f9sdfa9s',
'sdfasd6fgjhgjgdgjsfgsf5',
'nk54h3k2klj78kh89kh5kh3',
];
router.get('/api/user', async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
ctx.set("Access-Control-Allow-Credentials", true);
ctx.cookies.set('username', 'daryl', {
signed: true,
maxAge: 86400 * 30,
sameSite: 'none',
secure: true
})
ctx.body = {username: 'daryl', age: 18};
});
此时请求路径则需要修改为https
btn_get.addEventListener("click", () => {
fetch('https://localhost:8080/api/user',
{
credentials: 'include'
}
)
.then(response => {
return response.json();
})
// 请求的数据
.then(data => {
console.log(data);
}).catch(err => console.log(err));
});
来看看效果,可以看到警告没了,并且application
里看cookie
也成功设置上了
最后来看看设置成功后,请求会不会带上当前的cookie,发送给服务端(最终版)
const koa = require('koa');
const Router = require('koa-router');
const fs = require('fs');
const https = require('https');
const sslify = require('koa-sslify').default;
var options = {
key: fs.readFileSync('./private_key.pem'), //私钥文件路径
cert: fs.readFileSync('./ca-cert.pem') //证书文件路径
};
let server = new koa();
server.use(sslify());
let router = new Router();
server.use(router.routes());
https.createServer(options, server.callback()).listen(8080, (err) => {
if (err) {
console.log('服务启动出错', err);
} else {
console.log('guessWord-server运行在' + 8080 + '端口');
}
});
// cookie循环秘钥
server.keys = [
'sdf7as9d8f7asd7f9sdfa9s',
'sdfasd6fgjhgjgdgjsfgsf5',
'nk54h3k2klj78kh89kh5kh3',
];
router.get('/api/user', async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
ctx.set("Access-Control-Allow-Credentials", true);
ctx.cookies.set('username', 'daryl', {
signed: true,
maxAge: 86400 * 30,
sameSite: 'none',
secure: true
})
ctx.body = {username: 'daryl', age: 18};
});
router.get('/api/cookie', async (ctx, next) => {
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
ctx.set("Access-Control-Allow-Credentials", true);
console.log(ctx.cookies.get('username'));
ctx.body = ctx.cookies.get('username'); // 将cookie的值作为返回值
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<button id="btn_get">get</button>
<button id="btn_cookie">send cookie</button>
<script>
// 直接fetch请求
let btn_get = document.getElementById('btn_get');
btn_get.addEventListener("click", () => {
fetch('https://localhost:8080/api/user',
{
credentials: 'include'
}
)
.then(response => {
return response.json();
})
// 请求的数据
.then(data => {
console.log(data);
}).catch(err => console.log(err));
});
let btn_cookie = document.getElementById('btn_cookie');
btn_cookie.addEventListener('click', () => {
fetch('https://localhost:8080/api/cookie',{
credentials: 'include'
}).then(response => {
return response.json();
})
// 请求的数据
.then(data => {
console.log(data);
}).catch(err => console.log(err));
})
</script>
</body>
</html>
可以看到,最终cookie
值会放到请求头中,发送给服务端,而接口的返回值也是服务器读到的cookie
中的值;
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com