JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案
作用:
主要是做鉴权用的登录之后存储用户信息
生成得token(令牌)如下
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjg3Njc0NDkyLCJleHAiOjE2ODc3NjA4OTJ9.Y6eFGv4KXqUhlRHglGCESvcJEnyMkMwM1WfICt8xYC4
JWT的组成部分:
Header(头部): token(令牌)的类型(即 “JWT”)和所使用的签名算法。头部通常采用 JSON 对象表示,并进行 Base64 URL 编码。
- 组成部分:
- alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);
- typ属性表示这个令牌(token)的类型(type)
{
"alg": "HS256",
"typ": "JWT"
}
Payload(负载): Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。例如用户的身份、权限等。负载也是一个 JSON 对象,同样进行 Base64 URL 编码。
{
"exp": 1024945200,
"sub": "1234567890",
"username": "Tom"
}
JWT 规定了7个官方字段,供选用,具体如下:
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
Signature(签名): Signature 部分是对前两部分的签名,防止数据篡改,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),用大白话来说就是:签名是使用私钥对头部和负载进行加密的结果。它用于验证令牌的完整性和真实性。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
mySetKey
)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
搭建一个后端项目:
使用express进行搭建
- 安装
pnpm i express
pnpm i jsonwebtoken
pnpm i cors
import express from 'express';
import jwt from 'jsonwebtoken';//引入JWT的包
import cors from 'cors';//防止跨域
const app = express();
const mySetKey= 'mycode' //密钥
app.use(express.urlencoded({ extended: false }));// URL 处理编码问题
app.use(express.json());// JSON处理格式数据的中间件
app.use(cors())
let user = { name: 'admin', password: '123456', id: 1 } //模拟用户账号密码和id
app.post('/api/login', (req, res) => {//登录接口
console.log(req.body)
//判断客户端传入的和数据库存储的是否一致
if (req.body.name == user.name && req.body.password == user.password) {
res.json({
message: '登录成功',
code: 200,
token: jwt.sign({ id: user.id }, mySetKey, { expiresIn: 60 * 60 * 24 }) // jwt.sign使用JWT根据用户id和密钥 生成token mySetKey密钥 expiresIn设置失效时间
})
} else {
res.json({
message: '登录失败',
code: 400
})
}
})
// /api/list 验证密钥是否失效 没失效则返回对应的数据给客户端
app.get('/api/list', (req, res) => {
console.log(req.headers.authorization)
// JWT 根据mySetKey秘钥验证token的有效性
jwt.verify(req.headers.authorization as string, mySetKey, (err, data) => { //验证token
if (err) {
res.json({
message: 'token失效',
code: 403
})
} else {
res.json({
message: '获取列表成功',
code: 200,
data: [
{ name: '张三', age: 18 },
{ name: '李四', age: 20 },
]
})
}
})
})
app.listen(3000, () => {
console.log('server is running 3000');
})
前端代码
在前端文件中新建两个文件,分别是index.html和listPage.html
index.html
<!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.querySelector('#btn')
const name = document.querySelector('#name')
const password = document.querySelector('#password')
btn.onclick = () => {
fetch('http://localhost:3000/api/login', {
body: JSON.stringify({
name: name.value,
password: password.value
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
}).then(res => res.json()).then(res => {
localStorage.setItem('token', res.token)
location.href = './listPage.html'
})
}
</script>
</body>
</html>
listPage.html 如果没有token就访问不了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>List</title>
</head>
<body>
<script>
console.log(localStorage.getItem('token'))
fetch('http://localhost:3000/api/list', {
headers: {
'Authorization':`Bearer ${localStorage.getItem('token')}`
}
}).then(res => res.json()).then(res => {
console.log(res)
})
</script>
</body>
</html>