您的数据岌岌可危:为什么 TypeScript 中的认证绝非儿戏
\n\n想象一下:您用 TypeScript 创建了一个很棒的应用程序。它运行流畅,类型严格,代码美观。但有一个小细节——认证。这时,好戏才刚刚开始。一个错误——用户数据就会像水从指缝间流走一样泄露。熟悉吗?
\n\n在 2024 年,安全认证不仅仅是“加个登录功能”。它是一门完整的科学。而 TypeScript 是您最好的朋友。它不会让您意外传递错误的数据类型,会在您忘记验证令牌时提醒您,并在编译时拯救您于愚蠢的错误之中。
\n\n在本文中,我们将探讨如何从零开始构建TypeScript 应用程序中的安全认证。没有废话——只有真实的示例、最佳实践和一点点黑色幽默(怎能少了它)。
\n\nJWT:一见令牌就倾心
\n\nJSON Web Token (JWT) 是现代 Web 应用程序中认证的事实标准。但是,正如本叔叔所说,“能力越大,责任越大”。JWT 既可以是一面盾牌,也可以是一颗定时炸弹。
\n\n不应该怎么做:反面示例
\n\n新手最常见的错误是将令牌存储在 localStorage 中并以明文形式传输。是的,这行得通。但这就像把家门钥匙放在门垫下——看似方便,但任何路人都可以进来。XSS 攻击——您所有的令牌都会落入攻击者手中。
\n\n在 TypeScript 中正确处理 JWT
\n\n让我们编写一个安全的令牌处理包装器。TypeScript 在这里将帮助我们避免混淆类型并忘记验证。
\n\nimport jwt from 'jsonwebtoken';\n\ninterface TokenPayload {\n userId: string;\n role: 'user' | 'admin';\n iat?: number;\n exp?: number;\n}\n\n// 使用清晰的类型定义生成令牌\nfunction generateAccessToken(payload: TokenPayload): string {\n return jwt.sign(\n { ...payload },\n process.env.JWT_SECRET as string,\n { expiresIn: '15m' }\n );\n}\n\n// 验证并立即转换为我们的接口\nfunction verifyAccessToken(token: string): TokenPayload | null {\n try {\n return jwt.verify(\n token, \n process.env.JWT_SECRET as string\n ) as TokenPayload;\n } catch (error) {\n console.error('令牌验证失败:', error);\n return null;\n }\n}\n\n请注意:我们始终检查 JWT_SECRET 是否存在且为字符串。TypeScript 强制我们这样做——运行时绝不允许出现 undefined!
密码哈希:bcrypt 前来救援
\n\n以明文形式存储密码是一种犯罪。即使您的数据库比 Fort Knox 还要安全,一次入侵——您就会作为又一次“密码泄露”登上新闻。密码哈希是您的救命稻草。
\n\n为什么选择 bcrypt 而不是 MD5?
\n\nMD5 已经过时。SHA-256 也不是选项。现代 GPU 每秒可以处理数十亿个哈希。Bcrypt 则是特意设计为缓慢的。它使用盐(随机数据)和可调整的成本因子。成本越高——计算哈希所需时间越长。而这正是好事!
\n\n在 TypeScript 中的实现
\n\nimport bcrypt from 'bcrypt';\n\nconst SALT_ROUNDS = 12; // 2024 年的最佳值\n\nasync function hashPassword(password: string): Promise<string> {\n const salt = await bcrypt.genSalt(SALT_ROUNDS);\n return bcrypt.hash(password, salt);\n}\n\nasync function comparePassword(\n password: string, \n hash: string\n): Promise<boolean> {\n return bcrypt.compare(password, hash);\n}\n\n// 用于注册的类型化函数\ninterface CreateUserInput {\n email: string;\n password: string;\n name: string;\n}\n\nasync function createUser(input: CreateUserInput): Promise<Omit<User, 'passwordHash'>> {\n const passwordHash = await hashPassword(input.password);\n \n // 保存到数据库(此处为您的代