⚡后端安全基石:JWT 原理与身份验证实战

大家好,我是大布布将军。

在你的 BFF 服务中,你不可能让所有人都能调用 /orders/list 接口。我们需要一个机制来验证用户的身份和权限,这个机制在现代 Web 应用中,几乎都基于 JWT (JSON Web Token)

本篇,我们将深入 JWT 的原理,并学习如何在 Node.js 中实现一个安全的注册和登录流程。

1. 什么是 JWT?------无状态的身份证明

JWT 是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在双方之间安全地传输信息。

核心关键词:无状态 (Stateless)。

传统的 Session/Cookie 模式需要在服务器端存储用户的会话信息(有状态)。而 JWT 的所有信息都编码在 Token 中,服务器无需存储 Session,极大提高了 Node.js 服务的可扩展性集群化能力

一个 JWT 通常由三部分组成,用点号(.)分隔:

Header.Payload.Signature\text{Header} . \text{Payload} . \text{Signature}Header.Payload.Signature

部分 内容 作用
Header (头部) 包含 Token 类型(JWT)和所使用的加密算法(如 HMAC SHA256)。 告诉接收方如何解析和验证 Token。
Payload (载荷) 存放实际的声明信息(Claims),如用户 ID (userId)、角色 (role)、Token 过期时间 (exp)。 存放用户的身份信息。
Signature (签名) 使用 Header 和 Payload 结合一个只有服务器知道的密钥 (Secret Key) 进行加密计算得出的字符串。 防止数据被篡改。 如果有人修改了 Payload,签名就会失效。

2. 实践项目:注册、登录与 Token 校验

我们使用 Node.js 中常用的 jsonwebtoken 库来实现这套流程。

步骤一:用户注册与密码安全

在用户注册时,我们绝对不能 明文存储密码。我们需要使用 哈希算法 (如 bcrypt)对密码进行单向加密。

TypeScript

复制代码
// 📁 src/auth/auth.service.ts

import * as bcrypt from 'bcrypt';
import { Injectable } from '@nestjs/common';
import { UserService } from '../user/user.service'; // 假设我们有用户服务操作数据库

@Injectable()
export class AuthService {
    
    // 密码加密存储
    async register(username: string, password_plain: string): Promise<User> {
        // 1. 生成盐值 (Salt)
        const salt = await bcrypt.genSalt(); 
        // 2. 将密码和盐值进行哈希
        const hashedPassword = await bcrypt.hash(password_plain, salt); 

        // 3. 将用户名和哈希后的密码存储到数据库
        return this.userService.createUser(username, hashedPassword);
    }
}
步骤二:用户登录与 JWT 生成

用户登录成功后,我们需要为他生成一个 JWT Token,并返回给前端。

TypeScript

复制代码
// 📁 src/auth/auth.service.ts (续)

import * as jwt from 'jsonwebtoken';
// 假设你的密钥存储在环境变量中
const JWT_SECRET = process.env.JWT_SECRET || 'YOUR_UNSAFE_SECRET_KEY'; 
const JWT_EXPIRES_IN = '1d'; // Token 1天后过期

async login(username: string, password_plain: string): Promise<string> {
    const user = await this.userService.findUserByUsername(username);
    if (!user) throw new Error('用户不存在');

    // 1. 验证密码:将用户输入的明文密码与数据库中的哈希值进行比对
    const isMatch = await bcrypt.compare(password_plain, user.hashedPassword);
    if (!isMatch) throw new Error('密码错误');

    // 2. 构造 Payload (载荷)
    const payload = { 
        userId: user.id, 
        role: user.role 
    }; 
    
    // 3. 生成 JWT Token 并签名
    const token = jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });

    // 4. 返回 Token(前端会将其存储在 LocalStorage 或 Cookie 中)
    return token; 
}
步骤三:身份验证中间件/守卫(Token 校验)

在 BFF 层,我们需要在每一个受保护的 API 接口前,校验前端请求头中携带的 Token 是否有效。

TypeScript

复制代码
// 📁 src/middleware/auth.middleware.ts (NestJS Guard 或 Express Middleware)

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
// ... 其他依赖

@Injectable()
export class AuthGuard implements CanActivate {
    canActivate(context: ExecutionContext): boolean {
        const request = context.switchToHttp().getRequest();
        const authHeader = request.headers.authorization; 

        if (!authHeader || !authHeader.startsWith('Bearer ')) {
            throw new UnauthorizedException('请求未携带 Token 或格式错误');
        }
        
        const token = authHeader.substring(7); // 提取 Token(去掉 "Bearer ")

        try {
            // 1. 验证签名和过期时间
            const payload = jwt.verify(token, JWT_SECRET) as { userId: number, role: string };
            
            // 2. 将用户信息附加到请求对象上,供 Controller/Service 使用
            request.user = payload; 
            
            return true; // 验证通过,允许访问 Controller
        } catch (error) {
            // 签名错误或 Token 过期
            throw new UnauthorizedException('Token 无效或已过期');
        }
    }
}

3. 安全性考量:JWT 的缺点

虽然 JWT 很强大,但它并非完美:

  • 无法主动失效: 一旦 Token 被签发,除非过期,否则服务器无法强制使其失效(除非引入 Redis 黑名单机制)。
  • Payload 不加密: Payload 只是被 Base64 编码,任何人都可以解析 。因此,Payload 中绝不能存储敏感信息,如密码或密钥。

总结

掌握 JWT 是成为一名合格全栈工程师的必备技能。它让你能以可扩展、无状态 的方式管理用户身份。通过 bcrypt 保证密码安全,通过 jsonwebtoken 签发和校验 Token,你的 BFF 服务就有了坚固的安全门。

下一篇,我们将进入工程化领域,学习 Docker 容器化。这是将我们写好的 Node.js 服务部署到生产环境的通行证。

相关推荐
ybc46522 小时前
React、Next安全漏洞问题修复和自测
前端·安全·next.js
huali2 小时前
社区划分:让AI理解你的代码重构意图
前端·javascript·vue.js
掘金安东尼2 小时前
⏰前端周刊第446期(2025年12月22日–12月27日)
前端
汽车仪器仪表相关领域2 小时前
ZDT-I 伺服电机测试系统
数据库·功能测试·安全·机器人·压力测试·可用性测试
漏洞文库-Web安全2 小时前
强网杯 2024 web pyblockly 单题wp
安全·web安全·网络安全·ctf
im_AMBER2 小时前
Leetcode 90 最佳观光组合
数据结构·c++·笔记·学习·算法·leetcode
不老刘2 小时前
前端面试八股文:单线程的JavaScript是如何实现异步的
前端·javascript·面试
J总裁的小芒果2 小时前
后端返回参数不一致 前端手动处理key
前端·vue.js·elementui