Secrets 与 OIDC 联邦认证
解读
在国内一线互联网公司的 PHP 后端面试中,这道题表面问“密钥怎么存、OIDC 怎么用”,实质考察候选人是否具备“零信任”安全思维:
- 能否把硬编码在 config.php 里的 DB_PASS、JWT_SECRET 从代码仓库剥离;
- 能否用 OIDC 把内部员工、第三方 SaaS、小程序用户统一接入,避免每个系统再写一套登录;
- 能否在 PHP-FPM 容器化、K8s 多集群、DevOps 自动化场景下,把“拿密钥”和“验身份”做成平台能力,而不是业务硬编码。
答不出“密钥轮换”“联邦令牌吊销”“OP Cache 重启不掉密钥”这些点,基本会被判定为“只会写 CURD,不懂工程化”。
知识点
-
Secrets 管理
1.1 分级:静态(数据库口令、API Key)、动态(临时 STS、短寿命 JWT)、加密(国密 SM4、AES-256-GCM)。
1.2 存储:
- 单机开发:phpdotenv + .env 文件 600 权限;
- 生产容器:K8s Secret + sealed-secrets 加密落盘;
- 云原生:阿里云 KMS、腾讯云 SSMS、华为云 CSMS,通过 SDK 获取,不落盘;
- 合规场景:金融项目要求硬件加密机(HSM)或国密卡,PHP 通过 PKCS#11 扩展调用。
1.3 注入:
- php-fpm 池配置 env[DB_PASS] = $SECRET,避免 phpinfo 泄露;
- 使用 s6-overlay 或 initContainer 把密钥挂到 /dev/shm,容器重启即失;
- 支持热更新:Laravel 8+ 的 config:cache 结合 inotify 重新加载,无需重启 php-fpm。
1.4 轮换:
- 双签名机制:旧密钥验证历史 JWT,新密钥签发新 JWT,24h 灰度切换;
- 数据库账号:通过 Aliyun RDS 的账号回收站,先建新账号→改 Secrets→滚动重启 Pod→删旧账号。 -
OIDC 联邦认证
2.1 角色:OP(OpenID Provider)、RP(Relying Party,即 PHP 应用)、IdP(企业微信、钉钉、飞书、Keycloak、Authing)。
2.2 协议流程:
授权码模式:
① 用户访问 RP → 302 到 IdP 授权端点(带 client_id、scope=openid、response_type=code、state、PKCE 的 code_challenge);
② 用户扫码/账密登录后 IdP 返回 code;
③ RP 用 code + code_verifier 换 id_token、access_token;
④ RP 用 IdP 的 jwks_uri 公钥本地验证 id_token(避免每次请求远程 introspect);
⑤ 根据 claims(sub、name、department)生成本地 Session 或 JWT,完成联邦登录。
2.3 PHP 实现:
- 基础包:firebase/php-jwt + league/oauth2-client,支持 PKCE;
- 框架级:Laravel Socialite 的“socialiteproviders/wechat”或“socialiteproviders/dingtalk”插件,30 分钟可对接钉钉扫码;
- 企业级:Symfony OIDC Bundle 支持 role mapping,可把 IdP 返回的 department 直接映射为 Symfony 的 ROLE_HR、ROLE_FINANCE。
2.4 安全加固:
- state、nonce 防重放;
- 强制 HTTPS,HSTS 365 天;
- id_token 有效期 ≤5 分钟,access_token ≤15 分钟,刷新走 refresh_token 轮换;
- 国密合规:使用 SM2 签名时,需把 IdP 的 JWKS 换成 X.509 证书,PHP 通过 gmssl 扩展验签。
2.5 高可用:
- IdP 多活:企业微信北京/上海双机房,RP 侧做断路器,超时 1s 自动降级到本地缓存公钥;
- 证书轮换:IdP 提前 30 天在 jwks_uri 推送新 kid,RP 后台定时任务(Laravel Schedule)拉取并热更新,实现零停机。 -
PHP 侧常见坑
- php-fpm 子进程重启导致 /dev/shm 密钥丢失,需要 initContainer 重新写入;
- putenv 线程不安全,生产环境禁用,改用 getenv 或 $_ENV;
- Laravel config:cache 会把 .env 快照到 bootstrap/cache/config.php,更新 Secrets 后必须重新缓存;
- 使用 OPcache 预加载时,config.php 被常驻内存,需 kill -USR2 php-fpm 才能重载;
- 阿里云 KMS 返回的 Plaintext 是 base64,需 base64_decode 后再用,否则 PDO 连接数据库报 “Access denied”。
答案
“在 PHP 项目里,我们把 Secrets 和 OIDC 分成两条线治理:
- Secrets 侧,代码仓库里只有占位符。开发阶段用 .env,CI 阶段通过 GitLab Variable 注入;生产容器跑在阿里 ACK 上,敏感字段全走阿里云 KMS,通过官方 SDK alibabacloud/kms-2016-01-20 获取,拿到后写内存 /dev/shm,php-fpm 通过 getenv 读取,不落盘、不进镜像、不进 Git。密钥轮换用双签名,JWT 的 kid 指向新版本,24 小时灰度后旧密钥下线。
- OIDC 联邦侧,我们统一用 Authing 做 IdP,内部员工、企业微信、小程序扫码都走同一套 OP。PHP 端基于 league/oauth2-client 写了一个 AuthingProvider,授权码模式 + PKCE,id_token 本地用 firebase/php-jwt 验签,公钥每小时拉一次 JWKS。验签成功后,把 sub 作为用户主键,department 字段映射成本地角色,写入 Redis Session,前后端分离项目返回同域 JWT,HttpOnly + SameSite=Strict。
- 两者结合:PHP 拿到的 KMS 密钥里,有一项是 OIDC Client Secret,它本身也是 Secrets,通过同一套流程注入,实现‘密钥也受密钥管理’。整套方案上线后,密钥轮换零中断,联邦登录 1.2s 内完成,等保三级和国密测评一次通过。”
拓展思考
- 如果公司收购了一家使用 .NET 的子系统,如何让 PHP 与 .NET 共用同一套 OIDC 联邦,并做到跨域注销(RP-Initiated Logout)?
- 在“两地三中心”容灾场景下,KMS 密钥与 IdP 的 JWKS 如何做到跨 Region 同步,同时满足《个人信息保护法》数据不出境的要求?
- 当 PHP 应用需要调用第三方国密算法(SM2/SM3/SM4)的 HSM,而 HSM 只提供 JNI 接口时,如何用 PHP 的 FFI 或编写一个 Swoole 协程扩展,实现异步非阻塞调用,避免阻塞 php-fpm 进程?