Next.js 14 TS 中使用jwt 和 App Router 进行管理
jwt是一个很基础的工作。但是因为架构不一样,就算是相同的架构,版本不一样,加jwt都会有一定的差别。现在我们的项目是Next.js 14 TS 的 App Router项目(就是没有pages那种),添加jwt的步骤:
1、安装所需的依赖:
npm install jsonwebtoken bcryptjs
npm install -D @types/jsonwebtoken @types/bcryptjs
2、配置环境变量
//在项目根目录(package.json所在目录)下创建一个.env.local文件,用于存储环境变量,例如我们的 //JWT 秘密密钥:
JWT_SECRET=my_super_secret_key
JWT_EXPIRES_IN=1h
3、我们在 app/api 文件夹中创建两个 API 路由:一个用于登录,一个用于保护的数据获取。
1. 登录 API (app/api/login/route.ts)
为实现登录功能,我们需要处理用户输入的用户名和密码,验证它们,创建 JWT 并返回给客户端。import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';const users = [{ id: 1, username: '1', password: await bcrypt.hash('1', 10) },{ id: 2, username: '2', password: await bcrypt.hash('2', 10) },
];export async function POST(request: NextRequest) {const { username, password } = await request.json();const user = users.find(u => u.username === username);if (!user || !(await bcrypt.compare(password, user.password))) {return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });}const token = jwt.sign({ userId: user.id, username: user.username }, process.env.JWT_SECRET!, { expiresIn: process.env.JWT_EXPIRES_IN });return NextResponse.json({ token });
}
2. 受保护的 API (app/api/protected/route.ts)
这个路由将在请求时检查并验证 JWT,并返回受保护的数据。
import { NextRequest, NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';export function GET(request: NextRequest) {const authHeader = request.headers.get('authorization');if (!authHeader || !authHeader.startsWith('Bearer ')) {return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });}const token = authHeader.split(' ')[1];try {const decodedToken = jwt.verify(token, process.env.JWT_SECRET!);return NextResponse.json({ message: 'This is protected data', user: decodedToken });} catch (err) {return NextResponse.json({ error: 'Invalid or expired token' }, { status: 401 });}
}
4、配置中间件
如果有多个受保护的路由,建议使用中间件来验证 JWT。这可以避免在每个受保护的路由中重复相同的验证逻辑。
在 app/middleware.ts 文件中:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import jwt from 'jsonwebtoken';export const middleware = (req: NextRequest) => {const token = req.cookies.get('token');if (req.nextUrl.pathname.startsWith('/api/protected')) {if (!token) {return NextResponse.json({ message: 'Authorization token missing' }, { status: 401 });}try {jwt.verify(token, process.env.JWT_SECRET!);return NextResponse.next();} catch (error) {return NextResponse.json({ message: 'Invalid token' }, { status: 401 });}}if (req.nextUrl.pathname.startsWith('/app/test')) {if (!token) {return NextResponse.redirect(new URL('/login', req.url));}try {jwt.verify(token, process.env.JWT_SECRET!);return NextResponse.next();} catch (error) {return NextResponse.redirect(new URL('/login', req.url));}}return NextResponse.next();
};export const config = {matcher: ['/api/protected/:path*', '/app/test/:path*'],
};
5、创建 AuthGuard
组件
创建 AuthGuard 组件 (app/components/AuthGuard.tsx)
我们使用一个高阶组件来实现路由保护逻辑。
'use client';// app/components/AuthGuard.tsx
import { ReactNode, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '../context/auth';const AuthGuard = ({ children }: { children: ReactNode }) => {const router = useRouter();const { isAuthenticated } = useAuth();useEffect(() => {if (!isAuthenticated) {router.push('/login');}}, [isAuthenticated, router]);if (!isAuthenticated) {return null; // 或者一个加载动画}return <>{children}</>;
};export default AuthGuard;
6、高阶组件将封装所有重复的逻辑:
创建 AuthGuardwithAuth高阶组件 (app/components/AuthGuardwithAuth.tsx)
// app/components/withAuth.tsx
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '../context/auth';
import AuthGuard from './AuthGuard';const withAuth = (WrappedComponent: React.ComponentType<any>) => {return (props: any) => {const [message, setMessage] = useState('');const { isAuthenticated, logout } = useAuth();const router = useRouter();//console.log("withAuth启动了");useEffect(() => {const fetchData = async () => {const token = localStorage.getItem('token');const res = await fetch('/api/protected', {headers: {'Authorization': `Bearer ${token}`,},});const data = await res.json();if (res.ok) {setMessage(data.message);} else {setMessage(data.error);logout(); // 如果token无效,注销用户router.push('/login');}};if (isAuthenticated) {fetchData();}}, [isAuthenticated, logout, router]);return (<AuthGuard><WrappedComponent {...props} message={message} /></AuthGuard>);};
};export default withAuth;
7、使用 React Context 管理登录状态
使用 React Context 管理登录状态 (app/context/auth.tsx)
"use client"
// app/context/auth.tsx
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';interface AuthContextType {isAuthenticated: boolean;login: (token: string) => void;logout: () => void;
}const AuthContext = createContext<AuthContextType | undefined>(undefined);export const AuthProvider = ({ children }: { children: ReactNode }) => {const [isAuthenticated, setIsAuthenticated] = useState(false);useEffect(() => {const token = localStorage.getItem('token');if (token) {// Optionally, you can verify the token on the client side heresetIsAuthenticated(true);}}, []);const login = (token: string) => {localStorage.setItem('token', token);setIsAuthenticated(true);};const logout = () => {localStorage.removeItem('token');setIsAuthenticated(false);};return (<AuthContext.Provider value={{ isAuthenticated, login, logout }}>{children}</AuthContext.Provider>);
};export const useAuth = () => {const context = useContext(AuthContext);if (context === undefined) {throw new Error('useAuth must be used within an AuthProvider');}return context;
};
8、配置全局 AuthProvider
配置全局 AuthProvider (app/layout.tsx)
接下来,在 app/layout.tsx 中配置全局的 AuthProvider:
// src/app/layout.tsx
import type { Metadata } from 'next';
import { Roboto } from 'next/font/google';
import '@progress/kendo-theme-bootstrap/dist/all.css';
import './globals.css';
import IntlProviderWrapper from './IntlProviderWrapper';
import { AuthProvider } from './context/auth';export const metadata: Metadata = {title: 'Create Next App',description: 'Generated by create next app',
};const roboto = Roboto({weight: '400',subsets: ['latin'],
});export default function RootLayout({children,
}: {children: React.ReactNode
}) {return (<html lang="en"><body className={roboto.className}><AuthProvider><IntlProviderWrapper>{children}</IntlProviderWrapper></AuthProvider></body></html>);
}
9、登录页示例
登录页示例 (app/login/page.tsx)
'use client';import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '../context/auth';export default function Login() {const [username, setUsername] = useState('');const [password, setPassword] = useState('');const [error, setError] = useState('');const { login } = useAuth();const router = useRouter();const handleLogin = async () => {setError(''); // Reset error messageconst res = await fetch('/api/login', {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify({ username, password }),});const data = await res.json();if (res.ok) {login(data.token); // 使用 context 中的 login 方法router.push('/protected');} else {setError(data.error);}};return (<div><h1>Login</h1><inputtype="text"placeholder="Username"value={username}onChange={(e) => setUsername(e.target.value)}/><inputtype="password"placeholder="Password"value={password}onChange={(e) => setPassword(e.target.value)}/><button onClick={handleLogin}>Login</button>{error && <p style={{ color: 'red' }}>{error}</p>}</div>);
}
10、受保护页
import withAuth from '../components/AuthGuardwithAuth';
export default withAuth(App)
相关文章:
Next.js 14 TS 中使用jwt 和 App Router 进行管理
jwt是一个很基础的工作。但是因为架构不一样,就算是相同的架构,版本不一样,加jwt都会有一定的差别。现在我们的项目是Next.js 14 TS 的 App Router项目(就是没有pages那种),添加jwt的步骤: 1、…...
【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G
2025 - 01 - 21 - 第 45 篇 【洛谷】贪心算法题单 -【 贪心算法】 - 【学习笔记】 作者(Author): 郑龙浩 / 仟濹(CSND账号名) 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 【贪心算法】 文章目录 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06…...

Windows11无法打开Windows安全中心主界面
# 问题描述 安全中心无法打卡主界面,并弹出“需要使用新应用以打开此windowsdefender连接”. 解决方法 以管理员权限打开PowerShell,推荐使用快捷键win x打开快捷界面,选择Windows终端(管理员),并在终…...
下载arm架构的deb包的方法
在ARM板上操作 如果你是在arm板上使用apt安装和下载包,那么安装过的包会在以下路径里: /var/cache/apt/archives只需要复制出来就可以 如果只下载不安装,可以使用命令 sudo apt-get -d install package_name:arm64 # 如果是32位࿰…...
【Day29 LeetCode】动态规划DP
一、动态规划DP 1、不同路径 62 首先是dp数组,dp[i][j]表示从起点(0, 0)到达当前位置(i, j)的路径数,转移方程从只能向下和向右移动可知,初始化边界可直观推出第一行和第一列上的位置只有一条路径。 class Solution { public:int uniquePa…...

5分钟带你获取deepseek api并搭建简易问答应用
目录 1、获取api 2、获取base_url和chat_model 3、配置模型参数 方法一:终端中临时将加入 方法二:创建.env文件 4、 配置client 5、利用deepseek大模型实现简易问答 deepseek-v3是截止博文撰写之日,无论是国内还是国际上发布的大模型中…...
LeetCode题练习与总结:最短无序连续子数组--581
一、题目描述 给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。 请你找出符合题意的 最短 子数组,并输出它的长度。 示例 1: 输入:num…...
探秘 TCP TLP:从背景到实现
回家的路上还讨论了个关于 TCP TLP 的问题,闲着无事缕一缕。本文内容参考自 Tail Loss Probe (TLP): An Algorithm for Fast Recovery of Tail Losses 以及 Linux 内核源码。 TLP,先说缘由。自 TCP 引入 Fast retrans 机制就是为了尽力避免 RTO…...

linux学习之网络编程
一、两个模型及其对应关系 OSI七层模型 TCP/IP 四层模型 -------------------------------------------------------------------------- 应用层 表示层 ----> …...
scrol家族 offset家族 client家族学习
Scroll 系列属性 scrollTop & scrollLeft scrollTop: 返回元素的内容已向上滚动的部分的高度。scrollLeft: 返回元素的内容已向左滚动的部分的宽度。 scrollHeight & scrollWidth scrollHeight: 返回元素的实际高度,包括由于溢出而在屏幕上不可见的内容…...
css-background-color(transparent)
1.前言 在 CSS 中,background-color 属性用于设置元素的背景颜色。除了基本的颜色值(如 red、blue 等)和十六进制颜色值(如 #FF0000、#0000FF 等),还有一些特殊的属性值可以用来设置背景颜色。 2.backgrou…...

如何将xps文件转换为txt文件?xps转为pdf,pdf转为txt,提取pdf表格并转为txt
文章目录 xps转txt方法一方法二 pdf转txt整页转txt提取pdf表格,并转为txt 总结另外参考XPS文件转换为TXT文件XPS文件转换为PDF文件PDF文件转换为TXT文件提取PDF表格并转为TXT示例代码(部分) 本文测试代码已上传,路径如下ÿ…...

【Samba】Ubuntu20.04 Windows 共享文件夹
【Samba】Ubuntu20.04 Windows 共享文件夹 前言整体思路检查 Ubuntu 端 和 Windows 网络通信是否正常创建共享文件夹安装并配置 Samba 服务器安装 Samba 服务器创建 Samba 用户编辑 Samba 配置文件重启 Samba 服务器 在 Windows 端 访问 Ubuntu 的共享文件夹 前言 本文基于 Ub…...
gradle和maven的区别以及怎么选择使用它们
目录 区别 1. 配置方式 2. 依赖管理 3. 构建性能 4. 灵活性和扩展性 5. 多项目构建 如何选择使用 选择 Maven 的场景 选择 Gradle 的场景 区别 1. 配置方式 Maven: 使用基于 XML 的 pom.xml 文件进行配置。所有的项目信息、依赖管理、构建插件等都在这个文…...
360大数据面试题及参考答案
数据清理有哪些方法? 数据清理是指发现并纠正数据文件中可识别的错误,包括检查数据一致性,处理无效值和缺失值等。常见的数据清理方法有以下几种: 去重处理:数据中可能存在重复的记录,这不仅会占用存储空间,还可能影响分析结果。通过对比每条记录的关键属性,若所有关键…...
Myeclipse最新版本 C1 2019.4.0
Myeclipse C1 2019.4.0下载地址:链接: https://pan.baidu.com/s/1MbOMLewvAdemoQ4FNfL9pQ 提取码: tmf6 1.1、什么是集成开发环境? ★集成开发环境讲究-站式开发,使用这个工具即可。有提示功能,有自动纠错功能。 ★集成开发环境可以让软件开…...
MySQL 9.2.0 的功能
MySQL 9.2.0 的功能 MySQL 9.2.0 的功能新增、弃用和删除内容如下: 新增功能 权限新增12:引入了CREATE_SPATIAL_REFERENCE_SYSTEM权限,拥有该权限的用户可执行CREATE SPATIAL REFERENCE SYSTEM、CREATE OR REPLACE SPATIAL REFERENCE SYSTEM…...

接口 V2 完善:分布式环境下的 WebSocket 实现与 Token 校验
🎯 本文档详细介绍了如何使用WebSocket协议优化客户端与服务端之间的通信,特别是在处理异步订单创建通知的场景中。通过引入WebSocket代替传统的HTTP请求-响应模式,实现了服务器主动向客户端推送数据的功能,极大地提高了实时性和效…...
微前端架构在前端开发中的实践与挑战
随着单页面应用(SPA)和前端框架如 React、Vue、Angular 的快速发展,现代前端应用的复杂度日益提升。尤其是当应用规模逐渐增大时,单一的代码库往往难以应对不同团队的协作和版本管理问题。为了应对这一挑战,微前端架构…...

【自学嵌入式(6)天气时钟:软硬件准备、串口模块开发】
天气时钟:软硬件准备、串口模块开发 软硬件准备接线及模块划分ESP8266开发板引脚图软件准备 串口模块编写串口介绍Serial库介绍 近期跟着网上一些教学视频,编写了一个天气时钟,本篇及往后数篇都将围绕天气时钟的制作过程展开。本文先解决硬件…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...