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

PHP中web-token/jwt-framework解码JWE耗时过长求助

JWE解密耗时过长(13分钟)的优化方案(Totara15 + web-token/jwt-framework 2.2.11)

问题背景

基于Totara15(Moodle衍生系统)开发OAuth2登录流程,UserInfo接口返回内容采用JWE加密(对方使用我方公钥加密),需用我方私钥解密后完成登录。当前使用web-token/jwt-framework 2.2.11可成功解密,但单次解密耗时长达13分钟,仅$jweLoader->loadAndDecryptWithKey()这一行代码占用全部耗时,其余步骤仅需毫秒级。由于Totara15不支持PHP8.1,无法升级框架至最新版本。

核心瓶颈分析

排查后确定是RSA私钥解密的性能问题:

  • web-token框架的RSAOAEP算法默认优先使用ext-openssl扩展实现,若未启用扩展则会降级为纯PHP实现,纯PHP的RSA解密(尤其是4096位密钥)速度极慢;
  • 代码中存在不必要的组件加载,增加了框架内部的判断开销。

优化建议

1. 启用PHP OpenSSL扩展(最关键)

纯PHP实现的RSA解密速度是数量级的差距,必须确保ext-openssl已启用:

  • 执行php -m查看是否包含openssl模块;
  • 若未启用,修改php.ini添加extension=openssl并重启PHP服务;
  • 可在代码中添加var_dump(extension_loaded('openssl'));验证是否生效。

2. 缓存JWK对象,避免重复加载密钥

每次调用解密函数都重新读取密钥文件并生成JWK对象,虽耗时不高,但可通过缓存减少重复操作:

  • 使用静态变量或Totara/Moodle的缓存API(cache::make)按kid缓存JWK实例,避免重复初始化。

3. 精简框架配置,移除无用组件

当前代码引入了未使用的算法和压缩方法,精简后可减少框架内部的判断开销:

  • 仅保留实际使用的加密算法(RSAOAEP、A128CBCHS256),移除A256KW、A256CBCHS512等未使用的算法实例;
  • 若确认JWE未使用Deflate压缩,可直接移除CompressionMethodManager的初始化。

4. 添加HeaderCheckerManager提前验证算法

提前验证JWE的alg/enc头,避免框架尝试不必要的解密算法,减少无效操作:

  • 初始化HeaderCheckerManager并指定允许的算法列表,传入JWELoader。

5. 测试替代方案

若上述优化仍无法满足性能要求,可尝试:

  • 手动用OpenSSL解密:绕过框架 overhead,直接处理JWE的Compact格式(拆分5个部分,用OpenSSL解密加密密钥,再解密载荷);
  • 测试其他框架:如namshi/jose(支持JWE且兼容PHP7.4),但需注意API差异。

优化后的代码示例

<?php

namespace local_oauth2_extension;

require_once(__DIR__ . '/../vendor/autoload.php');

use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A128CBCHS256;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\Compression\Deflate;
use Jose\Component\Encryption\JWEDecrypter;
use Jose\Component\Core\JWK;
use Jose\Component\Encryption\Serializer\JWESerializerManager;
use Jose\Component\Encryption\Serializer\CompactSerializer;
use Jose\Component\Encryption\JWELoader;
use Jose\Component\Checker\HeaderCheckerManager;
use Jose\Component\Checker\AlgorithmChecker;

class jwe_helper
{
    public static function decode_response($token, $kid, $pem_path)
    {
        try{
            // 按kid缓存JWK,避免重复加载密钥文件
            static $jwkCache = [];
            if (!isset($jwkCache[$kid])) {
                $jwkCache[$kid] = JWKFactory::createFromKeyFile(
                    $pem_path,
                    null,
                    ['use' => 'enc', 'kid' => $kid]
                );
            }
            $jwk = $jwkCache[$kid];

            // 仅保留实际使用的加密算法
            $keyEncryptionAlgorithmManager = new AlgorithmManager([
                new RSAOAEP()
            ]);
            $contentEncryptionAlgorithmManager = new AlgorithmManager([
                new A128CBCHS256()
            ]);

            // 若JWE未使用压缩,可注释以下两行
            $compressionMethodManager = new CompressionMethodManager([
                new Deflate(),
            ]);

            // 提前验证JWE的算法头,避免无效尝试
            $headerCheckerManager = new HeaderCheckerManager(
                [new AlgorithmChecker(['RSA-OAEP'])],
                ['enc']
            );

            $jweDecrypter = new JWEDecrypter(
                $keyEncryptionAlgorithmManager,
                $contentEncryptionAlgorithmManager,
                isset($compressionMethodManager) ? $compressionMethodManager : null
            );

            $serializerManager = new JWESerializerManager([
                new CompactSerializer(),
            ]);

            $jweLoader = new JWELoader(
                $serializerManager,
                $jweDecrypter,
                $headerCheckerManager
            );
    
            $jwe = $jweLoader->loadAndDecryptWithKey($token, $jwk, $recipient);
            $payload = base64_decode($jwe->getPayload());
            $userinfo = self::extract_userinfo($payload);
        
            return $userinfo;
        } catch (\Exception $exception) {
            // 建议记录错误日志以便排查
            // error_log('JWE解密失败: ' . $exception->getMessage());
            return $token;
        }
    }

    private static function extract_userinfo($payload)
    {
        // 此处实现你的用户信息提取逻辑
        return json_decode($payload, true);
    }
}

额外调试技巧

  • 查看密钥长度:var_dump($jwk->get('n')->bitLength());,4096位密钥在无openssl扩展时解密会极慢;
  • 用OpenSSL命令行测试解密速度:提取JWE的第二部分(加密密钥),执行openssl rsautl -decrypt -inkey private.pem -oaep -in encrypted_key.bin,若命令行速度快则说明PHP的openssl扩展未启用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:35:15