jwt(json web token)
用于鉴权,登录之后存储用户信息
生成的 token(令牌) 格式
三段,每段由.连接
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjg3Njc0NDkyLCJleHAiOjE2ODc3NjA4OTJ9.Y6eFGv4KXqUhlRHglGCESvcJEnyMkMwM1WfICt8xYC4
组成
header 通常用 json 对象表示,并进行 Base64 URL 编码
1 2 3 4
   | {   "alg": "HS256",   "typ": "JWT" }
 
  | 
 
alg:代表所使用的签名算法,例如 HMAC SHA256(HS256)或 RSA 等
typ:代表令牌的类型,一般为 “JWT”。
payload 载荷
payload 也是一个 JSON 对象,同样进行 Base64 URL 编码。
负载包含所要传输的信息,例如用户的身份、权限等,但不是密码这种关键信息。
1 2 3 4 5 6
   | {   "iss": "example.com",   "exp": 1624645200,   "sub": "1234567890",   "username": "johndoe" }
 
  | 
 
iss:令牌颁发者(Issuer),代表该 JWT 的签发者。
exp:过期时间(Expiration Time),代表该 JWT 的过期时间,以 Unix 时间戳表示。
sub:主题(Subject),代表该 JWT 所面向的用户(一般是用户的唯一标识)。
自定义声明:可以添加除了预定义声明之外的任意其他声明。
signature 签名
签名是使用私钥对头部和负载进行加密的结果
用于验证令牌的完整性和真实性
1 2 3 4 5
   | HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey )
 
  | 
 
服务端 Express 实现 JWT
使用 jsonwebtoken.sign(infoObj, private_key_str, {expiresIn: ‘1h’})来生成 token
使用 const token = req.headers.authorization || ‘’;从请求头中读 token
如果无法通过 token 验证,返回状态码 401
1
   | pnpm i express cors jsonwebtoken @types/express @types/cors @types/jsonwebtoken
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   | import express from 'express'; import cors from 'cors'; import jsonwebtoken from 'jsonwebtoken'; const app = express();
  const private_key = '123456';
 
  app.use(express.json());
  app.use(cors());
  app.use(express.urlencoded({ extended: false })); let user = {   id: 1,   name: 'admin',   password: '123456', };
  app.post('/login', (req, res) => {   if (req.body.name === user.name && req.body.password === user.password) {     res.json({       code: 0,       token: jsonwebtoken.sign({ id: user.id }, private_key, {         expiresIn: '1h',       }),      });   } else {     res.status(401).json({       code: 1,       message: '用户名或密码错误',     });   } });
  app.get('/list', (req, res) => {      const token = req.headers.authorization || '';   jsonwebtoken.verify(token, private_key, (err, decoded) => {     if (err) {       res.status(401).json({         code: 1,         message: 'token过期或无效',       });     } else {       res.json({         code: 0,         data: [1, 2, 3, 4, 5],       });     }   }); }); app.listen(3000, () => {   console.log('server is running at http://localhost:3000'); });
 
  | 
 
用户端 fetch
将 token 保存在 localStorage 中,同域的所有窗口都可以获取,浏览器关闭后 item 依然存在
location.href = ‘./list.html’;实现原生页面跳转
一般来说 token 前面会加上’Bearer ‘前缀,这是一个 w3c 约定的规范,具体实施起来要前后台协商
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | loginBtn?.addEventListener('click', () => {   fetch('http://localhost:3000/login', {     method: 'POST',     headers: {       'Content-Type': 'application/json',     },     body: JSON.stringify({       name: (nameInput as HTMLInputElement).value,       password: (pwdInput as HTMLInputElement).value,     }),   })     .then((res) => res.json())     .then((d) => {       if (d.code === 0) {         sessionStorage.setItem('tk', d.token);         (nameInput as HTMLInputElement).value = '';         (pwdInput as HTMLInputElement).value = '';                           location.href = './list.html';       }     })     .catch((e) => {       console.log(e);     }); });
 
  |