OAuth 2.0 和 JWT 授权在 TypeScript 中的实践指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么 OAuth 2.0 和 JWT 是现代授权的标准

在现代 Web 开发中,安全认证和授权是任何应用程序的关键组成部分。OAuth 2.0 和 JWT(JSON Web Tokens)已成为构建分布式系统的事实标准。OAuth 2.0 提供了委托访问协议,而 JWT 则提供了一种紧凑且自包含的令牌格式。

在本文中,我们将通过 TypeScript 的实际示例,详细讲解如何实现完整的授权流程:从令牌颁发到验证和刷新。您将学习如何保护 API 端点、实现 refresh 令牌以及处理常见错误。



1. 项目基本设置和依赖安装

首先,我们创建一个基于 Node.js 和 TypeScript 的项目。请确保您已安装 Node.js(版本 18+)和 npm。

mkdir oauth-jwt-typescriptcd oauth-jwt-typescriptnpm init -ynpm install express cors dotenv jsonwebtoken bcryptnpm install -D typescript @types/express @types/cors @types/jsonwebtoken @types/bcrypt ts-node nodemonnpx tsc --init

配置 tsconfig.json 以支持 ES 模块和严格类型。在 .env 文件中添加密钥:

ACCESS_TOKEN_SECRET=your-super-secret-access-keyREFRESH_TOKEN_SECRET=your-super-secret-refresh-keyACCESS_TOKEN_EXPIRES_IN=15mREFRESH_TOKEN_EXPIRES_IN=7d


2. 实现 JWT 令牌的生成和验证

创建一个工具文件 src/utils/jwt.ts 用于处理令牌。我们将使用两种类型的令牌:Access Token(短期有效)和 Refresh Token(长期有效)。

import jwt from 'jsonwebtoken';import dotenv from 'dotenv';

dotenv.config();

interface TokenPayload { userId: string; email: string; role: 'user' | 'admin';}

export const generateAccessToken = (payload: TokenPayload): string => { return jwt.sign(payload, process.env.ACCESS_TOKEN_SECRET!, { expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN || '15m', });};

export const generateRefreshToken = (payload: TokenPayload): string => { return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, { expiresIn: process.env.REFRESH_TOKEN_EXPIRES_IN || '7d', });};

export const verifyAccessToken = (token: string): TokenPayload => { return jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!) as TokenPayload;};

export const verifyRefreshToken = (token: string): TokenPayload => { return jwt.verify(token, process.env.REFRESH_TOKEN_SECRET!) as TokenPayload;};

请注意:我们对 payload 使用了严格的类型定义。这确保了令牌中始终包含必要的字段。



3. 创建用于保护路由的中间件

现在,我们实现一个中间件,用于检查 Authorization 头中是否存在有效的 Access Token。创建 src/middleware/auth.ts:

import { Request, Response, NextFunction } from 'express';import { verifyAccessToken } from '../utils/jwt';

export interface AuthRequest extends Request { user?: { userId: string; email: string; role: 'user' | 'admin'; };}

export const authenticate = (req: AuthRequest, res: Response, next: NextFunction): void => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

if (!token) { res.status(401).json({ error: '需要访问令牌' }); return; }

try { const decoded = verifyAccessToken(token); req.user = decoded; next(); } catch (error) { res.status(403).json({ error: '访问令牌无效或已过期' }); }};

export const authorizeRole = (...roles: string[]) => { return (req: AuthRequest, res: Response, next: NextFunction): void => { if (!req.user || !roles

博客

书籍推荐