PHP开发JWT认证API时try块异常被捕获后仍继续执行问题
问题根因排查
1. 你观察到的执行顺序是假象,实际是收到了两次请求
浏览器在触发跨域请求前会自动发送OPTIONS类型的预检请求,这类请求不会携带自定义的Authorization头:
- 你的代码没有单独处理OPTIONS请求,所以预检请求走到JWT校验逻辑时抛出异常,返回401,这就是你看到的控制台第一个401警告
- 你的服务端没有正确响应预检请求(正常逻辑是OPTIONS请求直接返回200,不需要走业务逻辑和鉴权),部分浏览器在非严格模式下依然会继续发送实际的GET请求,GET请求如果实际携带了JWT,就会正常返回数据,这就是后续打印的结果来源
你可以在服务端代码最顶部加请求日志,打印请求方法和请求时间,就能确认是两次不同的请求。
2. catch块未终止脚本执行是核心隐患
就算排除两次请求的问题,你现在的catch块代码本身就有问题:
catch (Exception $e) { setResponseCode(401, "Access denied: " . $e->getMessage()); // 缺少终止执行的语句! }
setResponseCode本质只是设置HTTP响应头,不会中断代码运行,如果你后续还有其他全局逻辑、注册了register_shutdown_function回调,或者代码结构有嵌套,就可能出现异常被捕获后依然输出其他内容的问题,必须在catch块最后加exit或者die终止脚本。
3. 异常类型不匹配导致部分错误无法捕获
如果你用的是6.0以上版本的firebase/php-jwt库,它抛出的异常不是继承自根Exception类,而是实现了Throwable接口的专用异常类,比如SignatureInvalidException、BeforeValidException等,你只捕获Exception会导致这些异常直接抛出到全局,不会进入你的catch块。如果你开启了全局错误处理把错误吞了,就会继续执行后续代码。
建议把catch的类型改成Throwable,或者显式捕获所有JWT相关异常:
// 导入需要捕获的异常类 use Firebase\JWT\SignatureInvalidException; use Firebase\JWT\BeforeValidException; use Firebase\JWT\ExpiredException; use DomainException; use InvalidArgumentException; use UnexpectedValueException; try { $decoded = JWT::decode($jwt, $access_key, array('HS256')); // 业务逻辑 } catch (Throwable $e) { // 兼容所有PHP7+版本的异常/错误 setResponseCode(401, "Access denied: " . $e->getMessage()); exit; // 必须终止执行 }
4. $jwt变量未初始化的Notice错误被忽略
你现在的代码只有在收到Authorization头的时候才会给$jwt赋值,没有头的话$jwt是未定义变量,PHP默认会抛出Notice级别的错误,如果你的PHP配置里error_reporting关闭了Notice报错,那么$jwt会被当作空字符串传入decode方法,部分旧版本的JWT库可能不会对空字符串抛异常,导致后续逻辑继续执行。
建议在遍历header之前先初始化$jwt = '';,没有拿到Authorization头的时候直接返回401,不用进decode逻辑。
修复步骤
- 加OPTIONS请求判断,在header设置之后直接加:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
- 初始化$jwt变量,没拿到Authorization头直接返回401
- 修复catch块,加终止执行语句,调整异常捕获类型
- 验证请求日志,确认只有一次GET请求返回结果
内容的提问来源于stack exchange,提问作者Sean

