统一会话失效接口响应

This commit is contained in:
pllx
2026-05-05 21:55:48 +08:00
parent 725a38eac3
commit 64945a973e
2 changed files with 57 additions and 18 deletions
+29 -17
View File
@@ -1,5 +1,10 @@
<?php
/**
* 文件功能:Laravel 应用启动配置。
* 负责注册路由、中间件别名、代理信任规则与全局异常响应格式。
*/
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
@@ -38,8 +43,12 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->redirectGuestsTo('/');
})
->withExceptions(function (Exceptions $exceptions): void {
$isChatAjaxRequest = static function (Request $request): bool {
return $request->expectsJson() && $request->is(
$isJsonSessionRequest = static function (Request $request): bool {
if ($request->expectsJson() || $request->ajax()) {
return true;
}
return $request->is(
'room/*/send',
'room/*/heartbeat',
'room/*/leave',
@@ -51,25 +60,28 @@ return Application::configure(basePath: dirname(__DIR__))
);
};
// 聊天室 AJAX 接口:CSRF token 过期(419)时,返回 JSON 提示而非重定向
// 防止浏览器收到 302 后以 GET 方式重请求只允许 POST 的路由,产生 405 错误
$exceptions->render(function (TokenMismatchException $e, Request $request) use ($isChatAjaxRequest) {
if ($isChatAjaxRequest($request)) {
return response()->json([
'status' => 'error',
'message' => '页面已过期,请刷新后重试。',
], 419);
$expiredSessionResponse = static function () {
return response()->json([
'status' => 'error',
'code' => 'SESSION_EXPIRED',
'message' => '登录状态已失效,请刷新页面后重新登录。',
'reload' => true,
'login_url' => route('home'),
], 419);
};
// CSRF token 失效通常意味着页面还停留在旧会话里;JSON 请求统一返回业务提示,避免泄露框架异常堆栈。
$exceptions->render(function (TokenMismatchException $e, Request $request) use ($expiredSessionResponse, $isJsonSessionRequest) {
if ($isJsonSessionRequest($request)) {
return $expiredSessionResponse();
}
});
// Laravel 在某些环境下会先把 TokenMismatchException 包装成 419 HttpException
// 这里补一层兜底,确保聊天接口始终返回稳定 JSON,而不是默认 HTML 错误页
$exceptions->render(function (HttpExceptionInterface $e, Request $request) use ($isChatAjaxRequest) {
if ($e->getStatusCode() === 419 && $isChatAjaxRequest($request)) {
return response()->json([
'status' => 'error',
'message' => '页面已过期,请刷新后重试。',
], 419);
// 这里补一层兜底,确保接口始终返回稳定 JSON,而不是默认异常结构
$exceptions->render(function (HttpExceptionInterface $e, Request $request) use ($expiredSessionResponse, $isJsonSessionRequest) {
if ($e->getStatusCode() === 419 && $isJsonSessionRequest($request)) {
return $expiredSessionResponse();
}
});
})->create();
+28 -1
View File
@@ -25,6 +25,7 @@ use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
@@ -779,10 +780,36 @@ class ChatControllerTest extends TestCase
\Illuminate\Testing\TestResponse::fromBaseResponse($response)->assertStatus(419)->assertJson([
'status' => 'error',
'message' => '页面已过期,请刷新后重试。',
'code' => 'SESSION_EXPIRED',
'message' => '登录状态已失效,请刷新页面后重新登录。',
'reload' => true,
]);
}
/**
* 测试掉线后的普通 JSON 接口遇到 CSRF 失效时,不会泄露框架异常结构。
*/
public function test_json_token_mismatch_exception_renders_session_expired_response(): void
{
$request = Request::create('/user/profile', 'POST', server: [
'HTTP_ACCEPT' => 'application/json',
]);
$response = $this->app->make(\Illuminate\Contracts\Debug\ExceptionHandler::class)
->render($request, new TokenMismatchException('CSRF token mismatch.'));
\Illuminate\Testing\TestResponse::fromBaseResponse($response)
->assertStatus(419)
->assertJson([
'status' => 'error',
'code' => 'SESSION_EXPIRED',
'message' => '登录状态已失效,请刷新页面后重新登录。',
'reload' => true,
])
->assertJsonMissingPath('exception')
->assertJsonMissingPath('trace');
}
/**
* 测试房间公告更新广播中的动态内容会被转义。
*/