以 key-value 的形式存储在浏览器中
可配置一些用于控制 cookie 有效期、安全性、适用范围… 的可选属性
默认情况下,页面关闭 → 清除数据;也可以手动删除
key-value 的 key 和 value 都只能是 [字符串]
对于 [数字] value,存储时会自动将其转换为字符串
对于 [对象]、[数组] value,我们可以使用 JSON.stringify()
进行转换
客户端发送请求时,会自动把当前域名下所有未过期的 cookie 一同发送到服务器
因为客户端与服务器交互时 会带上 cookie,所以 cookie 相对不安全
不建议服务器将重要的隐私数据,以 cookie 的形式发送给浏览器
document.cookie
操作 cookiedocument.cookie = "name=superman"; // 存数据
console.log(document.cookie); // 获取所有 cookie
domain
:cookie 生效的域名path
:cookie 生效的路由路径expires
/ max-age
:cookie 的有效期;默认为 session,即页面关闭就清空数据expires
为指定时间,从 1970-1-1-0-0-0 开始算;max-age
为相对时间document.cookie = "age=18; max-age=30"; // 30s 后失效
npm i cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
res.cookie(key, value [, options])
:用于设置 cookie可通过配置对象 options
设置 cookie 的字段:
domain
:String;cookie 生效的域名。默认为应用程序的域名path
:String;cookie 生效的路由路径。默认为 /
expires
:Date;cookie (格林尼治时间)的失效日期。如果未指定或设置为 0
,则创建会话 cookiemaxAge
:Number;相对于当前时间设置失效时间,以毫秒为单位secure
:Boolean;将 cookie 标记为仅用于 HTTPSencode
:Function;为 cookie 值编码的同步函数。默认为 encodeURLComponenthttpOnly
:Boolean;将 cookie 标记为仅可由 web 服务器访问,前端不可访问;默认为 false
signed
:Boolean;指示是否应该对 cookie 进行签名const express = require("express");
const app = express();
app.listen(8080, () => console.log("http://127.0.0.1:8080"));
app.use(express.static(__dirname + "/public"));
// 引入、注册 cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get("/setCookie", (req, res) => {
// 设置 cookie
res.cookie('name', 'superman', {
maxAge: 10000
});
res.send("setCookie");
});
app.get("/getCookie", (req, res) => {
// 获取 cookie
console.log("cookies", req.cookies); // cookies { name: 'superman' }
res.send("getCookie");
});
<body>
<button id="but1">设置cookiebutton>
<button id="but2">获取cookiebutton>
body>
<script src="https://unpkg.com/axios/dist/axios.min.js">script>
<script>
// 设置 cookie
but1.onclick = async () => {
let res = await axios({
url: "/setCookie"
});
console.log("data", res.data); // data setCookie
}
// 获取 cookie
but2.onclick = async () => {
let res = await axios({
url: "/getCookie"
});
console.log("data", res.data); // data getCookie
}
script>
客户端第 1 次请求服务器时,服务器向客户端返回一个用于身份认证的 cookie,客户端会将该 cookie 保存在浏览器中
随后,当客户端请求服务器时,浏览器会自动将当前域名下所有未过期的 cookie (当然也就包括身份认证相关的 cookie) 以 [请求头] 的形式发送给服务器,服务器即可验证客户端身份
以 key-value 的形式存储在服务器中
默认情况下,页面关闭 → 清除数据;也可以手动删除
key-value 的 key 和 value 都只能是 [字符串]
对于 [数字] value,存储时会自动将其转换为字符串
对于 [对象]、[数组] value,我们可以使用 JSON.stringify()
进行转换
sessionStorage
操作 sessionStorageconsole.log(sessionStorage); // 打印 sessionStorage 对象
sessionStorage.setItem("name", "superman"); // 存数据
const name = sessionStorage.getItem("name"); // 取数据
sessionStorage.removeItem("name"); // 删除数据
sessionStorage.clear(); // 清空数据
null
JSON.parse(null)
返回 null
npm i express-session
const session = require("express-session");
app.use(session({
secret: "string key", // 加密的字符串,里面内容可以先随便写
resave: false, // 强制保存 session,即使它没变化
saveUninitialized: true, // 强制将未初始化的 session 存储
}));
req.session
获取、配置 session 啦console.log(req.session); // 获取 session
req.session.key = value; // 添加 session 属性
console.log(req.session.key); // 获取 session 属性
req.session.key = null; // 删除 session 属性
req.session.destory(err => {}); // 清空 session - 一般在退出登陆时执行
配合 cookie 操作 session
app.use(session({
secret: "string key",
resave: false,
saveUninitialized: true,
cookie: { // 配置 cookie
maxAge: 1000 * 60 * 60 * 24 * 3, // 三天
secure: false // 是否仅 https 可用
}
}));
session & MongoDB
npm i express-session connect-mongo
const session = require("express-session");
const MongoStore = require("connect-mongo")
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: 'mongodb://localhost:27017/node' }) // 存入 MongoDB
}));
浏览器第一次向服务器发送请求时,服务器会建立一个 session 对象,用于存储用户信息
随后 服务器将 session-key 返回给客户端,客户端将 session-key 存储为 cookie
浏览器再次访问时,会带上 cookie (包含 session-key),通过 session-key 找到对应的 session 对象
如此,便可根据 session-key 获取对应的数据、执行对应的操作
以 key-value 的形式存储在浏览器中
只要不删除,数据就会永久存储
key-value 的 key 和 value 都只能是 [字符串]
对于 [数字] value,存储时会自动将其转换为字符串
对于 [对象]、[数组] value,我们可以使用 JSON.stringify()
进行转换
localStorage
操作 localStorageconsole.log(localStorage); // 打印 localStorage 对象
localStorage.setItem("name", "superman"); // 存数据
const name = localStorage.getItem("name"); // 取数据
localStorage.removeItem("key"); // 删除数据
localStorage.clear(); // 清空数据
null
JSON.parse(null)
返回 null
HTTP 协议的无状态性
客户端的 HTTP 请求都是独立的,请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态。
所以需要在每次请求时 带上用户数据对应的 id,服务器拿到 id 并获取对应的数据、执行对应的操作。
而让所有请求都带上指定数据的技术便是 cookie
cookie & session
客户端第 1 次请求服务器时,服务器会建立一个 session 对象,用于存储用户信息
随后 服务器向客户端返回一个身份认证的 cookie —— session-key,客户端会将该 cookie 保存在浏览器中
当客户端再次请求服务器时,浏览器会自动将当前域名下所有未过期的 cookie (当然也就包括 session-key) 以 [请求头] 的形式发送给服务器,服务器通过 session-key 找到对应的 session 对象,即可验证客户端身份、获取对应的数据、执行对应的操作
可知,用户数据都是存储在服务端的,浏览器存储 cookie 数据只是用户数据对应的 id → session-key
局限性
session 认证机制需要配合 cookie 才能实现
由于 cookie 默认不支持跨域访问,所以,当涉及前端跨域请求后端接口时,需要做额外的配置,才能实现跨域 session 认证
如果在特定时间有大量用户访问服务器的话,服务器可能就要存储大量 session-key
如果使用多台服务器:服务器之间存储的 session-key 需要共享
如果使用数据库存储 session-key:要是数据库奔溃,会影响服务器获取 session-key
JWT - JSON Web Token,是目前最流行的跨域认证解决方案
使用
Authorization: Bearer <token>
JWT 的组成部分
.
隔开Header.Payload.Signture
npm i jsonwebtoken express-jwt
const jwt = require("jsonwebtoken");
const expressJWT = require("express-jwt");
const secretKey = "superman token"; // 可以是任意字符串,越复杂安全性越好
sign()
方法,将用户的信息加密成 token 字符串,响应给客户端app.post("/api/login", (req, res) => {
// ...
res.send({
status: 200,
message: "login success!",
// 调用 sing() 方法,生成 token 字符串
token: jwt.sign(
{username: "superman"}, // Payload - 用户信息对象
secretKey, // 加密密钥
{algorithm: "HS256", expiresIn: "30s"} // Header - 配置加解密算法、有效期 (s-秒、h-小时)
);
});
});
req.user
/ req.auth
上// 使用 app.use() 注册中间件
app.use( expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }) );
// expressJWT({ secret: secretKey }) - 解析 token 的中间件
// .unless({ path: [/^\/api\//] }) - 指定不需要访问权限的接口
req.user
/ req.auth
获取解析后的用户信息app.get("/admin/getinfo", (req, res) => {
console.log(req.user);
res.send({
status: 200,
message: "获取用户信息成功!",
data: req.user // 将用户信息发送给客户端
});
});
app.use((err, req, res, next) => {
// token 解析失败导致的错误
if(err.name === "UnauthorizedError")
return res.send({ status: 401, message: "无效的 token" });
// 其他原因导致的错误
res.send({ status:500, message: "未知的错误" });
});