深入理解 JWT 登录鉴权:从前端到模拟后端的完整实现

402 阅读5分钟

在今天的项目中,我实现了一个完整的 JWT(JSON Web Token)登录鉴权系统,涵盖了从状态管理到路由守卫的全流程。我们要实现实现一个如图的效果,当我们点击Pay,我们没有处于已登录的状态,我们会直接跳转到登录页面,默认用户名和密码为“admin”,“123456”,当我们登录成功后,我们的本地会存储我们的用户信息,包括用户名和密码,当然是经过加密的,作为身份验证:

1.gif

以下是核心内容的技术总结:

一、JWT 鉴权核心原理

JWT 解决了 HTTP 无状态协议下的用户认证问题,其工作流程如下:

  1. 登录认证:用户提交凭证 → 服务端验证 → 生成含用户信息的 JWT
  2. 令牌返回:服务端返回 JWT 给客户端(存储于 localStorage)
  3. 请求鉴权:客户端在请求头添加 Authorization: Bearer <token>
  4. 服务端验证:解析 JWT 获取用户信息 → 响应请求

我们曾经使用过cookie来做身份认证,但是cookie是明文的,不安全,容易被窃取,安全隐患大,我写过一篇关于cookie的文章,感兴趣的可以看看 当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊

二、关键实现模块详解

1. Mock 服务实现(login.js)

当然要使用jwt,我们首先得先安装 pnpm i jsonwebtoken

import jwt from 'jsonwebtoken'

// login 模块 mock 

// 安全性 编码的时候加密
// 解码的时候用于解码
// 加盐
const secret = '!&124coddefgg'

export default [
    {
        url: '/api/login',
        method: 'post',
        timeout: 2000, // 请求耗时
        response: (req, res) => {
            // req, username, password
            const { username, password } = req.body;
            if (username !== 'admin' || password !== '123456') {
                return {
                    code: 1,
                    message: '用户名或密码错误'
                }
            }
            // 生成token 颁发一个令牌
            // json 用户数据
            const token = jwt.sign({
                user: {
                    id: "001",
                    username: "admin",
                }
            }, secret, {
                expiresIn: 86400, // 有效期24小时
            })
            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.verify(token, secret)
                console.log(decode);
                return {
                    code: 0,
                    data: decode.user
                }
            } catch (err) {
                return {
                    code: 1,
                    message: 'Invalid Token'
                }
            }
            return {
                token
            }
        }

    }
]
  • 安全要点

    • 使用 secret 密钥签名防止篡改
    • expiresIn 设置令牌有效期增强安全性
    • 验证时严格捕获异常防止服务崩溃

我们用apifox 来测试一下是否可以成功得拿到数据,测试的地址就是你的react项目端口,加上我们设置的url,可以看到成功返回了数据,返回的经过加盐的token

image.png

科普时间

我们上述提到一个加盐的概念,什么是加盐呢?加盐是一种增加额外数据到密码或数据输入的技术,目的是保护数据不被通过预计算的攻击方法轻易破解。具体来说,盐值是一个随机生成的数据片段,它被添加到密码或其他敏感数据中,然后再进行哈希处理。

我们再来试试模拟服务器来进行解密呢,可以看到也是能成功解密的:

image.png

const token = req.headers['authorization'].split(' ')[1]大家可能对这段代码有点疑惑,当我们带着带着身份信息,这通常存放在Authorization:'Bearer ${token}' Bearer表示持有者,通常都会加上,所以我们在解码token的时候需要以空格分割

2. Axios 全局配置(/api/config.js)

模块化axios,不需要频繁的设置基础地址和token等

// 请求拦截器:自动注入Token
import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:5173/api'

// 请求拦截器
axios.interceptors.request.use(config => {
    // console.log('////');
    let token = localStorage.getItem('token') || '';
    if (token) config.headers.Authorization = `Bearer ${token}`
    // config.headers.Authorization = token
    return config
})

// 响应拦截器
axios.interceptors.response.use(res => {
    console.log('???');
    return res
})

export default axios

模块后的请求方法

import axios from './config'

export const getUser = async () => {
    return await axios.get('/user')
}

export const doLogin = async (data) => {
    return await axios.post('/login', data)
}
  • 设计亮点

    • 统一处理认证逻辑,避免重复代码
    • Bearer 前缀符合 OAuth 2.0 规范
    • 基础 URL 集中配置便于环境切换

3. Zustand 状态管理(user.js)

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 })
  }
}))
  • 状态设计

    • user:存储用户信息对象
    • isLogin:布尔值登录状态
    • 登录/登出操作封装为原子方法

三、前端组件实现技巧

1. 路由守卫(/components/RequireAuth.jsx)

const RequireAuth = ({ children }) => {
  const { isLogin } = useUserStore()
  const navigate = useNavigate()
  const { pathname } = useLocation()

  useEffect(() => {
    if (!isLogin) {
      navigate('/login', { state: { from: pathname } }) // 记录来源页面
    }
  }, [])

  return <>{children}</>
}
  • 实现要点

    • 在 useEffect 中执行跳转避免渲染阻塞
    • 传递来源路径实现登录后自动回跳
    • 纯组件设计不影响子组件渲染

2. 登录表单(/view/Login/index.jsx)

const Login = () => {
  const usernameRef = useRef()
  const passwordRef = useRef()
  const { login } = useUserStore()
  const navigate = useNavigate()

  const handleLogin = (e) => {
    e.preventDefault()
    const username = usernameRef.current.value
    const password = passwordRef.current.value
    
    if (!username || !password) {
      return alert('请输入用户名和密码')
    }
    
    login({ username, password })
    setTimeout(() => navigate('/'), 1000) // 登录成功跳转
  }

  return (
    <form onSubmit={handleLogin}>
      {/* 表单内容 */}
    </form>
  )
}
  • 优化点

    • useRef 替代 useState 减少渲染次数
    • 表单提交触发状态管理方法
    • 定时跳转提供视觉反馈时间

3. 导航栏(/view/NavBar/index.jsx)

const NavBar = () => {
  const { isLogin, user, logout } = useUserStore()
  
  return (
    <nav>
      <Link to='/'>Home</Link>
      <Link to='/pay'>Pay</Link>
      
      {isLogin ? (
        <>
          <span>欢迎您,{user.username}</span>
          <button onClick={logout}>退出登录</button>
        </>
      ) : (
        <Link to='/login'>登录</Link>
      )}
    </nav>
  )
}
  • 状态绑定

    • 根据 isLogin 动态渲染内容
    • 直接调用 logout 方法实现状态同步清除

四、核心流程剖析

  1. 初始化应用

    • 检查 localStorage 是否存在 token
    • 有 token 则设置 isLogin=true 并获取用户信息
  2. 登录流程

image.png

3.受保护路由访问

image.png

4.API 请求鉴权

// 请求拦截器自动注入
GET /api/user
Headers: 
  Authorization: Bearer eyJhbGciOi...

总结

通过本次实践,我深入理解了 JWT 鉴权在前端应用中的完整实现链路,掌握了 Zustand 状态管理与 axios 拦截器的配合技巧。关键收获:

  1. 认证与状态分离:JWT 提供无状态认证,前端状态管理负责用户体验
  2. 拦截器威力:极大减少重复代码,实现关注点分离
  3. 路由守卫模式:提供简洁的权限控制方案
  4. Mock API 价值:前后端并行开发的关键基础设施

本项目完整代码已托管至 GitHub 仓库,包含详细注释和优化建议。