在 React Router 中使用 JWT

在这篇文章中,我们将探讨 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 的实现

  1. 导入必要的模块和依赖包:

    1. 导入 axios 用于发送 API 请求
    2. react 导入 createContext useContext useEffect useMemo 以及 useState
import axios from "axios";
import {createContext,useContext,useEffect,useMemo,useState,
} from "react";
  1. 使用 createContext() 来创建一个用于身份验证的上下文

    1. createContext() 创建的空的上下文是用于在组件之间共享身份验证的数据和函数的
const AuthContext = createContext();

   2. 创建 AuthProvider 组件

  1. 这个组件是用于作为身份验证上下文 的 provider
  2. 它接收 children 作为 prop,代表将有权访问身份验证上下文的子组件。
const AuthProvider = ({ children }) => {// 组件内容写在这里
};

 3. 使用 useState 定义一个名为 token 的 state

  1. token 代表的是身份验证的令牌
  2. 如果令牌数据存在的话,我们将通过 localStorage.getItem("token") 来获取它
const [token, setToken_] = useState(localStorage.getItem("token"));

4. 创建 setToken 函数来更新身份验证的令牌数据

  1. 这个函数将会用于更新身份验证的令牌
  2. 它使用 setToken_ 函数更新令牌数据并且将更新之后的数据通过 localStorage.setItem() 存储在本地环境
const setToken = (newToken) => {setToken_(newToken);
};

5. 使用 useEffect() 来设置 axios 默认的身份验证请求头并且将身份验证的令牌数据保存到本地

  1. 每当 token 更新, 这个 effect 函数都会执行
  2. 如果 token 存在,它将被设置为 axios 的请求头并且保存到本地 localStorage 中
  3. 如果 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]);

6. 使用 useMemo 创建记忆化的上下文

  1. 这个上下文包含 token 和 setToken 函数
  2. token 的值会被作为记忆化的依赖项(如果 token 不变,则不会重新渲染)
const contextValue = useMemo(() => ({token,setToken,}),[token]
);

7. 给自组件注入身份验证的上下文

  1. 使用 AuthContext.Provider 包裹子组件
  2. 把 contextValue 作为 provider 的值传入
return (<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
);

8. 导出 useAuth 这个 hook ,以供外部使用到身份验证这个 context

  1. useAuth 是一个自定义的 hook,它可以让子组件很方便的访问到身份验证信息
export const useAuth = () => {return useContext(AuthContext);
};

9. 默认导出 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 文件

  1. 首先我们要从 react-router-dom 中导入必要的依赖
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";

      2. 定义 ProtectedRoute 组件,让它包裹我们所有的需要鉴权的路由

export const ProtectedRoute = () => {const { token } = useAuth();// 判断用户是否有权限if (!token) {// 如果没有授权,则跳转到登录页面return <Navigate to="/login" />;}// 如果已经授权,则直接渲染子组件return <Outlet />;};

     

        3. 在 ProtectedRoute 组件中,我们通过 AuthContext 提供的自定义 hook (useAuth) 来获取 token 信息

        4. 接下来我们检查 token 是否存在。如果用户没有被授权( token 是 faslse 或者是 null ),我们将把路由导航到登录页面(/login

        5. 如果用户被授权了,我们将使用 Outlet 组件来渲染子路由。Outlet 组件充当占位符,显示父路由中定义的子组件。

小结,ProtectedRoute 组件充当了身份验证的路由的守卫。 如果用户未通过身份验证,他们将被重定向到登录页面。 如果用户通过身份验证,则 ProtectedRoute 组件中定义的子路由将使用 Outlet 组件呈现。

上述代码使我们能够根据用户的身份验证状态轻松保护特定路由并控制访问,从而在我们的 React 应用程序中提供安全的导航体验。

深入探索路由


现在我们已经有了 ProtectedRoute 组件和身份验证上下文,我们可以继续定义我们的路由。通过区分公共路由、受校验保护路由和非认证用户路由,我们可以有效地处理基于 JWT 认证的导航和访问控制。接下来我们将深入到 src > routes > index.jsx 文件并探索如何将 JWT 身份校验集成到我们的路由结构中

  1. 导入必要的依赖

    1. RouterProvider createBrowserRouter 用于配置和提供路由功能
    2. useAuth 运行我们访问身份校验的上下文
    3. ProtectedRoute 组件包裹着受校验路由
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
  1. 定义路由组件
    1. 该函数组件充当配置应用程序路由的入口
const Routes = () => {const { token } = useAuth();// 路由配置写在这里
};
  1. 使用 useAuth hook 访问身份校验令牌

    1. 调用 useAuth hook 可以从身份校验上下文中获取令牌
const { token } = useAuth();
  1. 定义面向所有用户的路由(公共路由)

    1. routesForPublic 数组保护所有可被所有用户访问的路由信息。每个路由信息对象包含一个 path 和一个 element
    2. path 属性明确了路由的 URL 路径,element 属性指向该路由下需要渲染的 jsx 组件/元素
const routesForPublic = [{path: "/service",element: <div>Service Page</div>,},{path: "/about-us",element: <div>About Us</div>,},
];
  1. 定义只有授权用户可以访问的路由

    1. 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>,},],},
];
  1. 定义只有没有授权的用户才可以访问的路由

    1. routesForNotAuthenticatedOnly 数组包含没有经过身份验证的用户访问的路由对象。它包含登录路由(/login )
const routesForNotAuthenticatedOnly = [{path: "/",element: <div>Home Page</div>,},{path: "/login",element: <div>Login</div>,},
];

基于身份验证状态来组合和判断路由

  1. createBrowserRouter 函数用于创建路由配置,它接收一个路由数组作为入参
  2. 扩展运算符 (…) 用于将多个路由数组合并到一个数组
  3. 条件表达式 (!token ? routesForNotAuthenticatedOnly : []) 检查用户是否已通过身份验证(令牌存在)。 如果不是,则包含 routesForNotAuthenticatedOnly 数组; 否则,它包含一个空数组。
const router = createBrowserRouter([...routesForPublic,...(!token ? routesForNotAuthenticatedOnly : []),...routesForAuthenticatedOnly,
]);
  1. 使用 RouterProvider 注入路由配置

    1. 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 ProtectedRoutechildren: [{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;

最后整合

现在我们已经准备好了 AuthContextAuthProvider  和  Routes 。让我们把它们整合到 App.jsx

  1. 导入必要的组件和文件

    1. AuthProvider 是从 ./provider/authProvider 文件中导入的组件。它为整个应用程序提供了身份验证的上下文
    2. ./routes 中导入 Routes 。它定义了应用路由
import AuthProvider from "./provider/authProvider";
import Routes from "./routes";
  1. 使用 AuthProvider 组件包装 Routes 组件

    1. 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 组件

测试流程


  1. 当你第一次访问根页面 / 时,会看到 routesForNotAuthenticatedOnly 数组中的 “ Home page ”
  2. 如果你导航到 /login,在延迟 3 秒后,将模拟登录过程。 它将使用身份验证上下文中的 setToken 函数设置测试令牌,然后你将被react-router-dom 库中的导航函数重定向到根页面 / 。 重定向后,你将从 routesForAuthenticatedOnly 数组中看到 “User Home Page”
  3. 如果你随后访问 /logout,在延迟 3 秒后,将模拟登出过程。 它将通过不带任何参数调用 setToken 函数来清除身份验证令牌,然后您将被重定向到根页面 / 。 由于你现在已登出,我们将从 routesForNotAuthenticatedOnly 数组中看到 “ Home Page ”。

此流程演示了登录和登出过程,其中用户在经过身份验证和未经过身份验证的状态之间转换,并相应地显示相应的路由。

以上就是本篇文章的全部内容,感谢大家对本文的支持~欢迎点赞收藏,在评论区留下你的高见 🌹🌹🌹

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/140024.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为云Ascend310服务器使用

使用华为云服务器 cpu: 16vCPUs Kunpeng 920 内存&#xff1a;16GiB gpu&#xff1a;4* HUAWEI Ascend 310 cann: 20.1.rc1 操作系统&#xff1a;Ubuntu aarch64目的 使用该服务器进行docker镜像编译&#xff0c;测试模型。 已知生产环境&#xff1a;mindx版本为3.0.rc3&a…

keep-alive缓存,三级路由不生效

此文章讲诉在vue中使用keep-alive缓存&#xff0c;三级路由缓存失败处理方案。 一二级路由缓存无任何问题&#xff0c;三级以上就会失败&#xff0c;因此我们在路由守卫中对matched做出如下优化 Router.beforeEach((to, from, next)>{if(to.matched && to.matched.l…

【机器学习】Kmeans聚类算法

一、聚类简介 Clustering (聚类)是常见的unsupervised learning (无监督学习)方法&#xff0c;简单地说就是把相似的数据样本分到一组&#xff08;簇&#xff09;&#xff0c;聚类的过程&#xff0c;我们并不清楚某一类是什么&#xff08;通常无标签信息&#xff09;&#xff0…

【PTE-day07 文件上传2】

1、常见的绕过方式 (1)畸形后缀名绕过 .php、.pht、.php3、.php4、.php5、.php2、.phtml、.pHp、.html、.Htm......(2)双写过滤字符绕过 (3).htaccess文件绕过 <FilesMatch "jpg"> SetHandler application/x-httpd-php

通义千问, 文心一言, ChatGLM, GPT-4, Llama2, DevOps 能力评测

引言 “克隆 dev 环境到 test 环境&#xff0c;等所有服务运行正常之后&#xff0c;把访问地址告诉我”&#xff0c;“检查所有项目&#xff0c;告诉我有哪些服务不正常&#xff0c;给出异常原因和修复建议”&#xff0c;在过去的工程师生涯中&#xff0c;也曾幻想过能够通过这…

目标检测工程化最佳实践:Python 并行条件下YOLOv8的模型推理,线程安全的模型推理!

文章大纲 YOLOv8模型的线程安全推理 背景简介Python 线程的一些理解共享模型实例的问题与危害!非线程安全的代码样例: 单个模型实例非线程安全的代码样例: 多个个模型实例YOLOv8 中线程安全的推理方式Thread-Safe Example 1Thread-Safe Example 2YOLOv8 主要开发人员的回复结论…

【FAQ】Gradle开发问题汇总

1. buildSrc依赖Spring Denpendency时报错 来自预编译脚本的插件请求不能包含版本号。请从有问题的请求中删除该版本&#xff0c;并确保包含所请求插件io.spring.dependency-management的模块是一个实现依赖项 解决方案 https://www.5axxw.com/questions/content/uqw0grhttps:/…

Flink之Catalog

Catalog Catalog概述Catalog分类 GenericInMemoryCatalogJdbcCatalog下载JAR包及使用重启操作创建Catalog查看与使用Catalog自动初始化catalog HiveCatalog下载JAR包及使用重启操作hive metastore服务创建Catalog查看与使用CatalogFlink与Hive中操作自动初始化catalog 用户自定…

基于springboot实现桥牌计分管理系统项目【项目源码】计算机毕业设计

基于springboot实现桥牌计分管理系统演示 JAVA简介 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#…

MYSQL操作详解

一)计算机的基本结构 但是实际上&#xff0c;更多的是这种情况: 二)MYSQL中的数据类型: 一)数值类型: 数据类型内存大小(字节)说明bit(M)M指定位数,默认为1单个二进制位值&#xff0c;或者为0或者为1&#xff0c;主要用于开/关标志tinyint1字节1个字节的整数值&#xff0c;支持…

KITTI数据集(.bin数据)转换为点云数据(.pcd文件)

目录 cmake代码 代码 cmake代码 cmake_minimum_required(VERSION 3.17) project(TEST2)set(CMAKE_CXX_STANDARD 14)# Find PCL find_package(PCL 1.8 REQUIRED)# If PCL was found, add its include directories to the project if(PCL_FOUND)include_directories(${PCL_INC…

使用openvc进行人脸检测:Haar级联分类器

1 人脸检测介绍 1.1 什么是人脸检测 人脸检测的目标是确定图像或视频中是否存在人脸。如果存在多个面&#xff0c;则每个面都被一个边界框包围&#xff0c;因此我们知道这些面的位置 人脸检测算法的主要目标是准确有效地确定图像或视频中人脸的存在和位置。这些算法分析数据…

Unity 获取对象的方法

Unity获取对象的方法还是有不少的。 以下是一些我知道的方法。 1、GameObject.Find()&#xff1a;这是一种最简单的方法&#xff0c;可以通过对象的名称来查找对象。例如&#xff0c;GameObject.Find("Cube")&#xff1b;将返回名称为"Cube"的对象。 2、…

一文入门Springboot+actuator+Prometheus+Grafana

环境介绍 技术栈 springbootmybatis-plusmysqloracleactuatorPrometheusGrafana 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 本地主机应用 192.168.1.9:8007 PrometheusGrafana安装在同一台主机 http://…

[西湖论剑 2022]real_ez_node

文章目录 前置知识EJS模板注入&#xff08;CVE-2022-29078&#xff09;原型链污染漏洞 &#xff08;CVE-2021-25928&#xff09;HTTP响应拆分攻击&#xff08;CRLF&#xff09; 解题过程代码审计构造payload 前置知识 EJS模板注入&#xff08;CVE-2022-29078&#xff09; EJS…

.net在使用存储过程中IN参数的拼接方案,使用Join()方法

有时候拼接SQL语句时&#xff0c;可能会需要将list中的元素都加上单引号&#xff0c;并以逗号分开&#xff0c;但是Join只能简单的分开&#xff0c;没有有单引号&#xff01; 1.第一种拼接方案 List<string> arrIds new List<string>(); arrIds.Add("aa&qu…

Cross-Origin跨站问题详解(跨站请求、跨站cookie)

背景&#xff1a;我部署frontend和backend到两个不同的docker容器&#xff0c;前端路径为http://localhost:3000&#xff0c;后端路径为http://localhost:4000。我设置了用户登录功能&#xff0c;并使用cookie进行session管理。当我的前端登录时&#xff0c;创建了一个session&…

【Java 进阶篇】Java与JQuery选择器:解锁前端开发的魔法大门

在前端开发的世界中&#xff0c;选择器是我们与HTML文档进行互动的钥匙&#xff0c;而Java和JQuery则为我们提供了强大的工具&#xff0c;使得前端开发不再是一个艰深的谜题。本篇博客将围绕Java与JQuery选择器展开&#xff0c;深入解析选择器的奥秘&#xff0c;为你打开前端开…

nvdiffrast的MeshRenderer

获取输入: vertex: 顶点坐标,大小为(B, N, 3)tri: 面片索引,大小为(B, M, 3) 或 (M, 3)feat(可选): 顶点features,大小为(B, C)计算NDC(标准设备坐标)投影矩阵,用于投影到图像平面。将顶点坐标转换到同质坐标(加1维,方便后续运算)。用NDC投影矩阵将顶点坐标转换到NDC空间。创建…

C++ 模板保姆级详解——template<class T>(什么是模板?模板分哪几类?模板如何应用?)

目录 一、前言 二、 什么是C模板 &#x1f4a6;泛型编程的思想 &#x1f4a6;C模板的分类 三、函数模板 &#x1f4a6;函数模板概念 &#x1f4a6;函数模板格式 &#x1f4a6;函数模板的原理 &#x1f4a6;函数模板的实例化 &#x1f34e;隐式实例化 &#x1f349;显式实…