PHP生成Epic FHIR JWT遇签名验证失败及工具报错问题排查
排查Epic on FHIR JWT生成失败问题
一、手动生成JWT时签名验证失败的问题
核心错误是混淆了签名算法类型:RS384是基于RSA的非对称签名算法,但你用了hash_hmac(HMAC对称签名算法)生成签名,完全不符合RS384的要求。
修正代码步骤:
- 替换签名生成逻辑,改用
openssl_sign实现RSA签名 - 确保私钥是标准PEM格式(包含
-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----头尾部) - 确保
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错误,主要源于两个问题:
- 参数顺序完全错误:
JWT::encode的正确参数顺序是($payload, $key, $algorithm, $header),你把$header和$payload的位置搞反了 - 私钥加载或格式错误:私钥必须是标准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验证失败的要点
- 签名算法匹配:生成时用的RS384要和验证时选择的算法一致
- 密钥对应:用生成JWT的私钥对应的公钥在jwt.io上验证
- Payload字段正确性:
aud必须严格匹配Epic的token端点URLexp必须是UTC时间戳,且未过期jti必须是唯一值(当前时间戳实现没问题)
- base64url编码正确性:不能出现
+、/、=字符,必须替换为JWT标准字符
内容的提问来源于stack exchange,提问作者ternary
相关产品推荐
相关产品推荐

