PHP中如何验证JWT令牌并提取声明?Azure B2C集成问题排查
问题根因排查
重定向到login.php的核心原因
- Azure AD B2C完成登录后的重定向是GET请求,默认不会携带
Authorization请求头,你的代码第一判断逻辑就是该头不存在直接跳转登录页,必然会触发重定向。B2C返回的ID Token/Access Token默认放在URL的id_token/access_token查询参数中,需要从$_GET全局变量读取,而非请求头。 - 代码存在低级拼写错误:你定义的token变量是
$retrievedToken,调用JWT::decode时用的是$retrieveToken,变量名少了末尾的d,会触发未定义变量错误,导致后续逻辑中断。 - 签名算法与密钥规则不匹配:Azure B2C默认使用RS256非对称加密算法签名JWT,你的代码硬编码了HS256和自定义密钥,完全不符合B2C的签名规则,就算拿到正确token也会验证失败。
密钥疑问解答
JWT头部的kid(密钥ID)是B2C系统内部生成的,用于标识签名所用的密钥对,和你脚本里写的自定义密钥没有任何关系。B2C用私钥对JWT签名,你需要从B2C的公开JWKS(JSON Web密钥集)中拿到对应kid的公钥,才能完成签名验证。
修复后的可运行代码
require_once "/home/mydirectory/vendor/autoload.php"; use Firebase\JWT\JWT; use Firebase\JWT\Key; // 1. 从B2C回调的URL参数中获取ID Token if (!isset($_GET['id_token'])) { header('Location: login.php'); exit; } $retrievedToken = $_GET['id_token']; // 2. 替换为你自己的B2C配置参数 $issuer = 'https://example.org/lettersnumbers/v2.0/'; // 你的B2C issuer地址 $tenantName = '你的B2C租户名称'; $policyName = '你的用户流策略名,例如B2C_1_signinsignup'; // 3. 获取B2C公钥列表(生产环境建议对公钥做缓存,避免每次请求拉取) $jwksUrl = "https://{$tenantName}.b2clogin.com/{$tenantName}.onmicrosoft.com/{$policyName}/discovery/v2.0/keys"; $jwks = json_decode(file_get_contents($jwksUrl), true); $keys = []; foreach ($jwks['keys'] as $k) { $publicKey = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----\n" . wordwrap($k['x5c'][0], 64, "\n", true) . "\n-----END PUBLIC KEY-----"); $keys[$k['kid']] = new Key($publicKey, $k['alg']); } try { // 4. 自动匹配对应kid的公钥解码验证JWT $token = JWT::decode($retrievedToken, $keys); $now = new DateTimeImmutable(); // 5. 额外验证发行方、有效期等字段 if ($token->iss !== $issuer || $token->nbf > $now->getTimestamp() || $token->exp < $now->getTimestamp()) { header('HTTP/1.1 401 Unauthorized'); exit; } // 6. 提取目标字段 $firstName = $token->first_name; } catch (Exception $e) { // 验证失败跳转登录 header('Location: login.php'); exit; }
补充说明
你原本的.htaccess配置作用是将Authorization头透传给PHP,仅在请求主动携带该头的场景生效,B2C回调场景不需要用到该配置,可以保留用于后续前后端分离的请求验证。生产环境建议首次拿到token后存入session,避免token长期暴露在URL中。
内容的提问来源于stack exchange,提问作者Phil M
相关产品推荐
相关产品推荐

