摘要:本文详细讲解了JWT无状态鉴权方案的实现过程,从原理到实战,手把手带你搭建一个完整的前端登录认证系统。通过Zustand管理全局状态,结合路由守卫和API封装,轻松实现用户权限控制。文末附有完整代码仓库,适合前端开发者深入学习现代Web应用的鉴权机制。
前言
在现代 Web 应用中,用户登录与权限管理是安全体系的基石。传统的 Session+Cookie 方案虽经典,但在前后端分离、移动端兴起的今天,JWT(JSON Web Token)已成为主流的无状态鉴权方案。本demo带你从原理到实战,彻底吃透JWT登录鉴权的每个环节。
项目结构:
mock/ # 模拟后端接口与数据
src/
├── api/ # API接口层
├── components/ # 公共组件
├── store/ # 全局状态管理
├── view/ # 页面组件
└── main.jsx # 应用入口
全局状态管理:isLogin 与 user
在前端的开发中,用户是否已经登录,以及用户的详细信息(比如说用户名、用户的ID、用户的权限等级等等),通常需要在整个应用的各个页面、各个组件之间都能随时获取和使用。比如:访问个人信息时需要判断是否登录,多页面导航栏显示用户昵称,某些按钮的控制或功能是否可见等。
如果每个页面、每个组件都各自保存一份用户信息,就会导致数据的混乱且难以维护,所以说,需要一个全局的地方来统一存储和管理这些信息,这样就可以实现无论在哪个页面、哪个组件,都可以方便地获取和修改用户的登录状态和信息。
常见的全局状态管理方式有:
- React的Context
- Redux
- Zustand
- Vuex(Vue项目)
本demo采用的轻量级的Zustand(详细可见往期文章Zustand)来全局地共享状态和对状态的管理。
全局状态管理代码:
// user.js
import { create } from "zustand";
import { doLogin } from "../api/user";
export const useUserStore = create((set) => ({
user: null, // 用户信息
isLogin: false, // 是否登录
login: async ({ username = "", password = "" }) => {
const res = await doLogin({ username, password });
const { token, data: user } = res.data;
localStorage.setItem("token", token);
set({
user,
isLogin: true,
});
},
logout: () => {
localStorage.removeItem("token");
set({
user: null,
isLogin: false,
});
},
}));
我们在store.js文件中定义一个useUserStore的hook,本demo只定义了user与isLogin两个状态还有login与logout两种方法。
login方法内传入username和password去请求登录接口,把登录成功后获得的token(令牌)保存到浏览器的本地存储中,然后修改全局状态。
logout方法即是退出登录移除token并修改全局状态。
比如:登录成功后,isLogin 设为 true,user 存储用户对象(如 {id:112, username:"LeeAt67", level:"4"})。
退出登录时,重置状态。
控制台输出isLogin与user对象
本地存储:
Mock登录与API模拟
在本demo中,后端接口没有开发,使用Apifox来模拟API请求,在login.js定义了模拟登录接口(/api/login)与模拟获取用户信息接口(/api/user)来快速验证前端逻辑。
import jwt from "jsonwebtoken";
const { sign } = jwt;
const secret = "!&1244jajf";
export default [
{
url: "/api/login",
method: "post",
timeout: 2000, // 模拟请求耗时
response: (req, res) => {
const { username, password } = req.body;
if (username !== "admin" || password !== "123456") {
return {
code: 1,
message: "用户名或密码错误",
};
}
// json 用户数据
const token = sign(
{
user: {
id: "001",
username: "admin",
},
},
secret,
{ expiresIn: 86400 }
);
console.log(token, "-----");
// 生成token 颁发令牌
return {
token,
data: {
id: "001",
username: "admin",
},
};
},
},
{
url: "/api/user",
method: "get",
response: (req, res) => {
// 用户端 token headers
const token = req.headers["authorization"].split(" ")[1];
console.log(token);
try {
const decode = jwt.decode(token, secret);
console.log(decode);
return {
code: 0,
data: decode.user,
message: "获取用户信息成功",
};
} catch (err) {
return {
code: 1,
message: "Invalid token",
};
}
return {
token,
};
},
},
];
使用了Apifox来模拟POST请求,成功获取到了token与data。
接着使用GET请求并在请求头中带上Authorization确实获取到了用户的信息。
会话授权原理
Session 机制
HTTP协议本身是无状态的,服务器无法主动记住每个客户端身份。
传统做法是:用户登录后,服务器端生成一个唯一的Session ID(简称sid),并通过HTTP响应头的 Set-Cookie 字段,把sid写入浏览器Cookie。之后每次请求都会自动携带这个Cookie(包含sid),服务器端收到这个请求后,通过sid查找到对应的用户信息,从而记住用户身份。
JWT 方案
JWT方案(JSON Web Token)是一种无状态的认证机制,和Session机制的最大区别就在于:服务端不会保存会话数据,用户身份信息全部存储在Token(令牌)中,由客户端保存和传递。
JWT具体流程:
登陆时, 用提交用户名和密码,服务器验证通过后,生成一个包含用户信息的JWT(用密钥加密签名),返回给客户端。客户端保存JSW,通常存储在localStorage、sessionStorage或Cookie中。在后续请求的时候,客户端在请求头(通常是Authorization:Bearer <token>)中携带JWT,服务器验证JWT,通过密钥解密和校验签名,确认用户身份,无需查数据库或内存保护的会话信息。
如下图,控制台输出生成的加密用户信息的JSON字符串JWT。
jsonwebtoken库
安装jwt库:
pnpm i jwt
jwt用法:
import jwt from "jsonwebtoken";
const{sign} = jwt;
const secret = "!&1244jajf"; // 密钥
// 颁发 token
const token = sign(user, secret,expiresIn);
// 校验 token
const user = verify(token, secret);
// 解码
const decode = decode(token,secret)
将包含用户信息的JS对象user,密钥以及有效期expiresIn传给sign进行序列化,生成字符串类型的Token,后面再进行校验。
JWT签名时,其中的secret相当于“盐”,这个加密的过程就是熟知的加盐,主要就是指对敏感数据(比如密码)进行加密或哈希处理的时候,额外添加一段随机的数据(称为“盐”),来增强安全性。
前端的用户权限状态
在路由跳转前后,利用路由守卫来拦截并处理页面的访问权限、数据校验等逻辑,此外用封装统一的API请求方法,将token自动携带上。
路由
// App.jsx
function App() {
return (
<>
{/* 懒加载 */}
<Suspense fallback={<div>Loading...</div>}>
<NavBar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route
path="/pay"
element={
<RequireAuth>
<Pay />
</RequireAuth>
}
/>
</Routes>
</Suspense>
</>
);
}
路由守卫
对需要保护的Pay页面封装一个路由守卫,来处理页面访问。
<Route
path="/pay"
element={
<RequireAuth>
<Pay />
</RequireAuth>
}
/>
API封装
在配置模块,引入了axios库来发送请求,设置基础URL,再配置了请求和响应拦截器来处理每次的请求与响应,自动从localStorage里取出token(如果有),并加到请求头的Authorization中和统一处理响应。
// config.js
import axios from "axios";
axios.defaults.baseURL = "http://localhost:5173/api";
axios.interceptors.request.use((config) => {
const token = localStorage.getItem("token") || "";
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config;
});
axios.interceptors.response.use((res) => {
return res;
});
export default axios;
总结
本文详细介绍了JWT登录鉴权的完整实现流程,从理论到实践全面覆盖:
- 全局状态管理:使用轻量级的Zustand管理用户登录状态和信息
- Mock接口实现:通过模拟后端API快速验证前端逻辑
- JWT原理解析:对比传统Session机制,深入理解JWT的无状态特性
- 前端权限控制:结合路由守卫和API请求拦截器实现完整的权限管理
Github直通demo仓库:JWT-Demo
参考: