JWT 5:路由守卫是什么?把整个 JWT 登录鉴权流程串起来

现在最后还有一个问题:

即使我们已经有:

text 复制代码
token
user

用户仍然可以手动输入:

text 复制代码
/pay

直接访问 Pay 页面。

所以我们还需要解决:

未登录用户不能进入受保护页面。

这就是:

路由守卫。


一、为什么需要路由守卫?

假设:

text 复制代码
/pay

是只有登录用户才能访问的页面。

那么我们希望:

text 复制代码
已登录   → 可以访问 /pay
未登录   → 跳转 /login

这个判断不属于 Pay 页面自己的业务。

所以我们创建一个专门的组件:

text 复制代码
src/components/RequireAuth.jsx

它的任务很简单:

保护页面。


二、创建 RequireAuth

代码:

jsx 复制代码
import { Navigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '../store/user';

function RequireAuth({ children }) {
  const token = useAuthStore(state => state.token);
  const location = useLocation();

  if (!token) {
    return (
      <Navigate
        to="/login"
        state={{ from: location.pathname }}
        replace
      />
    );
  }

  return children;
}

export default RequireAuth;

这里最核心的判断:

js 复制代码
if (!token)

意思就是:

没有登录凭证。

那怎么办?

jsx 复制代码
<Navigate to="/login" />

直接去登录页。


三、为什么把 Pay 包在 RequireAuth 里面?

路由中:

jsx 复制代码
<Route
  path="/pay"
  element={
    <RequireAuth>
      <Pay />
    </RequireAuth>
  }
/>

这里可以理解成:

text 复制代码
访问 /pay
        ↓
先经过 RequireAuth
        ↓
检查 token
        ↓
有 token → Pay
没有 token → Login

所以 RequireAuth 就像一个门。

Pay 页面是门后面的内容。


四、为什么不直接把判断写到 Pay.jsx?

当然也可以。

但是如果以后:

text 复制代码
Pay
Order
Profile
Admin
Settings

都需要登录。

难道每一个页面都写:

js 复制代码
if (!token) {
  navigate('/login');
}

这样会重复大量代码。

所以我们把这个公共逻辑抽成:

text 复制代码
RequireAuth

以后所有需要保护的页面都可以复用。


五、登录后为什么要回到原来的页面?

假设用户本来访问:

text 复制代码
/pay

但是没登录。

于是:

text 复制代码
/pay
 ↓
/login

如果登录成功以后永远跳到:

text 复制代码
/

用户就会感觉:

"我刚才明明想去 Pay,怎么登录完跑首页去了?"

所以 RequireAuth 里记录了:

jsx 复制代码
state={{ from: location.pathname }}

意思是:

告诉登录页面,我之前是从哪里来的。


六、Login 页面读取 from

登录页面:

js 复制代码
const location = useLocation();

const from = location.state?.from || '/';

如果用户之前访问的是:

text 复制代码
/pay

那么:

js 复制代码
from === '/pay'

如果没有记录来源:

js 复制代码
from === '/'

七、登录成功以后跳回去

登录成功:

js 复制代码
const res = await login(formData);

然后:

js 复制代码
if (res.code === 0) {
  setAuth({
    token: res.token,
    user: res.user
  });

  navigate(from, {
    replace: true
  });
}

于是:

text 复制代码
用户访问 /pay
        ↓
RequireAuth 检查 token
        ↓
没有 token
        ↓
跳到 /login
        ↓
保存 from = /pay
        ↓
用户完成登录
        ↓
服务器返回 Token
        ↓
setAuth()
        ↓
navigate('/pay')
        ↓
进入 Pay

这就形成了完整的登录跳转体验。


八、现在终于可以把整个项目串起来

到这里,这个 demo 的所有主要部分都已经出现了:

text 复制代码
用户访问受保护页面
        ↓
RequireAuth 检查 Zustand 中的 token
        ↓
没有 token → 跳转登录页
        ↓
用户填写账号密码
        ↓
Login 调用 user.js 的 login()
        ↓
Axios 发送 /api/login
        ↓
Mock 后端验证账号密码
        ↓
jwt.sign() 生成 Token
        ↓
Token 返回前端
        ↓
Zustand 保存 token 和 user
        ↓
同时保存到 localStorage
        ↓
后续 Axios 请求自动添加 Authorization
        ↓
Mock 后端读取 Bearer Token
        ↓
jwt.verify() 验证 Token
        ↓
验证成功 → 返回受保护数据

这条链就是整个 demo 的核心。


九、最后再区分几个非常容易混淆的东西

这个 demo 里出现了很多概念,刚学的时候很容易全部混到一起。

API 层

例如:

text 复制代码
src/api/user.js
src/api/repo.js

解决的是:

前端怎么组织接口请求。


Mock

例如:

text 复制代码
mock/user.js

解决的是:

开发阶段没有真实后端时,谁来模拟后端接口。


JWT

例如:

js 复制代码
jwt.sign()
jwt.verify()

解决的是:

服务器怎么给用户签发身份凭证,以及怎么验证这个凭证。


Axios 拦截器

例如:

js 复制代码
instance.interceptors.request.use(...)

解决的是:

怎么让每次请求自动携带 Token,而不是一个接口一个接口地写。


Zustand

例如:

js 复制代码
useAuthStore(...)

解决的是:

React 应用内部怎么共享当前登录状态。


localStorage

解决的是:

刷新页面之后,Token 仍然能够被找回来。


RequireAuth

解决的是:

没有登录的用户能不能进入某个页面。


最后:你真正应该记住的不是五篇文章

这个 demo 如果只记 API 名字,很快就会乱。

真正应该记住的是下面这几个"为什么":

为什么需要 API 层?

因为不能让每个 React 页面自己管理请求细节。

为什么需要 Mock?

因为前端开发时,需要一个可以模拟后端的环境。

为什么需要 Token?

因为 HTTP 是无状态的,服务器需要一个凭证知道"你是谁"。

为什么需要 Axios 拦截器?

因为每个请求都需要 Token,公共逻辑应该统一处理。

为什么需要 Zustand?

因为多个 React 组件都需要知道当前用户是谁,登录状态属于全局状态。

为什么需要 localStorage?

因为 React 内存状态刷新后会丢,需要一个持久化的位置保存 Token。

为什么需要 RequireAuth?

因为"有没有 Token"不仅影响请求,也影响用户能不能进入某个页面。

最终,你应该能够自己说出这一句话:

用户登录成功后,服务器使用 JWT 签发 Token,前端把 Token 保存下来并放进登录状态 Store;之后 Axios 拦截器会在请求发送前自动把 Token 放进 Authorization Header,服务器收到请求后通过 jwt.verify() 验证身份,而 React Router 再利用全局登录状态决定用户能不能访问受保护页面。

这句话如果你能真正讲明白,这个 demo 的主线基本就已经吃透了。

相关推荐
东风破_1 小时前
JWT 3:为什么 Token 要放进 Authorization?Axios 拦截器到底解决了什么?
前端·后端
东风破_1 小时前
JWT 2:HTTP 是无状态的,为什么登录成功后还要给 Token?
前端·后端
东风破_1 小时前
JWT 4:Zustand 到底解决了什么?为什么登录状态要放进 Store?
前端·后端
IT_陈寒1 小时前
Vite动态导入差点让我秃头,原来问题出在这
前端·人工智能·后端
白泽_hunter2 小时前
项目里最常见的 5 个 this 指向坑:从规则到实战彻底讲透
前端
宿6742 小时前
vue3-pinia
前端·vue.js
白泽_hunter2 小时前
搞懂 JS 数据类型:从 typeof 到手写完整类型判断,一张图理清
前端
唐青枫2 小时前
别只把大括号当作用域:Zig Block、标签块与控制流实战
后端
日光倾2 小时前
TypeScript 随手记 —— 1
前端·javascript·typescript