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 axios from "axios";
import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react";
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [token, setToken_] = useState(localStorage.getItem("token"));
const setToken = (newToken) => {
setToken_(newToken);
};
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]);
const contextValue = useMemo(
() => ({
token,
setToken,
}),
[token]
);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
return useContext(AuthContext);
};
export default AuthProvider;
小結,此代碼使用 React 的 context API 設置身份驗證上下文。 它通過 context 向子組件提供身份驗證令牌和 setToken 函數。 它還確保在身份驗證令牌更新時可以及時更新 axios 中的默認授權請求頭。
為 JWT 身份驗證創建路由
為了能夠更高效的組織路由,我們將創建一個 src > routes 目錄。在這個目錄裏,我們將創建一個 index.jsx 文件,這個文件用來作為定義整個應用路由的入口。通過在單獨的文件夾中構建我們的路由,我們可以保持清晰且易於管理的路由結構。讓我們繼續創建路由並探索如何將 JWT 身份驗證集成到我們的 React 應用程序中。
為身份驗證路由創建受保護路由組件
為了保護我們身份驗證的路由並防止未經授權的訪問,我們將創建一個名為 ProtectedRoute 的組件。這個組件將包裹我們的身份驗證路由,以確保只有被授權的用户才能夠訪問。通過現實這個組件,我們可以輕鬆完成身份驗證需求並提供良好的用户體驗。我們將在 src > routes 下創建 ProtectedRoute.jsx 文件
- 首先我們要從
react-router-dom中導入必要的依賴
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
- 定義
ProtectedRoute組件,讓它包裹我們所有的需要鑑權的路由
export const ProtectedRoute = () => {
const { token } = useAuth();
// 判斷用户是否有權限
if (!token) {
// 如果沒有授權,則跳轉到登錄頁面
return <Navigate to="/login" />;
}
// 如果已經授權,則直接渲染子組件
return <Outlet />;
};
- 在
ProtectedRoute組件中,我們通過 AuthContext 提供的自定義 hook (useAuth) 來獲取 token 信息 - 接下來我們檢查 token 是否存在。如果用户沒有被授權( token 是 faslse 或者是 null ),我們將把路由導航到登錄頁面(
/login) - 如果用户被授權了,我們將使用 Outlet 組件來渲染子路由。Outlet 組件充當佔位符,顯示父路由中定義的子組件。
小結,ProtectedRoute 組件充當了身份驗證的路由的守衞。 如果用户未通過身份驗證,他們將被重定向到登錄頁面。 如果用户通過身份驗證,則 ProtectedRoute 組件中定義的子路由將使用 Outlet 組件呈現。
上述代碼使我們能夠根據用户的身份驗證狀態輕鬆保護特定路由並控制訪問,從而在我們的 React 應用程序中提供安全的導航體驗。
深入探索路由
現在我們已經有了 ProtectedRoute 組件和身份驗證上下文,我們可以繼續定義我們的路由。通過區分公共路由、受校驗保護路由和非認證用户路由,我們可以有效地處理基於 JWT 認證的導航和訪問控制。接下來我們將深入到 src > routes > index.jsx 文件並探索如何將 JWT 身份校驗集成到我們的路由結構中
-
導入必要的依賴
RouterProvider和createBrowserRouter用於配置和提供路由功能useAuth運行我們訪問身份校驗的上下文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 { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
const Routes = () => {
const { token } = useAuth();
// 公共路由配置
const routesForPublic = [
{
path: "/service",
element: <div>Service Page</div>,
},
{
path: "/about-us",
element: <div>About Us</div>,
},
];
// 授權的用户才可以訪問的路由配置
const routesForAuthenticatedOnly = [
{
path: "/",
element: <ProtectedRoute />, // Wrap the component in ProtectedRoute
children: [
{
path: "/",
element: <div>User Home Page</div>,
},
{
path: "/profile",
element: <div>User Profile</div>,
},
{
path: "/logout",
element: <div>Logout</div>,
},
],
},
];
// 沒有授權的用户才可以訪問的路由配置
const routesForNotAuthenticatedOnly = [
{
path: "/",
element: <div>Home Page</div>,
},
{
path: "/login",
element: <div>Login</div>,
},
];
// 合併路由配置
const router = createBrowserRouter([
...routesForPublic,
...(!token ? routesForNotAuthenticatedOnly : []),
...routesForAuthenticatedOnly,
]);
return <RouterProvider router={router} />;
};
export default Routes;
最後整合
現在我們已經準備好了 AuthContext, AuthProvider 和 Routes 。讓我們把它們整合到 App.jsx
-
導入必要的組件和文件
AuthProvider是從./provider/authProvider文件中導入的組件。它為整個應用程序提供了身份驗證的上下文- 從
./routes中導入Routes。它定義了應用路由
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";
-
使用
AuthProvider組件包裝Routes組件AuthProvider組件用於嚮應用程序提供身份驗證上下文。 它包裝了Routes組件,使身份驗證上下文可用於Routes組件樹中的所有組件
return (
<AuthProvider>
<Routes />
</AuthProvider>
);
完整代碼
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";
function App() {
return (
<AuthProvider>
<Routes />
</AuthProvider>
);
}
export default App;
實現登錄與登出
在 src > pages > Login.jsx 創建 登錄頁面
const Login = () => {
const { setToken } = useAuth();
const navigate = useNavigate();
const handleLogin = () => {
setToken("this is a test token");
navigate("/", { replace: true });
};
setTimeout(() => {
handleLogin();
}, 3 * 1000);
return <>Login Page</>;
};
export default Login;
- 登錄組件是一個用於表示登錄頁面的函數組件
- 使用 useAuth hook 從身份校驗上下文中導入
setToken函數 - 從
react-router-dom中導入 navigate 函數用於處理路由跳轉 - 在組件內部,有一個
handleLogin函數,它使用上下文中的 setToken 函數設置測試令牌,並導航到主頁 (“/”),並將替換選項(replace)設置為 true - setTimeout 函數用於模擬執行
handleLogin函數前的 3 秒延遲 - 組件為登錄頁返回 JSX,在此處充當一個佔位符文本
現在,我們在 src > pages > Logout.jsx 創建一個 登出頁面
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;
- 在登出頁面中,我們調用了
setToken函數並且沒有傳參,這相當於調用setToken(null)
現在,我們將用更新後的版本替換路由組件中的登錄和登出組件
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 ”。
此流程演示了登錄和登出過程,其中用户在經過身份驗證和未經過身份驗證的狀態之間轉換,並相應地顯示相應的路由。
以上就是本篇文章的全部內容,感謝大家對本文的支持~歡迎點贊收藏,在評論區留下你的高見 🌹🌹🌹
其他
- 本文為翻譯文,原文地址 在這裏
- 代碼倉庫