CodeIgniter 4 REST API认证过滤器异常:篡改JWT令牌后无法返回自定义响应
解决CodeIgniter 4认证过滤器篡改令牌返回500错误的问题
问题回顾
你在CI4开发REST API的认证过滤器时遇到了一个头疼的问题:令牌有效或者没传令牌时都正常,但篡改令牌内容后,本该返回自定义的401响应,结果却出现了500错误,控制台里满是JSON解析失败的报错。
你遇到的错误日志(简化后):
Fatal error: Uncaught CodeIgniter\Format\Exceptions\FormatException: Failed to parse json string, error: "Malformed UTF-8 characters, possibly incorrectly encoded".
你当前使用的app/Filters/Auth.php代码:
<?php namespace App\Filters; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Firebase\JWT\JWT; use Firebase\JWT\Key; class Auth implements FilterInterface { public function before(RequestInterface $request, $arguments = null) { $key = getenv('JWT_SECRET'); $header = $request->getServer('HTTP_AUTHORIZATION'); $token = null; if (!empty($header)) { $token = explode(' ', $header)[1]; } if (is_null($token) || empty($token)) { $response = [ 'status' => 401, 'error' => true, 'message' => 'Token required.', 'data' => [] ]; echo json_encode($response); exit(); } try { JWT::decode($token, new Key($key, 'HS256')); } catch (Exception $ex) { $response = [ 'status' => 401, 'error' => true, 'message' => 'Access denied.', 'data' => [] ]; echo json_encode($response); exit(); } } public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { // } }
问题根源
我帮你排查了下,问题主要出在两个地方:
- 异常捕获不全:你只捕获了
Exception,但Firebase JWT库抛出的是DomainException、UnexpectedValueException这类更具体的异常,而且你没引入对应的命名空间,导致这些异常没被捕获,触发了CI4的全局异常处理。 - 响应方式不规范:你直接用
echo json_encode()加exit()的方式返回结果,这种写法绕过了CI4的响应处理流程,当有未捕获的异常时,系统会尝试格式化响应,进而引发UTF-8编码错误。
修复方案
下面是修正后的代码,我标注了关键修改点:
<?php namespace App\Filters; use CodeIgniter\Filters\FilterInterface; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use Firebase\JWT\JWT; use Firebase\JWT\Key; // 1. 引入JWT所有可能抛出的异常类 use Firebase\JWT\ExpiredException; use Firebase\JWT\InvalidArgumentException; use DomainException; use UnexpectedValueException; class Auth implements FilterInterface { public function before(RequestInterface $request, $arguments = null) { $key = getenv('JWT_SECRET'); $header = $request->getServer('HTTP_AUTHORIZATION'); $token = null; if (!empty($header)) { // 额外加个判断,确保Authorization头格式是Bearer xxxx $parts = explode(' ', $header); if (count($parts) === 2 && strtolower($parts[0]) === 'bearer') { $token = $parts[1]; } else { return $this->sendUnauthorized('Invalid authorization header format.'); } } if (is_null($token) || empty($token)) { return $this->sendUnauthorized('Token required.'); } try { JWT::decode($token, new Key($key, 'HS256')); } catch (ExpiredException $ex) { // 单独捕获令牌过期的情况,返回更友好的提示 return $this->sendUnauthorized('Token has expired.'); } catch (InvalidArgumentException | DomainException | UnexpectedValueException $ex) { // 2. 捕获所有JWT解码失败的异常 return $this->sendUnauthorized('Access denied.'); } } public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { // 无需修改 } /** * 3. 封装统一的401响应方法,符合CI4规范 * @param string $message * @return ResponseInterface */ private function sendUnauthorized(string $message): ResponseInterface { $response = service('response'); $response->setStatusCode(ResponseInterface::UNAUTHORIZED); $response->setJSON([ 'status' => 401, 'error' => true, 'message' => $message, 'data' => [] ]); return $response; } }
修改说明
- 捕获全部异常:把Firebase JWT可能抛出的所有异常都引入并捕获,确保不会有未处理的异常漏出去触发全局错误。
- 规范响应方式:用CI4官方的
service('response')来构建响应,设置状态码和JSON内容后返回,完全遵循CI4的请求处理流程,不会再出现编码问题。 - 封装响应逻辑:把重复的401响应代码封装成私有方法,后续修改响应格式只需要改这一处,更便于维护。
这样修改后,再去篡改令牌测试,就能得到你想要的自定义401响应,不会再出现500错误了。
内容的提问来源于stack exchange,提问作者Lidia K.
相关产品推荐
相关产品推荐

