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

PHP生成Epic FHIR JWT遇签名验证失败及工具报错问题排查

排查Epic on FHIR JWT生成失败问题

一、手动生成JWT时签名验证失败的问题

核心错误是混淆了签名算法类型:RS384是基于RSA的非对称签名算法,但你用了hash_hmac(HMAC对称签名算法)生成签名,完全不符合RS384的要求。

修正代码步骤:

  1. 替换签名生成逻辑,改用openssl_sign实现RSA签名
  2. 确保私钥是标准PEM格式(包含-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----头尾部)
  3. 确保base64url_encode实现符合JWT标准(替换+为-,/为_,移除末尾的=)

修正后的代码示例:

$header = [
    'alg' => "RS384",
    'typ' => "JWT"
];

$payload = [
    'iss' => 'my-client-id',
    'sub' => 'my-client-id',
    'aud' => "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
    'jti' => (string)strtotime(gmdate("Y-m-d H:i:s")),
    'exp' => strtotime(gmdate("Y-m-d H:i:s")) + 270,
];

// 加载PEM格式的私钥文件,不要直接传入格式不完整的字符串
$privateKey = file_get_contents('/path/to/your/private.key');

// 符合JWT标准的base64url编码实现
function base64url_encode(string $data): string
{
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

$headers_encoded = base64url_encode(json_encode($header));
$payload_encoded = base64url_encode(json_encode($payload));

$signature = '';
// 使用openssl_sign生成RS384签名
openssl_sign($headers_encoded . '.' . $payload_encoded, $signature, $privateKey, OPENSSL_ALGO_SHA384);

$signature_encoded = base64url_encode($signature);

$jwt = $headers_encoded . '.' . $payload_encoded . '.' . $signature_encoded;

二、使用firebase/php-jwt包时的错误问题

你遇到的the private key cannot be coerced和invalid algorithm错误,主要源于两个问题:

  1. 参数顺序完全错误:JWT::encode的正确参数顺序是($payload, $key, $algorithm, $header),你把$header和$payload的位置搞反了
  2. 私钥加载或格式错误:私钥必须是标准PEM格式,且要正确加载(不能直接传入格式不完整的字符串)

修正后的代码示例:

use Firebase\JWT\JWT;

// 加载PEM格式的私钥
$privateKey = file_get_contents('/path/to/your/private.key');

$header = [
    'typ' => "JWT"
];

$payload = [
    'iss' => 'my-client-id',
    'sub' => 'my-client-id',
    'aud' => "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
    'jti' => (string)strtotime(gmdate("Y-m-d H:i:s")),
    'exp' => strtotime(gmdate("Y-m-d H:i:s")) + 270,
];

// 注意参数顺序:payload在前,然后是私钥、算法,最后是header
$jwt = JWT::encode($payload, $privateKey, 'RS384', $header);

额外排查点:

  • 确保PHP的openssl扩展已启用(可通过phpinfo()查看)
  • 私钥不能有多余空格或换行,必须是完整的PEM格式
  • 如果私钥有密码,需在加载时指定:openssl_pkey_get_private($privateKey, 'your-password')

通用排查JWT验证失败的要点

  1. 签名算法匹配:生成时用的RS384要和验证时选择的算法一致
  2. 密钥对应:用生成JWT的私钥对应的公钥在jwt.io上验证
  3. Payload字段正确性:
    • aud必须严格匹配Epic的token端点URL
    • exp必须是UTC时间戳,且未过期
    • jti必须是唯一值(当前时间戳实现没问题)
  4. base64url编码正确性:不能出现+、/、=字符,必须替换为JWT标准字符

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 11:20:27