先强调一个基本认知:PHP 本身不内置 OpenID Connect 支持,必须依赖第三方库来实现。直接手写 JWT 解析、JWKS 获取、nonce 校验这些,风险极高,容易出安全漏洞,千万别尝试。

用 web-token/jwt-framework 还是 league/oauth2-client?
这两个库的定位完全不同,选错了后续容易反复踩坑。简单来说:
league/oauth2-client是通用 OAuth 2.0 客户端,OpenID Connect 属于它的扩展协议,需要配合league/oauth2-openid-connect使用,或者手动补全 ID Token 的验证逻辑。web-token/jwt-framework专注于 JWT/JOSE 标准,适合在已经拿到id_token后做深度校验,比如自定义azp检查、多密钥轮转等场景。但它不处理授权码交换流程。- 生产环境下的推荐组合:用
league/oauth2-client+league/oauth2-openid-connect处理登录流程,再配合web-token/jwt-framework做 ID Token 的细粒度验证。这样分工明确,各司其职。
必须手动实现的三个关键校验点
很多教程会跳过这一步,结果上线后出现被伪造 id_token 绕过认证的情况。这三点必须严格检查:
iss必须严格匹配你配置的 OIDC 提供方 issuer,比如https://accounts.google.com。不能只检查是否以该域名开头,必须完全一致。aud要校验是否包含你的客户端 ID。如果提供方返回了azp(Authorized Party),当aud !== azp时,必须直接拒绝。nonce必须在发起授权请求时生成并存入 session,收到id_token后逐字节比对,并且用完即销毁。漏掉这一点,重放攻击可以直接登录他人账号。
getJwksUri() 返回 403 或解析失败怎么办?
这是 OIDC 集成中最常见的卡点,原因比较具体:
- 部分提供商(如 Auth0)要求在 JWKS 请求头中带上
Accept: application/json,否则返回 403。web-token/jwt-framework默认不加这个头,需要手动传$httpOptions = ['headers' => ['Accept' => 'application/json']]。 - Key ID(
kid)在 JWT header 中存在,但 JWKS 响应里找不到对应的 key?检查 provider 是否启用了密钥轮转,或者你缓存了过期的 JWKS JSON。建议加 TTL 缓存(比如 1 小时),但首次加载必须实时获取。 - JWT 使用
RS256签名,你却尝试用HS256验证?查看id_token的 header 部分(base64url 解码前两个.之间的字符串),确认alg字段值。
真正麻烦的不是调通登录,而是后续每次请求都要复用同一套 JWKS 加载、key 查找、签名验证逻辑。把验证器封装成独立的 service 类,比散落在 controller 里更容易维护和测试。