新旧加密方式可在thinkphp接口中并存,关键是在路由层或控制器入口通过x-encrypt-version头识别版本,v1兼容默认,v2强制校验;解密须前置、不污染业务层,safeinput()封装双逻辑,签名失败多因时间/编码/上下文问题,上线前需压测、异常测试和灰度验证。

如何让新旧加密方式在同一个ThinkPHP接口里同时生效
关键不是替换,而是路由层或控制器入口处做协议识别——旧请求走老解密逻辑,新请求走新逻辑,两者互不干扰。
- 在
app\common\middleware\AuthCheck中增加前置判断:先读取请求头X-Encrypt-Version或参数encrypt_ver,值为v1或v2 - 不要在模型或服务层做判断,避免污染业务逻辑;解密必须在请求进入控制器前完成
- 若没有版本标识,默认走
v1兼容逻辑,防止老客户端直接报错 - 注意:
input()获取的原始数据需在解密前缓存一份(如$rawInput = file_get_contents('php://input')),否则input()二次调用会为空
ThinkPHP 6.x 中如何安全替换 Request::param() 的解密行为
不能直接重写 Request::param(),它被框架多处依赖;正确做法是封装一个 safeInput() 工具函数,在控制器中显式调用。
- 新建
app\utils\CryptoInput.php,提供静态方法safeInput($version = 'v1'),内部根据版本调用对应解密器 -
v1使用旧的openssl_decrypt+ 固定 IV 和密钥;v2改用 AEAD 模式(如openssl_encrypt(..., 'aes-256-gcm')),并校验 tag - 解密失败时,
v1返回空数组并记录 warn 日志;v2必须抛出HttpException(400, 'Invalid signature'),禁止静默降级 - 切勿在
safeInput()中修改$_POST或$_GET,ThinkPHP 的input()缓存机制会因此错乱
双版本共存时签名验证失败的典型原因和排查路径
90% 的签名失败不是算法问题,而是时间、编码或上下文丢失导致的。
- 检查客户端传的
timestamp是否在服务端接受窗口内(建议 ≤ 300 秒),v2版本必须严格校验,v1可放宽至 600 秒过渡 - 确认签名原文是否包含排序后的参数键值对——
v1按字典序拼接,v2要求 JSON 序列化后 SHA256,且必须使用JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES - 留意
Content-Type: application/json请求下,v1仍尝试解析$_POST导致空签名;应统一用file_get_contents('php://input')原始体 - Log 中出现
openssl_error_string(): OpenSSL error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt,大概率是 IV 不匹配或密文被截断,检查 Base64 解码是否丢失了换行符
上线前必须验证的三个边界场景
平滑升级最怕“看似正常,实则漏单”,这三个点不测,上线后就等报警。
- 混合流量压测:用脚本同时发 1000 个
v1和 1000 个v2请求,观察日志中decrypt_failed计数是否归零,且响应耗时无明显抖动 - 异常组合测试:故意发送
X-Encrypt-Version: v2但 body 用v1格式加密,确认返回 400 而非 500,并记录清晰错误码(如ERR_ENCRYPT_MISMATCH) - 灰度开关验证:在中间件里加一个配置项
enable_v2_only,设为 true 后,所有无版本头的请求也强制走v2,用于最终切换前的全量验证
最易忽略的是时钟同步——v2 签名强依赖时间戳,NTP 未开启或偏差 >5 秒会导致批量失效,别只盯着代码。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











