PHP开发JWT认证REST API:Access Token过期后使用Refresh Token获取新Token异常排查
问题分析与修复方案
看起来你的代码里有几个关键错误导致了这个问题,我帮你逐一拆解:
1. Refresh Token解码时的参数顺序错误
这是直接导致你触发echo "i am here"的核心原因!在解码Refresh Token时,你把算法参数当成了密钥传入了Key类:
// 错误写法:重复传入算法,完全没用到Refresh Token的密钥 $decode_refresh = JWT::decode($databody->refresh_token, new key($usr_refresh_algorthim,$usr_refresh_algorthim));
正确的写法应该和你解码Access Token的格式一致——密钥在前,算法在后:
// 假设你有专门的Refresh Token密钥$secret_refresh_key $decode_refresh = JWT::decode($databody->refresh_token, new Key($secret_refresh_key, $usr_refresh_algorthim));
这个错误会直接导致Refresh Token解码失败,抛出非"Expired token"的异常,从而进入catch块的else分支,执行echo "i am here"。
2. 访问了未定义的$decoded_data变量
当Access Token过期抛出异常后,第一个try块里的$decoded_data并没有被成功赋值,但你在Refresh Token的处理逻辑里依然直接使用:
$ruserid = $decoded_data->data->id;
这会触发PHP报错,而且你没有捕获这个错误,后续逻辑完全混乱。正确的做法是从解码后的Refresh Token里获取用户信息:
// 从Refresh Token的解码结果里提取用户ID和数据 $ruserid = $decode_refresh->data->id; $user_arr_data = $decode_refresh->data;
3. 依赖异常消息字符串判断过期不可靠
你通过$ex->getMessage() == "Expired token"来判断token过期,这非常脆弱——比如常用的firebase/php-jwt库,抛出的过期异常是特定的ExpiredException类,而不是靠固定字符串匹配。正确的做法是捕获特定异常类:
// 先引入对应的异常类 use Firebase\JWT\ExpiredException; use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; // 然后在catch里区分异常类型 catch(ExpiredException $ex){ // 处理Access Token过期的逻辑 } catch(SignatureInvalidException $ex){ // 处理签名无效的情况 } catch(BeforeValidException $ex){ // 处理token还未生效的情况 } catch(Exception $ex){ // 处理其他未知异常 }
这样能避免因为库版本更新、消息字符串变化导致的逻辑错误。
4. $user_arr_data未定义
在生成新的Access Token时,你用到了$user_arr_data但没有定义它,应该从解码后的Refresh Token里提取用户数据,确保新的Access Token包含正确的用户信息。
修复后的完整代码示例
<?php // 确保引入必要的类 use Firebase\JWT\JWT; use Firebase\JWT\Key; use Firebase\JWT\ExpiredException; use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; if($_SERVER['REQUEST_METHOD'] === "POST"){ $all_headers = getallheaders(); $data = new \stdClass(); // 注意去掉Authorization头里可能存在的Bearer前缀 $data->jwt = isset($all_headers["Authorization"]) ? str_replace('Bearer ', '', $all_headers["Authorization"]) : ''; $databody = json_decode(file_get_contents("php://input")); if(!empty($data->jwt)){ try{ $decoded_data = JWT::decode($data->jwt, new Key($secret_key, $usr_algorthim)); http_response_code(200); $ruserid = $decoded_data->data->id; echo json_encode(array( "status" => 1, "message" => "We got JWT Token.", "user_data" => $decoded_data, "user_id" => $ruserid )); } catch(ExpiredException $ex){ // Access Token过期,尝试用Refresh Token刷新 if(empty($databody->refresh_token)){ http_response_code(401); echo json_encode(array( "status" => 0, "message" => "Refresh Token is required." )); exit; } try{ // 修正参数顺序:密钥在前,算法在后 $decode_refresh = JWT::decode($databody->refresh_token, new Key($secret_refresh_key, $usr_refresh_algorthim)); // 从Refresh Token里获取用户数据 $user_arr_data = $decode_refresh->data; $ruserid = $user_arr_data->id; // 生成新的Access Token $iat = time(); $nbf = $iat; $exp = $iat + 60; // 1分钟有效期 $payload_info = array( "iss"=>$iss, "iat"=>$iat, "nbf"=>$nbf, "exp"=>$exp, "aud"=>$aud, "data"=>$user_arr_data ); $new_jwt = JWT::encode($payload_info, $secret_key, $usr_algorthim); http_response_code(200); echo json_encode(array( "status" => 1, "jwt" => $new_jwt, "message" => "Access Token refreshed successfully.", "user_id" => $ruserid )); } catch(ExpiredException $exrefresh){ // Refresh Token也过期了 http_response_code(401); echo json_encode(array( "status" => 0, "message" => "Your session has expired. Please login again." )); } catch(Exception $exrefresh){ // 其他Refresh Token相关错误(比如签名无效) http_response_code(401); echo json_encode(array( "status" => 0, "message" => "Invalid Refresh Token: " . $exrefresh->getMessage() )); } } catch(SignatureInvalidException $ex){ http_response_code(401); echo json_encode(array( "status" => 0, "message" => "Invalid Access Token signature." )); } catch(BeforeValidException $ex){ http_response_code(401); echo json_encode(array( "status" => 0, "message" => "Access Token is not yet valid." )); } catch(Exception $ex){ http_response_code(401); echo json_encode(array( "status" => 0, "message" => "Invalid Access Token: " . $ex->getMessage() )); } } else { http_response_code(400); echo json_encode(array( "status" => 0, "message" => "Access Token is required." )); } } ?>
额外注意事项
- 确保
$secret_refresh_key是专门用于Refresh Token的密钥,不要和Access Token的密钥混用,提升安全性。 getallheaders()在Nginx环境下可能需要额外配置才能正常获取Authorization头,你也可以用$_SERVER['HTTP_AUTHORIZATION']替代。- 前端传递Authorization头时,通常会带上
Bearer前缀,记得在代码里去掉,否则会导致解码失败。
内容的提问来源于stack exchange,提问作者Khasim Md
相关产品推荐
相关产品推荐

