我们先从一个问题来引入接下来的内容,先看下以下代码
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