cookie带不上?响应头拿不到数据?

466 阅读6分钟

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 (注意这里开始是有个 . )这样就可以了
    • 默认是访问的域名
  • 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的话那么

  1. 后端必须设置'Access-Control-Allow-Credentials': 'true'
  2. 后端如果设置了'Access-Control-Allow-Credentials': 'true',那么Access-Control-Allow-Origin不可设置成*,必须指定具体的域名,例如:'Access-Control-Allow-Origin', 'http://localhost:63342'
  3. 前端请求时候必须设置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);
          });
      })
    }