@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
145 lines (119 loc) • 4.06 kB
text/typescript
import { TRPCError } from '@trpc/server';
import debug from 'debug';
import { importJWK, jwtVerify } from 'jose';
import { oidcEnv } from '@/envs/oidc';
const log = debug('oidc-jwt');
/**
* 从环境变量中获取 JWKS
* 该 JWKS 是一个包含 RS256 私钥的 JSON 对象
*/
export const getJWKS = (): object => {
try {
const jwksString = oidcEnv.OIDC_JWKS_KEY;
if (!jwksString) {
throw new Error(
'OIDC_JWKS_KEY 环境变量是必需的。请使用 scripts/generate-oidc-jwk.mjs 生成 JWKS。',
);
}
// 尝试解析 JWKS JSON 字符串
const jwks = JSON.parse(jwksString);
// 检查 JWKS 格式是否正确
if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {
throw new Error('JWKS 格式无效: 缺少或为空的 keys 数组');
}
// 检查是否有 RS256 算法的密钥
const hasRS256Key = jwks.keys.some((key: any) => key.alg === 'RS256' && key.kty === 'RSA');
if (!hasRS256Key) {
throw new Error('JWKS 中没有找到 RS256 算法的 RSA 密钥');
}
return jwks;
} catch (error) {
console.error('解析 JWKS 失败:', error);
throw new Error(`OIDC_JWKS_KEY 解析错误: ${(error as Error).message}`);
}
};
const getVerificationKey = async () => {
try {
const jwksString = oidcEnv.OIDC_JWKS_KEY;
if (!jwksString) {
throw new Error('OIDC_JWKS_KEY 环境变量未设置');
}
const jwks = JSON.parse(jwksString);
if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {
throw new Error('JWKS 格式无效: 缺少或为空的 keys 数组');
}
const privateRsaKey = jwks.keys.find((key: any) => key.alg === 'RS256' && key.kty === 'RSA');
if (!privateRsaKey) {
throw new Error('JWKS 中没有找到 RS256 算法的 RSA 密钥');
}
// 创建一个只包含公钥组件的“纯净”JWK对象。
// RSA公钥的关键字段是 kty, n, e。其他如 kid, alg, use 也是公共的。
const publicKeyJwk = {
alg: privateRsaKey.alg,
e: privateRsaKey.e,
kid: privateRsaKey.kid,
kty: privateRsaKey.kty,
n: privateRsaKey.n,
use: privateRsaKey.use,
};
// 移除任何可能存在的 undefined 字段,保持对象干净
Object.keys(publicKeyJwk).forEach(
(key) => (publicKeyJwk as any)[key] === undefined && delete (publicKeyJwk as any)[key],
);
// 现在,无论在哪个环境下,`importJWK` 都会将这个对象正确地识别为一个公钥。
return await importJWK(publicKeyJwk, 'RS256');
} catch (error) {
log('获取 JWKS 公钥失败: %O', error);
throw new Error(`JWKS 公key获取失败: ${(error as Error).message}`);
}
};
/**
* 验证 OIDC JWT Access Token
* @param token - JWT access token
* @returns 解析后的 token payload 和用户信息
*/
export const validateOIDCJWT = async (token: string) => {
try {
log('开始验证 OIDC JWT token');
// 获取公钥
const publicKey = await getVerificationKey();
// 验证 JWT
const { payload } = await jwtVerify(token, publicKey, {
algorithms: ['RS256'],
// 可以添加其他验证选项,如 issuer、audience 等
});
log('JWT 验证成功,payload: %O', payload);
// 提取用户信息
const userId = payload.sub;
const clientId = payload.aud;
if (!userId) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'JWT token 中缺少用户 ID (sub)',
});
}
return {
clientId,
payload,
tokenData: {
aud: clientId,
client_id: clientId,
exp: payload.exp,
iat: payload.iat,
jti: payload.jti,
scope: payload.scope,
sub: userId,
},
userId,
};
} catch (error) {
if (error instanceof TRPCError) {
throw error;
}
log('JWT 验证失败: %O', error);
throw new TRPCError({
code: 'UNAUTHORIZED',
message: `JWT token 验证失败: ${(error as Error).message}`,
});
}
};