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