基于 JSON Web Token 的身份认证系统

JSON Web Token

JSON Web Token 是一种基于 JSON 的开放标准(RFC 7519),用于在各个层面上向用户提供身份验证。

以下内容中将简称为 JWT 或 Token

JWT 由三部分组成:

  • Header
  • Payload
  • Signature

其中,Header 和 Payload 都是 JSON 对象,Signature 是 Header 和 Payload 的签名,用于验证 Token 的完整性。

Header 部分包含了 Token 的类型以及签名所用的算法。

json 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}
ts 复制代码
export interface JWTHeader {
  alg: string;
  typ: string;
}

Payload

Payload 部分包含了用户的身份信息,例如用户 ID、用户名、权限等。

json 复制代码
{
  "userId": "1234567890",
  "username": "admin",
  "write": true,
  "exp": 1516239022
}
ts 复制代码
export interface JWTPayload {
  userId: string;   # 用户ID
  username: string; # 用户名
  write?: boolean;  # 管理界面权限
  exp?: number;     # 过期时间(暂未用到)
}

Signature

Signature 部分是 Header 和 Payload 的签名,用于验证 Token 的完整性。

ts 复制代码
function sign(payload: JWTPayload, secret: string): string {
  const header = { alg: "HS256", typ: "JWT" };

  const headerString = JSON.stringify(header);
  const payloadString = JSON.stringify(payload);
  const signatureString = crypto
    .createHmac("sha256", secret)
    .update(`${headerString}.${payloadString}`)
    .digest("hex");

  // 使用 `.` 拼接三个部分,这就是一个完整的 JSON Web Token
  return `${headerString}.${payloadString}.${signatureString}`;
}

验证 Token

ts 复制代码
function verify(token: string, secret: string): JWTPayload {
  const parts = token.split(".");

  const header = JSON.parse(Buffer.from(parts[0], "base64").toString());
  const payload = JSON.parse(Buffer.from(parts[1], "base64").toString());
  const signatureString = parts[2];

  // 验证算法
  if (header.alg !== "HS256") {
    throw new Error("无效算法");
  }

  // 验证签名
  // 通过对解析后的 header 和 payload 再次进行签名,比对两个签名是否一致
  const signature = crypto
    .createHmac("sha256", secret)
    .update(`${parts[0]}.${parts[1]}`)
    .digest("hex");
  if (signature !== signatureString) {
    throw new Error("无效签名");
  }

  // 验证过期时间
  if (payload.exp && payload.exp < Date.now()) {
    throw new Error("身份认证已过期");
  }

  return payload;
}

认证流程

1. 用户登录/注册

用户在登录/注册页面输入用户名和密码,点击登录/注册按钮,服务端将用户名和密码在数据库中进行验证/创建,验证通过后使用用户身份信息生成一个 Token,并将 Token 返回给客户端。

注意:Token 只应该包含可用与验证用户身份的信息,千万不要包含诸如密码之类的敏感信息,因为 Token 并不是加密的,在浏览器中使用函数 atob(token.split('.')[1]) 即可解析到 payload 对象的完整内容。

2. 客户端保存并使用 Token

客户端将 Token 保存在 Cookie 中。

客户端每次请求都会自动带上 Cookie,服务端可以通过验证 Cookie 中 Token 的有效性来判断用户的身份。

3. 服务端验证 Token

服务端首先检查请求的 Cookie 中是否有 Token,如果没有则返回错误信息。

如果有 Token,则通过解析 Token 验证它的有效性,没有通过验证则返回错误信息。

验证 Token 的有效性包括但不限于:

  • Token 是否被篡改
  • Token 是否过期

如果验证通过,则继续返回用户需要的资源。

权限控制

目前实现比较简单,通过解析 JWT Payload 对象,获取并校验 write 字段来判断用户是否有权限访问管理界面。

ts 复制代码
if (!payload.write) {
  // 处理没有权限的情况
}

常见问题

1. 如何防止 Token 被篡改?

在生成 JSON Web Token 时,服务端会使用一个只存在于服务端的密钥 secret 来生成每个 Token 的第三段的签名。

当客户端使用 Token 时,服务端会使用相同的密钥根据 Token 的第一段和第二段重新生成 Token 的签名,如果两个签名不一致,则说明 Token 被篡改了。

所以存在服务端的密钥 secret 很关键,一定不要泄漏它!

相关推荐
JefferyXZF12 小时前
Next.js Server Actions 详解: 无缝衔接前后端的革命性技术(八)
前端·全栈·next.js
CF14年老兵1 天前
🤯 AI写代码比人类便宜99.9%!但这就是真相吗?
openai·全栈·trae
滕本尊2 天前
从业务到框架:Elpis 企业级应用的 NPM 包抽离实践
前端·全栈
EndingCoder2 天前
测试 Next.js 应用:工具与策略
开发语言·前端·javascript·log4j·测试·全栈·next.js
滕本尊3 天前
构建可扩展的 DSL 驱动前端框架:从 CRUD 到领域模型的跃迁
前端·全栈
页面仔Dony3 天前
个人主页项目架构设计
面试·全栈
江城开朗的豌豆3 天前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
魏嗣宗4 天前
Node.js 网络编程全解析:从 Socket 到 HTTP,再到流式协议
前端·全栈
三花AI4 天前
xAI AI 伴侣 Ani 和 Valentine 支持电话实时通话
openai·全栈
Zeetenium5 天前
献给前端小白的首次全栈项目心得:Uniapp X全栈项目开发实录
全栈