cookie没带上
前情概要
某天正在努力学习(摸鱼),收到一个需求,说需要增加一个鉴权的标识,后端会设置好,前端cookie带上就行。我心想,这还哪里用我改,后端都设置了好了,前端自动就会带上了,躺平,继续学习(摸鱼)
后端设置cookie
我们都知道前端可以设置cookie,但是后端也是可以设置cookie的。让我们看看后端怎么设置cookie。
// 引入express
const express = require('express');
const app = express();
// 设置中间件
app.use((req, res, next) => {
// 设置cookie,
// 设置了一个名字是【testCookie】,值是【testCookieValue】,
res.cookie('testCookie', 'testCookieValue', {
maxAge: 3600000, // 过期时间是一个小时
httpOnly: true, // 只允许http或者https带上,无法通过客户端脚本访问
sameSite: 'none', //
secure: 'false',
domain: 'localhost'
});
next();
});
就是我上面这样一串代码,然后当响应时候响应头就会带上一个 set-cookie 的字段,浏览器识别到这个字段就会对应的值设置到cookie的缓存里面去,而且前端发送请求的时候也会自动带上,所以一般后端说他设置了,前端就是基本不用理得了。
在这里简单说一下设置cookie的一些常用配置项,不然很容易踩坑:
- expires/maxAge:
-
- 到期时间:设置cookie的到,如new Date(某个时间段)
- 过期时效:毫秒为单位,比如设置1000就是1秒后就失效。
- 失效的cookie浏览器回自动清除
- 默认是会话内有效,关闭会话就自动清除(关掉对应网页对应页面)
- domain:
-
- 就是在什么域名内这个cookie才可生效使用,,这个要注意,比如你的域名是
woshiyuming.com,然后通过域名解析增加了一个aa.woshiyuming.com,一个bb.woshiyuming.com,那么设置你单独设置哪个都只会在当前域名生效,如果你想三个域名都生效,应该设置.woshiyuming.com(注意这里开始是有个 . )这样就可以了 - 默认是访问的域名
- 就是在什么域名内这个cookie才可生效使用,,这个要注意,比如你的域名是
- path:
-
- 这个也是在什么域内才有效,就是同一个域名但是设置的path不同,cookie也不可共享
- 默认就是/
- httpOnly
-
- 只允许http或者https带上,无法通过客户端脚本访问
- 默认是false,如果false别人就可以使用 document.cookie API 在客户端 JavaScript 中读取和修改这些 cookie,很危险
- 只能后端设置,前端无法设置
- sameSite
-
- none:适用于跨域共享cookie的场景
- lax:
-
-
- 可带上cookie:用网址访问可以带上,用表单请求跳转get方式可以带上
- 不可带上cookie:post表单提交,ajax请求,iframe或iframe的导航
-
-
- strict:只能在设置cookie的站点才会带上cookie
- 默认的话不同浏览器会有不同
- secure
-
- 如果sameSite设置成了none,那么secure必须设置成false,不然无法跨域共享
- 如果设置了true的话,那么只有在https协议里cookie才会有效
- 默认值是false
withCredentials
测试给我指派了一下bug,鉴权没有通过,后端告诉我请求没有带上cookie,我说怎么可能,是不是后端没有返回,然后我一看响应体。后端是有返回的,所以是我的锅?
看一下浏览cookie的缓存
居然没有存进去后端返回的值,真的是前端的锅?看一下前端的请求代码:
function sendRequest() {
fetch('http://localhost:9969/test', {
method: "post",
headers: {
"content-type": 'application/json'
},
}).then(res => res.json()).then(data => {
console.log(data);
})
}
代码看起来没什么问题,可是我代码又没有设置cookie为空之类的操作,所以经过我的不懈google,找出了几个点,就是跨域需要带上cookie的话那么
- 后端必须设置
'Access-Control-Allow-Credentials': 'true' - 后端如果设置了
'Access-Control-Allow-Credentials': 'true',那么Access-Control-Allow-Origin不可设置成*,必须指定具体的域名,例如:'Access-Control-Allow-Origin', 'http://localhost:63342' - 前端请求时候必须设置
withCredentials: true
原来是这样,那就是我和后端都需要进行修改,修改后的代码:
function sendRequest() {
fetch('http://localhost:9969/test', {
method: "post",
credentials: 'include', //增加了这个配置, fetch里面就是这样设置的
headers: {
"content-type": 'application/json'
},
}).then(res => res.json()).then(data => {
console.log(data);
})
}
// axios的设置,原生ajax的设置都是设置 withCredentials为true,具体大家自己baidu啦
// 引入express
const express = require('express');
const app = express();
// 设置中间件
app.use((req, res, next) => {
// 这四个header都是设置跨域的一些规则
res.header('Access-Control-Allow-Origin', 'http://localhost:63342'); // 修改了这个
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
res.header('Access-Control-Allow-Credentials', 'true'); // 增加了这个
res.cookie('testCookie', 'testCookieValue', { maxAge: 3600000, httpOnly: true});
next();
})
按照这个设置,应该就一切正常了,发送一个请求看看:
总结
终于搞定了!让我们来总结一下:
- 不同的二级域名且顶级域名相同的需要共享cookie需要设置【.父级域名】
- cookie带不上的原因其实一般都是跨域,需要设置:
-
- 跨域前端发送请求时候需要设置:
withCredentials: true - 后端需要设置
'Access-Control-Allow-Credentials':'true'同时'Access-Control-Allow-Origin'不能设置成*,必须是具体域名 - 后端在设置cookie时候需要需要设置这两个选项
sameSite: 'none'; secure: 'false'
- 跨域前端发送请求时候需要设置:
后端响应头丢了
无法获取响应头
测试反馈还是鉴权不通过,我一查看,发现前端获取一个后端设置的token请求头获取不到,是空值。但是我查看浏览器的响应头是有数据的,而且'Access-Control-Expose-Headers'也设置了*,应该就是暴漏所有请求头了
但是一看前端获取的请求头数据,没有我们需要的token数据
然后我开始百度查后端是如何设置跨域的请求头的,查出来的毫无例外都是设置'Access-Control-Expose-Headers'这个字段,但是我看别人都是设置了具体的字段,但是我查好像设置*也可以。所以我没觉得这个是后端的问题,最后在mdn看到了这段话
若请求没有携带凭据(请求没有 HTTP Cookie或认证信息),“”才会被当作一个特殊的通配符。对于带有凭据的请求,会被简单地当作标头名称“”,没有特殊的语义。
服务器可以为不带凭据的请求响应通配符:
Access-Control-Expose-Headers: *
但是,这并不会匹配 Authorization 标头,所以如果你要暴露它,需要显式指定:
Access-Control-Expose-Headers: *, Authorization
总结
所以就是说我们是携带了凭证,有cookie的,然这个*星号通配符就是无用的,所以是需要指定一个具体的请求头,所以是后端的锅,后端代码需要修改。
还有之所有获取不了token但是浏览器的响应头还能正常显示也是浏览器的机制,不可以用这个就判断后端的代码没有问题。
const express = require('express');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:63342');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
res.header('Access-Control-Allow-Credentials', 'true');
// 这里设置跨域暴露token,修改的就是这一行
res.header('Access-Control-Expose-Headers', 'token');
// 这里是后端设置token
res.header('token', 'ejijejfjidjfjweijodjfsi');
res.cookie('testCookie', 'testCookieValue', {
expires: '',
maxAge: 3600000,
httpOnly: true,
sameSite: 'none',
secure: 'false',
domain: 'localhost'
});
next();
})
function sendRequest() {
fetch('http://localhost:9969/test', {
method: "post",
credentials: 'include',
headers: {
"content-type": 'application/json'
},
}).then(res => {
res.headers.forEach((value, key) => { // 用fetch请求的话,headers是要这样获取的
console.log(key + ': ' + value);
});
})
}