You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:47:51