JWT(鉴权)

jwt(json web token)

用于鉴权,登录之后存储用户信息

生成的 token(令牌) 格式

三段,每段由.连接

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjg3Njc0NDkyLCJleHAiOjE2ODc3NjA4OTJ9.Y6eFGv4KXqUhlRHglGCESvcJEnyMkMwM1WfICt8xYC4

组成

header 头部

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();
// 一般来说private key是在环境变量中配置,或者在一个单独的文件中
const private_key = '123456';

// 支持解析json
app.use(express.json());
// 解决跨域
app.use(cors());
// 不支持解析urlencoded
app.use(express.urlencoded({ extended: false }));
let user = {
id: 1,
name: 'admin',
password: '123456',
};
// 1.登陆返回前端token用于授权
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',
}), // token过期时间为1h,后续可以修改为配置文件读取,失效之后要重新登陆授权
});
} else {
res.status(401).json({
code: 1,
message: '用户名或密码错误',
});
}
});
// 2.只有token通过认证才能访问list,否则403
app.get('/list', (req, res) => {
// 前端会把token放在请求头中 这是一个规范
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 = '';
// 原生页面跳转
// localStorage支持同域访问
location.href = './list.html';
}
})
.catch((e) => {
console.log(e);
});
});

JWT(鉴权)
https://hugtyftg.github.io/2024/02/01/JWT/
作者
mmy@hugtyftg
发布于
2024年2月1日
许可协议