Melody AuthMelody Auth
  • 认证服务器设置
  • 管理面板设置
  • 邮箱提供商设置
  • 短信提供商设置
  • 外部身份提供商

    • 社交登录提供商设置
    • OIDC SSO 设置
    • SAML SSO 设置
  • 主要功能

    • 身份认证
    • JWT & JWKS
    • 多重认证(MFA)
    • 角色权限控制(RBAC)
    • 策略(policy)
    • 组织(organization)
  • 其他功能

    • 用户属性
    • 应用横幅
    • 组织分组
    • 模拟登录
    • 日志管理
  • 定制化

    • 认证服务器配置
    • 品牌与主题
    • 语言本地化
  • 前端 SDK

    • React SDK
    • Angular SDK
    • Vue SDK
    • Web SDK
  • 后端 API

    • 服务间认证 API 设置
    • 服务间认证 API Swagger
    • 嵌入式认证 API 设置
    • 嵌入式认证 API Swagger
  • 部署流水线
  • 轮换 JWT 密钥
  • English
  • zh-CN
  • 认证服务器设置
  • 管理面板设置
  • 邮箱提供商设置
  • 短信提供商设置
  • 外部身份提供商

    • 社交登录提供商设置
    • OIDC SSO 设置
    • SAML SSO 设置
  • 主要功能

    • 身份认证
    • JWT & JWKS
    • 多重认证(MFA)
    • 角色权限控制(RBAC)
    • 策略(policy)
    • 组织(organization)
  • 其他功能

    • 用户属性
    • 应用横幅
    • 组织分组
    • 模拟登录
    • 日志管理
  • 定制化

    • 认证服务器配置
    • 品牌与主题
    • 语言本地化
  • 前端 SDK

    • React SDK
    • Angular SDK
    • Vue SDK
    • Web SDK
  • 后端 API

    • 服务间认证 API 设置
    • 服务间认证 API Swagger
    • 嵌入式认证 API 设置
    • 嵌入式认证 API Swagger
  • 部署流水线
  • 轮换 JWT 密钥
  • English
  • zh-CN
  • JWT 与 JWKS

JWT 与 JWKS

Melody Auth 使用 RS256 算法签署 JWT access tokens。你可以使用 Auth Server 发布的 JWKS 端点来验证这些 tokens。

概述

  • Melody Auth 为 SPA 签发 RS256 签名的 JWT access tokens。
  • 使用 Auth Server 的 JWKS 端点验证 tokens: [AUTH_SERVER]/.well-known/jwks.json。
  • 始终验证 JWT 的签名、算法,以及关键声明(claims),例如 aud(audience)和 iss(issuer)。

JWKS 端点与密钥轮换

  • JWKS 端点按 kid(key ID)公开当前的公钥。
  • 密钥可能会轮换。使用支持缓存和速率限制的 JWKS 客户端。
  • 验证时,使用 token 头部的 kid 选择正确的公钥。

Node.js 示例(TypeScript)

import { verify, JwtHeader } from 'jsonwebtoken'
import jwksClient, { SigningKey } from 'jwks-rsa'

// 必需的环境变量
// NEXT_PUBLIC_SERVER_URI: Auth Server 的基础 URL(例如 https://auth-server.example.com)
// EXPECTED_AUDIENCE: API 期望的 audience

const client = jwksClient({
  jwksUri: `${process.env.NEXT_PUBLIC_SERVER_URI}/.well-known/jwks.json`,
  cache: true,
  cacheMaxEntries: 5,
  cacheMaxAge: 10 * 60 * 1000,
  rateLimit: true,
  jwksRequestsPerMinute: 10,
})

function getKey(header: JwtHeader, callback: (err: Error | null, key?: string) => void) {
  if (!header.kid) return callback(new Error('Missing kid in token header'))
  client.getSigningKey(header.kid, (err: Error | null, key?: SigningKey) => {
    if (err || !key) return callback(err || new Error('Signing key not found'))
    const signingKey = (key as any).publicKey || (key as any).rsaPublicKey
    callback(null, signingKey)
  })
}

export function verifyAccessTokenRaw(token: string): Promise<Record<string, any>> {
  return new Promise((resolve, reject) => {
    verify(
      token,
      getKey,
      {
        algorithms: ['RS256'],
      },
      (err, decoded) => {
        if (err) return reject(err)
        resolve(decoded as Record<string, any>)
      },
    )
  })
}

// Express 中间件示例
export async function requireAuth(req: any, res: any, next: any) {
  try {
    const authHeader = req.headers['authorization'] as string | undefined
    const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined
    if (!token) return res.status(401).json({ error: 'Missing access token' })
    const claims = await verifyAccessTokenRaw(token)
    // 可选:在此添加额外检查(例如 scopes/roles)
    req.user = claims
    next()
  } catch (err: any) {
    return res.status(401).json({ error: 'Invalid or expired token', details: err?.message })
  }
}
最近更新: 2025/8/11 23:27
Contributors: Baozier