如何跨域携带cookies
通常情况下,浏览器是不允许http请求跨域携带cookies的,但凡事有例外,总有一些特殊场景,我们需要跨域携带cookies。
文章中demo,服务端代码使用nodejs 服务端框架koa2编写,客户端使用原生js编写。
场景
我们使用不同的端口号来制造跨域场景,测试http://localhost:3001
上的js脚本 是否可以正常将cookies带给http://localhost:3000
的服务器。
http://localhost:3000
服务端代码
- 服务端包含两个服务:
/api/info
:访问这个服务,会为浏览器种下cookie
/api/hello
: 我们期望通过这个服务,取回我们种在浏览器的cookie
- 服务端使用cors插件,设置
origin:*
允许了跨域访问
const path = require('path');
const Koa = require('koa');
const Router = require('koa-router');
const static = require('koa-static');
const cors = require('koa2-cors');
const router = new Router();
router.get('/api/info', async (ctx) => {
ctx.cookies.set(
'cid',
'000000',
{
domain: 'localhost', // 写cookie所在的域名
path: '/', // 写cookie所在的路径
maxAge: 10 * 60 * 1000, // cookie有效时长
expires: new Date('2021-02-15'), // cookie失效时间
httpOnly: false, // 是否只用于http请求中获取
overwrite: false // 是否允许重写
}
)
ctx.body = 'success'
}).get('/api/hello', async (ctx) => {
const cid = ctx.cookies.get('cid');
ctx.body = {
cid: cid,
}
})
const app = new Koa();
const corsParams = {
origin: '*',
}
app.use(cors(corsParams));
app.use(static(path.join(__dirname, './static')));
app.use(router.routes()).use(router.allowedMethods());
app.listen(process.env.PORT || '3000');
http://localhost:3001/index1.html
脚本
index1.html
上的脚本会进行如下两步操作:
- 自动请求一次
http://localhost:3000
服务器上的/api/info
服务,来种下cookie - 然后手动点击按钮,请求
/api/hello
接口,看是否可以将cookie带给http://localhost:3000
服务器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>hello</h1>
<button onclick="hello()">发送</button>
<script>
setCookies()
function setCookies() {
const xhr = new XMLHttpRequest();
xhr.open('get', '//localhost:3000/api/info')
xhr.send();
}
function hello() {
const xhr = new XMLHttpRequest();
xhr.open('get', '//localhost:3000/api/hello')
xhr.send();
}
</script>
</body>
</html>
从结果中可以看出/api/info
相应头中包含Set-cookie
但是之后的/api/hello
请求头中并没有带有cookie
Credentials
这是因为跨域请求的情况下,浏览器默认禁止携带cookie,如果想携带cookie,需要在请求时设置withCredentials
参数为true
,然后我们像下面这样修改 hello
方法,再次实验。
function setCookies() {
const xhr = new XMLHttpRequest();
xhr.open('get', '//localhost:3000/api/info')
// 这里也需要加,不然浏览器会忽略 /api/info 种的cookie
xhr.withCredentials = true;
xhr.send();
// 使用fetch方法
// fetch('//localhost:3000/api/info', {credentials: 'include'}).then((res) => {
// console.log(res);
// })
}
function hello() {
const xhr = new XMLHttpRequest();
xhr.open('get', '//localhost:3000/api/hello')
xhr.withCredentials = true;
xhr.send();
// 使用fetch方法
// fetch('//localhost:3000/api/hello', {credentials: 'include'}).then((res) => {
// console.log(res);
// })
}
发现浏览器控制台直接报错了:
根据错误提示:当请求的credentials
模式为include
时,响应头Access-Control-Origin
的值不可以是*
,也就是说服务端的cors插件的origin参数,必须指定具体域名,而不能是*
这种通配符。然后我们将服务端代码做如下调整,并再次实验。
...
const app = new Koa();
const corsParams = {
origin: 'http://localhost:3001',
}
app.use(cors(corsParams));
...
然后发现竟然又报错了
根据错误提示:当请求的credentials
模式是include
时,响应头Access-Control-Credentials
的值应该是true
,不应该是''
,所以这次是服务端响应头的设置的问题。我们观察响应头,确实没有设置Access-Control-Credentials
于是修改服务端代码:
...
const app = new Koa();
const corsParams = {
origin: 'http://localhost:3001',
credentials: true,
}
app.use(cors(corsParams));
...
再次发送,请求头中包含了Cookie,响应头中包含了Access-Control-Credentials
,并不再报错。
总结
-
服务端需要设置
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: [特定域名] // 不可以是*
-
客户端
XMLHttpRequest发请求需要设置withCredentials=true,fetch 发请求需要设置 credentials = include