⚡后端安全基石: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 服务部署到生产环境的通行证。

相关推荐
好奇龙猫15 小时前
大学院-筆記試験練習:数据库(データベース問題訓練) と 软件工程(ソフトウェア)(12)
学习
皮蛋sol周15 小时前
嵌入式学习数据结构(二)双向链表 内核链表
linux·数据结构·学习·嵌入式·arm·双向链表
满栀58515 小时前
插件轮播图制作
开发语言·前端·javascript·jquery
代码游侠15 小时前
应用——基于 51 单片机的多功能嵌入式系统
笔记·单片机·嵌入式硬件·学习·51单片机
切糕师学AI15 小时前
Vue 中的计算属性(computed)
前端·javascript·vue.js
亦复何言??15 小时前
DreamWaQ - 基于隐式地形想象的鲁棒四足机器人运动学习
学习·机器人
程琬清君15 小时前
Vue3DraggableResizable可移动范围有问题
前端·javascript·vue.js
lkbhua莱克瓦2415 小时前
CSS盒子模型:网页布局的基石与艺术
前端·css·笔记·javaweb
winfredzhang15 小时前
实战:从零构建一个支持屏幕录制与片段合并的视频管理系统 (Node.js + FFmpeg)
ffmpeg·node.js·音视频·录屏
Curvatureflight15 小时前
前端性能优化指南:从加载到交互的每一毫秒
前端·性能优化·交互