我正在尝试使用 cloudflare 工作人员执行经过身份验证的操作。
我使用 firebase 进行身份验证,并可以访问通过的访问令牌,但由于 firebase-admin 使用 nodejs 模块,它无法在平台上工作,因此我只能手动验证令牌。
我一直在尝试使用 Crypto API 进行身份验证,并最终让它导入公钥签署令牌以检查其是否有效,但我一直得到 FALSE。我正在努力弄清楚为什么它总是返回 false 的有效性。
我导入的加密密钥的类型为“秘密”,我希望它是“公开”的。
任何想法或帮助都会很大。在过去的几天里我一直用头撞桌子试图解决这个问题
这是我到目前为止所拥有的:
function _utf8ToUint8Array(str) {
return Base64URL.parse(btoa(unescape(encodeURIComponent(str))))
}
class Base64URL {
static parse(s) {
return new Uint8Array(Array.prototype.map.call(atob(s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')), c => c.charCodeAt(0)))
}
static stringify(a) {
return btoa(String.fromCharCode.apply(0, a)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
}
export async function verify(userToken: string) {
let jwt = decodeJWT(userToken)
var jwKey = await fetchPublicKey(jwt.header.kid);
let publicKey = await importPublicKey(jwKey);
var isValid = await verifyPublicKey(publicKey, userToken);
console.log('isValid', isValid) // RETURNS FALSE
return isValid;
}
function decodeJWT(jwtString: string): IJWT {
// @ts-ignore
const jwt: IJWT = jwtString.match(
/(?<header>[^.]+)\.(?<payload>[^.]+)\.(?<signature>[^.]+)/
).groups;
// @ts-ignore
jwt.header = JSON.parse(atob(jwt.header));
// @ts-ignore
jwt.payload = JSON.parse(atob(jwt.payload));
return jwt;
}
async function fetchPublicKey(kid: string) {
var key: any = await (await fetch('https://www.googleapis.com/robot/v1/metadata/x509/[email protected]')).json();
key = key[kid];
key = _utf8ToUint8Array(key)
return key;
}
function importPublicKey(jwKey) {
return crypto.subtle.importKey('raw', jwKey, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
}
async function verifyPublicKey(publicKey: CryptoKey, token: string) {
const tokenParts = token.split('.')
let res = await crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, publicKey, _utf8ToUint8Array(tokenParts.slice(0, 2).join('.')))
return Base64URL.stringify(new Uint8Array(res)) === tokenParts[2];
}
https://www.googleapis.com/service_accounts/v1/jwk/[email protected]
(记录于此处)。
async function fetchPublicKey(kid) {
const result = await (
await fetch(
"https://www.googleapis.com/service_accounts/v1/jwk/[email protected]"
)
).json();
return result.keys.find((key) => key.kid === kid);
}
使用密钥(下面名为jwk),可以验证签名:
const encoder = new TextEncoder();
const data = encoder.encode([token.raw.header, token.raw.payload].join("."));
const signature = new Uint8Array(
Array.from(token.signature).map((c) => c.charCodeAt(0))
);
const key = await crypto.subtle.importKey(
"jwk",
jwk,
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
false,
["verify"]
);
return crypto.subtle.verify("RSASSA-PKCS1-v1_5", key, signature, data);
您的代码存在一些问题:
您调用获取公钥的 URL 返回 x509 证书列表。这些不是用于验证签名的公钥。您确定您无法直接访问公钥吗?似乎可以从 x509 证书获取公钥信息(如下所述:从 X.509 证书中提取 PEM 公钥),尽管我不确定这是否可以从 Cloudflare 工作人员那里获得。
在
importPublicKey
中,您告诉 import
方法,密钥采用原始格式,并且它是 HMAC
密钥。这意味着 crypto 将您的密钥视为对称 HMAC 密钥,而不是公钥。根据文档:https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo您应该使用spki
格式,因为这是导入公钥的格式。您必须预先知道 JWT 访问令牌是使用 RSA 还是椭圆曲线算法进行签名。 (例如检查 alg
标题声明)
您正在使用
sign
方法来验证签名。事情不是这样的。您应该使用 verify
的 crypto.subtle
方法,此方法将为您验证签名。
我认为您不应该尝试手动验证 JWT,因为您很可能会做错(并为您的应用程序带来安全问题)。您应该使用处理 JWT 签名验证的库。这对您来说会更容易,对您的应用程序来说也会更安全。您必须弄清楚的一件事是您应该从哪里获取公钥。
您可以使用 @codehelios/verify-tokenid 库来验证 Cloudflare Workers 上的 Firebase ID 令牌。
示例:
import { verifyTokenId } from "@codehelios/verify-tokenid";
const tokenId = "<ID_TOKEN>"
const { isValid, decoded, error } = await verifyTokenId(tokenId, "https://securetoken.google.com/<projectId>", "<projectId>");