You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用jose验证Firebase Session Cookie(JWT)?

环境设置
  • 我正在遵循Firebase Auth管理Cookie的官方文档。
  • 我使用Next.js中间件,无法用Firebase函数验证Cookie,因此尝试用jose库来验证。
  • Firebase文档指出:

确保会话Cookie是由与令牌kid声明对应的私钥签名的。从Google的公钥地址获取公钥,并使用JWT库验证签名。

已尝试的方案
// middleware.js

import { NextResponse } from 'next/server'
import * as jose from 'jose'

export async function middleware(request) {
  // 检查所有Cookie
  const allCookies = request.cookies.getAll()
  console.log(allCookies)

  // 获取sessionCookie(如果存在)
  const sessionCookie = request.cookies.get("sessionCookie")
  console.log("sessionCookie", sessionCookie)

  // 使用从指定地址获取的硬编码公钥进行验证测试
  const alg = 'RS256'
  const spki = `-----BEGIN CERTIFICATE-----\n  MIIDHDCCAgSgAwIBAgIEIhgI5zANBgkqhkiG9w0BAQsFADAzMQ8wDQYDVQQDEwZH\n  aXRraXQxEzARBgNVBAoTCkdvb2dsZSBJbmMxCzAJBgNVBAYTAlVTMB4XDTE5MDEx\n  NDIzMjgzMVoXDTIwMDEwOTIzMjgzMVowMzEPMA0GA1UEAxMGR2l0a2l0MRMwEQYD\n  VQQKEwpHb29nbGUgSW5jMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQAD\n  ggEPADCCAQoCggEBAMLh7U8PNsOebb1HVLDa81W/CZK8DrdYl9Vlwj/0/GF7kfLP\n  zF4qyShw1zAZddlzt7f6lDy2ZvmQ1nqW0IRy8xgYTeAb6aCLY+rm4DEnwJOCnAVo\n  m1xpgcOVExsXXpleWsP0MugM5xa91Y79CYkVTevPZThgjqfGjan3GiXLQwIJTLG8\n  xEXXSzmMDeW6dP6CBVPsbbTYSat6CX2nBm2YoJ4v2dij3DQBzOD/d5WLaYL4yE9p\n  TjmreitFvs1r0AZF53Cq5ju7M10indVyk1zwfK+tuk7VnCZz/mBHEPxT9+Q5EWFi\n  lVVkZ+2Qau0qRIvar/1QukVpyEsVHPZH+aZ9ilMCAwEAAaM4MDYwDAYDVR0TAQH/\n  BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCB4AwDQYJ\n  KoZIhvcNAQELBQADggEBAH4ca9fhlBd9XmaX9E0rZ8QCWT8AnxcVLY+S+nHJg2IT\n  dj7ZytNJWttKEssOdslrQyamxt2SdQSfpd2UpA+fCO2sRSJOBEA3squGrj1yh1Pt\n  lajf2TM1MwpdO7ZqBbfE3hLH7srBbVXHxKsUemepxviC+dpF0u4o3EjqHpAvkruO\n  4RtC4grbFBNRIbBTu7oJHpKnBJWxK7w11a5Sabs6reAuFh68QaVAQzrscYP/M4i9\n  SPwArWKoCJgrxxVof1N1cal4UpziEH9OaWa7RfyZTYAeEJ/atDo5s8AACPUH542f\n  JEwmwAJKbJKLRoFYJJXCPeKp1Q7EXQHJNNdS2ABjw+w=\n  -----END CERTIFICATE-----`

  const publicKey = await jose.importSPKI(spki, alg)
  const { payload, protectedHeader } = await jose.jwtVerify(sessionCookie.value, publicKey)
  
  console.log(protectedHeader)
  console.log(payload)
}

执行代码后出现错误:

error "spki" must be SPKI formatted string

问题
  • 如何解决这个问题,实现验证功能?
  • 为什么指定公钥地址提供的不是一个,而是五个不同的公钥?
  • 每个公钥都有唯一的键值对,例如:
"skIBNg": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIB...vb2dsZSBJbmMxCzAJBgNVBAYTA.f+9Vg=\n-----END CERTIFICATE-----\n"

其中的键"skIBNg"有什么作用?我目前并未使用它。

解决方案与解释

1. 解决SPKI格式错误并实现验证

你遇到的错误是因为传入的是X.509证书,而importSPKI方法需要的是SPKI格式的公钥字符串。解决方式如下:

  • 直接用jose的importX509方法导入证书,无需转换格式:
    // 替换原importSPKI代码
    const publicKey = await jose.importX509(spki, alg)
    
  • 更规范的动态验证流程:
    1. 解析Session Cookie的JWT头部,提取kid字段
    2. 从公钥接口获取对应kid的证书
    3. 导入证书后验证JWT
      示例代码:
    export async function middleware(request) {
      const sessionCookie = request.cookies.get("sessionCookie")
      if (!sessionCookie) return NextResponse.redirect(new URL('/login', request.url))
    
      // 解析JWT头部获取kid
      const [headerEncoded] = sessionCookie.value.split('.')
      const header = JSON.parse(Buffer.from(headerEncoded, 'base64url').toString())
      const kid = header.kid
    
      // 获取公钥列表并匹配对应证书
      const response = await fetch('https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys')
      const publicKeys = await response.json()
      const certificate = publicKeys[kid]
      if (!certificate) return NextResponse.redirect(new URL('/login', request.url))
    
      // 验证JWT
      const alg = 'RS256'
      const publicKey = await jose.importX509(certificate, alg)
      try {
        await jose.jwtVerify(sessionCookie.value, publicKey)
        return NextResponse.next()
      } catch (err) {
        return NextResponse.redirect(new URL('/login', request.url))
      }
    }
    

2. 为什么存在多个公钥?

这是Google的密钥轮换机制:

  • 为提升安全性,服务商定期更换签名私钥,对应公钥同步更新
  • 保留多个公钥是为了兼容不同时间段签发的JWT,避免旧令牌突然失效
  • 通常包含当前生效、即将生效、刚过期不久的公钥,保证系统平滑过渡

3. 键"skIBNg"的作用

这个键就是JWT头部的kid(Key ID)字段:

  • 每个公钥对应唯一kid,用来标识签名该JWT的私钥对应的公钥
  • 验证时必须用这个kid从公钥列表中匹配对应证书,确保用正确的公钥验证签名
  • 硬编码证书时不需要,但动态获取公钥是核心匹配依据

内容的提问来源于stack exchange,提问作者Ben

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 17:44:58