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); }); });
|