TypeScript中的授权与认证:从理论到实践
在使用TypeScript开发现代Web应用程序时,用户数据安全是首要任务。两个关键过程——认证(身份验证)和授权(权限检查)——构成了任何安全系统的基础。在本指南中,我们将使用流行的库和最佳实践,全面解析在TypeScript中实现认证和授权的完整流程。
1. 基本概念与区别
- 认证 — 验证用户是否为其所声称的身份的过程(用户名+密码、生物识别、OAuth)。
- 授权 — 确定已认证用户可以访问哪些资源和执行哪些操作的过程(角色、访问权限)。
例如:用户输入了正确的密码(认证),但被禁止删除他人的帖子(授权)。
2. 所需工具和库
我们将使用以下工具进行实现:
bcrypt— 用于密码哈希jsonwebtoken(JWT) — 用于创建和验证令牌express+@types/express— 服务器端zod— 输入数据验证
3. 用户注册(认证)
注册时,密码永远不会以明文形式存储。我们使用bcrypt创建哈希:
import bcrypt from 'bcrypt';import { z } from 'zod';
const registerSchema = z.object({ email: z.string().email(), password: z.string().min(8)});
async function registerUser(email: string, password: string) { const validated = registerSchema.parse({ email, password }); const saltRounds = 10; const hashedPassword = await bcrypt.hash(validated.password, saltRounds); // 将 email + hashedPassword 保存到数据库 // await db.user.create({ data: { email, password: hashedPassword } }); return { success: true };}4. 登录系统并颁发JWT
密码验证成功后,生成访问令牌:
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET!; // 务必放在 .env 文件中
async function loginUser(email: string, password: string) { // 1. 根据email在数据库中查找用户 // const user = await db.user.findUnique({ where: { email } }); // if (!user) throw new Error('User not found'); // 2. 比较密码 const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) throw new Error('Invalid password'); // 3. 创建JWT const token = jwt.sign( { userId: user.id, role: user.role }, JWT_SECRET, { expiresIn: '1h' } ); return { token, user: { id: user.id, email: user.email } };}5. 用于验证JWT的中间件(认证)
创建一个中间件,用于在每个受保护的请求中验证令牌:
import { Request, Response, NextFunction } from 'express';
interface AuthRequest extends Request { user?: { userId: string; role: string };}
export function authMiddleware(req: AuthRequest, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (!authHeader?.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; try { const decoded = jwt.verify(token, JWT_SECRET) as { userId: string; role: string }; req.user = decoded; next(); } catch (err) { return res.status(403).json({ error: 'Invalid or expired token' }); }}6. 基于角色的授权(RBAC)
创建一个中间件