如何跨域携带cookies

11,048 阅读3分钟

如何跨域携带cookies

通常情况下,浏览器是不允许http请求跨域携带cookies的,但凡事有例外,总有一些特殊场景,我们需要跨域携带cookies。

文章中demo,服务端代码使用nodejs 服务端框架koa2编写,客户端使用原生js编写。

场景

我们使用不同的端口号来制造跨域场景,测试http://localhost:3001上的js脚本 是否可以正常将cookies带给http://localhost:3000 的服务器。

http://localhost:3000服务端代码

  1. 服务端包含两个服务:

/api/info:访问这个服务,会为浏览器种下cookie

/api/hello: 我们期望通过这个服务,取回我们种在浏览器的cookie

  1. 服务端使用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上的脚本会进行如下两步操作:

  1. 自动请求一次http://localhost:3000服务器上的/api/info服务,来种下cookie
  2. 然后手动点击按钮,请求/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,并不再报错。

总结

  1. 服务端需要设置

    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Origin: [特定域名] // 不可以是*
    
  2. 客户端

    XMLHttpRequest发请求需要设置withCredentials=true,fetch 发请求需要设置 credentials = include

参考

developer.mozilla.org/en-US/docs/…