hello 大家好,我是 superZidan,这篇文章想跟大家聊聊 在React Router 中使用 JWT ,如果大家遇到任何问题,欢迎 联系我 或者直接微信添加 superZidan41
在这篇文章中,我们将探讨 JWT 身份校验与 React 和 React-router 的无缝集成。 我们还将学习如何处理公共路由、受校验保护路由,以及如何利用 axios 库通过身份验证令牌(token)发出 API 请求。
创建一个 React 项目
使用下方的指令会为我们创建一个项目
$ npm create vite@latest react-jwt-cn
然后我们选择 react 和 javascript 作为我们的框架和语言。在项目开始之前,我们要确保所有的依赖都已经被安装,所以我们要先执行
$ npm install
安装完毕后,在项目的根目录下,我们可以运行下面的指令来启动我们的项目
$ npm run dev
我们通过这些步骤来让我们的 React 项目顺利启动和运行
安装 React-Router 和 Axios
在我们继续之前,要确保我们已经为我们的项目安装了必要的依赖项。 我们将从安装 react-router v6 开始,它将处理我们的 React 应用程序中的路由。 此外,我们将安装 Axios,这是一个用于发送 API 请求的库。 通过执行这些步骤,我们将配备实现无缝路由和执行高效 API 通信所需的工具。 让我们从安装这些依赖项开始。
$ npm install react-router-dom axios
在 React 中创建 AuthProvider 和 AuthContext
接下来我们要实现的就是 JWT 身份验证的功能。在这个小节中我们将创建一个 AuthProvider 组件和一个关联的 AuthContext 。这将协助我们在整个应用中存储和共享 JWT 身份验证相关的数据和函数
在 src > provider 下创建 authProvider.js 。然后我们来探 AuthProvider 和 AuthContext 的实现
导入必要的模块和依赖包:
- 导入
axios用于发送 API 请求 - 从
react导入createContextuseContextuseEffectuseMemo以及useState
- 导入
import axios from "axios";
import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";使用
createContext()来创建一个用于身份验证的上下文createContext()创建的空的上下文是用于在组件之间共享身份验证的数据和函数的
const AuthContext = createContext();创建 AuthProvider 组件
- 这个组件是用于作为身份验证上下文 的 provider
- 它接收 children 作为 prop,代表将有权访问身份验证上下文的子组件。
const AuthProvider = ({ children }) => {
// 组件内容写在这里
};使用
useState定义一个名为token的 statetoken代表的是身份验证的令牌- 如果令牌数据存在的话,我们将通过
localStorage.getItem("token")来获取它
const [token, setToken_] = useState(localStorage.getItem("token"));创建
setToken函数来更新身份验证的令牌数据- 这个函数将会用于更新身份验证的令牌
- 它使用
setToken_函数更新令牌数据并且将更新之后的数据通过localStorage.setItem()存储在本地环境
const setToken = (newToken) => {
setToken_(newToken);
};使用
useEffect()来设置 axios 默认的身份验证请求头并且将身份验证的令牌数据保存到本地- 每当
token更新, 这个 effect 函数都会执行 - 如果
token存在,它将被设置为 axios 的请求头并且保存到本地 localStorage 中 - 如果
token是 null 或者 undefined ,它将移除对应的 axios 请求头以及本地身份验证相关的 localStorage 的数据
- 每当
useEffect(() => {
if (token) {
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
localStorage.setItem('token',token);
} else {
delete axios.defaults.headers.common["Authorization"];
localStorage.removeItem('token')
}
}, [token]);使用
useMemo创建记忆化的上下文- 这个上下文包含
token和setToken函数 - token 的值会被作为记忆化的依赖项(如果 token 不变,则不会重新渲染)
- 这个上下文包含
const contextValue = useMemo(
() => ({
token,
setToken,
}),
[token]
);给自组件注入身份验证的上下文
- 使用
AuthContext.Provider包裹子组件 - 把 contextValue 作为 provider 的值传入
- 使用
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);导出 useAuth 这个 hook ,以供外部使用到身份验证这个 context
- useAuth 是一个自定义的 hook,它可以让子组件很方便的访问到身份验证信息
export const useAuth = () => {
return useContext(AuthContext);
};- 默认导出 AuthProvider
export default AuthProvider;完整代码
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";- 定义
ProtectedRoute组件,让它包裹我们所有的需要鉴权的路由
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";定义路由组件
- 该函数组件充当配置应用程序路由的入口
const Routes = () => {
const { token } = useAuth();
// 路由配置写在这里
};使用 useAuth hook 访问身份校验令牌
- 调用 useAuth hook 可以从身份校验上下文中获取令牌
const { token } = useAuth();定义面向所有用户的路由(公共路由)
routesForPublic数组保护所有可被所有用户访问的路由信息。每个路由信息对象包含一个 path 和一个 element- path 属性明确了路由的 URL 路径,element 属性指向该路由下需要渲染的 jsx 组件/元素
const routesForPublic = [
{
path: "/service",
element: <div>Service Page</div>,
},
{
path: "/about-us",
element: <div>About Us</div>,
},
];定义只有授权用户可以访问的路由
routesForAuthenticatedOnly数组包含只能由经过身份验证的用户访问的路由对象。它包括包装在 ProtectedRoute 组件中的受保护根路由(“/”)和使用 children 属性定义的其他子路由。
const routesForAuthenticatedOnly = [
{
path: "/",
element: <ProtectedRoute />,
children: [
{
path: "/",
element: <div>User Home Page</div>,
},
{
path: "/profile",
element: <div>User Profile</div>,
},
{
path: "/logout",
element: <div>Logout</div>,
},
],
},
];定义只有没有授权的用户才可以访问的路由
routesForNotAuthenticatedOnly数组包含没有经过身份验证的用户访问的路由对象。它包含登录路由(/login)
const routesForNotAuthenticatedOnly = [
{
path: "/",
element: <div>Home Page</div>,
},
{
path: "/login",
element: <div>Login</div>,
},
];基于身份验证状态来组合和判断路由
- createBrowserRouter 函数用于创建路由配置,它接收一个路由数组作为入参
- 扩展运算符 (…) 用于将多个路由数组合并到一个数组
- 条件表达式 (
!token ? routesForNotAuthenticatedOnly : []) 检查用户是否已通过身份验证(令牌存在)。 如果不是,则包含 routesForNotAuthenticatedOnly 数组; 否则,它包含一个空数组。
const router = createBrowserRouter([
...routesForPublic,
...(!token ? routesForNotAuthenticatedOnly : []),
...routesForAuthenticatedOnly,
]);使用 RouterProvider 注入路由配置
- RouterProvider 组件包装路由配置,使其可用于整个应用程序
return <RouterProvider router={router} />;完整代码
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";使用
AuthProvider组件包装Routes组件AuthProvider组件用于向应用程序提供身份验证上下文。 它包装了Routes组件,使身份验证上下文可用于Routes组件树中的所有组件
return (
<AuthProvider>
<Routes />
</AuthProvider>
);完整代码
const Login = () => {
const { setToken } = useAuth();
const navigate = useNavigate();
<span class="hljs-keyword">const</span> <span class="hljs-title function_">handleLogin</span> = (<span class="hljs-params"></span>) => {
<span class="hljs-title function_">setToken</span>(<span class="hljs-string">"this is a test token"</span>);
<span class="hljs-title function_">navigate</span>(<span class="hljs-string">"/"</span>, { <span class="hljs-attr">replace</span>: <span class="hljs-literal">true</span> });
};
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {
<span class="hljs-title function_">handleLogin</span>();
}, <span class="hljs-number">3</span> * <span class="hljs-number">1000</span>);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><></span>Login Page<span class="hljs-tag"></></span></span>;
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Login</span>;</pre><ul><li>登录组件是一个用于表示登录页面的函数组件</li><li>使用 useAuth hook 从身份校验上下文中导入 <code>setToken</code> 函数</li><li>从 <code>react-router-dom</code> 中导入 navigate 函数用于处理路由跳转</li><li>在组件内部,有一个 <code>handleLogin</code> 函数,它使用上下文中的 setToken 函数设置测试令牌,并导航到主页 (“/”),并将替换选项(replace)设置为 true</li><li>setTimeout 函数用于模拟执行 <code>handleLogin</code> 函数前的 3 秒延迟</li><li>组件为登录页返回 JSX,在此处充当一个占位符文本</li></ul><p>现在,我们在 <code>src > pages > Logout.jsx</code> 创建一个 <strong>登出页面</strong></p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="import { useNavigate } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
const Logout = () => {
const { setToken } = useAuth();
const navigate = useNavigate();
const handleLogout = () => {
setToken();
navigate("/", { replace: true });
};
setTimeout(() => {
handleLogout();
}, 3 * 1000);
return <>Logout Page</>;
};
export default Logout;" aria-label="复制" data-bs-original-title="复制"></button>
</div>
import { useNavigate } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
const Logout = () => {
const { setToken } = useAuth();
const navigate = useNavigate();
<span class="hljs-keyword">const</span> <span class="hljs-title function_">handleLogout</span> = (<span class="hljs-params"></span>) => {
<span class="hljs-title function_">setToken</span>();
<span class="hljs-title function_">navigate</span>(<span class="hljs-string">"/"</span>, { <span class="hljs-attr">replace</span>: <span class="hljs-literal">true</span> });
};
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =></span> {
<span class="hljs-title function_">handleLogout</span>();
}, <span class="hljs-number">3</span> * <span class="hljs-number">1000</span>);
<span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag"><></span>Logout Page<span class="hljs-tag"></></span></span>;
};
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-title class_">Logout</span>;</pre><ul><li>在登出页面中,我们调用了 <code>setToken</code> 函数并且没有传参,这相当于调用 <code>setToken(null)</code></li></ul><p>现在,我们将用更新后的版本替换路由组件中的登录和登出组件</p><div class="widget-codetool" style="display: none;">
<div class="widget-codetool--inner">
<button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="const routesForNotAuthenticatedOnly = [
{
path: "/",
element: <div>Home Page</div>,
},
{
path: "/login",
element: <Login />,
},
];" aria-label="复制" data-bs-original-title="复制"></button>
</div>
const routesForNotAuthenticatedOnly = [
{
path: "/",
element: <div>Home Page</div>,
},
{
path: "/login",
element: <Login />,
},
];
在 routesForNotAuthenticatedOnly 数组中,“/login” 的 element 属性设置为 <Login />,表示当用户访问 “/login” 路径时,会渲染 Login 组件
const routesForAuthenticatedOnly = [
{
path: "/",
element: <ProtectedRoute />,
children: [
{
path: "/",
element: <div>User Home Page</div>,
},
{
path: "/profile",
element: <div>User Profile</div>,
},
{
path: "/logout",
element: <Logout />,
},
],
},
];
在 routesForAuthenticatedOnly 数组中,“/logout” 的 element 属性设置为 <Logout />,表示当用户访问 “/logout” 路径时,会渲染 Logout 组件
测试流程
- 当你第一次访问根页面
/ 时,会看到 routesForNotAuthenticatedOnly 数组中的 “ Home page ” - 如果你导航到
/login,在延迟 3 秒后,将模拟登录过程。 它将使用身份验证上下文中的 setToken 函数设置测试令牌,然后你将被react-router-dom 库中的导航函数重定向到根页面 / 。 重定向后,你将从 routesForAuthenticatedOnly 数组中看到 “User Home Page” - 如果你随后访问
/logout,在延迟 3 秒后,将模拟登出过程。 它将通过不带任何参数调用 setToken 函数来清除身份验证令牌,然后您将被重定向到根页面 / 。 由于你现在已登出,我们将从 routesForNotAuthenticatedOnly 数组中看到 “ Home Page ”。
此流程演示了登录和登出过程,其中用户在经过身份验证和未经过身份验证的状态之间转换,并相应地显示相应的路由。
以上就是本篇文章的全部内容,感谢大家对本文的支持~欢迎点赞收藏,在评论区留下你的高见 🌹🌹🌹
其他