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 } //模擬用戶賬號密碼和idapp.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) => { //驗證tokenif (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>