JWT

全称JSON Web Token,用于鉴权,体现为登录后存储用户信息

这是一段生成的token(令牌):

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjg3Njc0NDkyLCJleHAiOjE2ODc3NjA4OTJ9.Y6eFGv4KXqUhlRHglGCESvcJEnyMkMwM1WfICt8xYC4

JWT由三部分组成:头部、负载、签名

  • 头部 (Header):通常由令牌的类型和所使用的签名算法两部分组成,通常采用JSON对象表示,并进行Base64 URL编码

    1
    2
    3
    4
    {
    "alg": "HS256",
    "typ": "JWT"
    }
    • alg:所使用的签名算法,例如HMAC、SMA256 (HS256)、RSA等
    • typ:令牌的类型,一般为JWT
  • 负载 (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

后端

安装依赖:

1
npm i @types/express express cors 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
56
57
58
59
60
61
62
63
64
65
66
67
68
import express from 'express'
import jwt from 'jsonwebtoken'
import cors from 'cors'

let app = express()
// 支持json
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
// 跨域
app.use(cors())

// 私钥(盐值)
// 一般为了安全,会把key藏在环境变量或文件内
let Key = 'yjsp'

// mock一个用户
let user = {
name: 'yajue',
password: 114514,
id: 1919810
}

// 登录,给前端返回token用于授权
app.post('/api/login', (req, res) => {
if (req.body.name === user.name && req.body.password === user.password) {
// 验证成功,返回token
res.json({
message: '登录成功',
// 参数1:负载 参数2:私钥(加盐) 参数3:有效期
token: jwt.sign({ id: user.id }, Key, { expiresIn: '1h' })
})
} else {
// 验证失败,返回403
res.status(403).json({ message: '用户名或密码错误' })
}
})

// 授权状态时返回列表,否则403
app.get('/api/list', (req, res) => {
const token = req.headers.authorization // 前端会把token放入这个请求头,这是个规范
if (!token) {
res.status(403).json({ message: '无权限' })
} else {
// 如果请求头加上了Bearer,记得分割字符串
const token2 = token.split(' ')[1]
// token:被验证的token key:私钥(盐值) 函数:验证成功or失败的返回
// 也可以把这段验证写进中间件里进行全局验证并配以白名单
jwt.verify(token2, Key, (err, decode) => {
if (err) {
res.status(403).json({ message: '无权限' })
} else {
res.json({
list: [
{ id: 1, name: 'aaa' },
{ id: 2, name: 'bbb' },
{ id: 3, name: 'ccc' },
{ id: 4, name: 'ddd' },
{ id: 5, name: 'eee' }
]
})
}
})
}
})

app.listen(3000, () => {
console.log('server started')
})

前端

index.html:

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<div>
<div>
<span>账号</span><input id="name" type="text">
</div>
<div>
<span>账号</span><input id="password" type="password">
</div>
<button id="btn"></button>
</div>
<script>
const btn = document.getElementById('btn')
const name = document.getElementById('name')
const password = document.getElementById('password')

btn.onclick = ()=> {
fetch('http://localhost:3000/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name.value,
password: password.value
})
}).then(res=> res.json()).then(res=> {
console.log(res)
// 保存token
localStorage.setItem('token', res.token)
location.href = './list.html'
})
}
</script>
</body>
</html>

list.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 和index.html同域,所以在这个页面也能访问到token -->
<script>
// 请求list接口
fetch('http://localhost:3000/api/list', {
headers: {
// 请求头中携带token,不一定非要加上Bearer ,它是个规范(别的名字也可以)
'Authorization': `Bearer ${localStorage.getItem("token")}`
}
}).then(res=> res.json()).then(res=> {
console.log(res)
})
</script>
</body>
</html>