Laravel Sanctum测试API密码确认接口遇Session未设置异常
解决Laravel Sanctum API测试密码确认接口时的Session异常问题
我太懂你这个痛点了!用Laravel Sanctum做API认证,测试密码确认接口时偏偏碰到RuntimeException: Session store not set on request的报错,排查后发现是框架自带的密码确认逻辑依赖Session,但API请求本身是无状态的,根本不支持Session操作对吧?
问题根源
框架默认的PasswordConfirmController里的resetPasswordConfirmationTimeout方法是为**Web端(带Session)**设计的,它会向Session存入auth.password_confirmed_at字段来标记用户已确认密码。但API请求遵循无状态原则,默认没有Session支持,所以当你携带密码发起请求时,走到操作Session的步骤就直接抛出异常了。而你发空请求时,验证逻辑先触发,还没到操作Session的环节,所以能正常返回验证错误。
解决方案
根据API无状态的设计原则,推荐两种处理方式:
方案一:自定义密码确认逻辑(推荐生产环境使用)
完全绕过Session,用缓存来实现密码确认的超时机制:
- 创建自定义的密码确认控制器
<?php namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Validation\ValidationException; class PasswordConfirmController extends Controller { public function confirm(Request $request) { // 验证密码字段 $request->validate([ 'password' => ['required', 'string'], ]); // 校验密码是否正确 if (!Hash::check($request->password, $request->user()->password)) { throw ValidationException::withMessages([ 'password' => __('auth.password'), ]); } // 用缓存存储密码确认状态,有效期和框架默认的Session超时一致(默认30分钟) cache()->put( 'password_confirmed_' . $request->user()->id, true, config('auth.password_timeout', 1800) ); return response()->json(['message' => 'Password confirmed successfully']); } }
- 更新API路由
修改routes/api.php,指向自定义控制器:
use App\Http\Controllers\Auth\PasswordConfirmController; Route::post('/password-confirmation', [PasswordConfirmController::class, 'confirm']) ->middleware('auth:sanctum');
- 自定义中间件验证密码确认状态
如果有需要验证用户已确认密码的接口,创建一个中间件来检查缓存:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class EnsurePasswordIsConfirmed { public function handle(Request $request, Closure $next) { if (!cache()->get('password_confirmed_' . $request->user()->id)) { return response()->json(['message' => 'Please confirm your password first.'], 403); } return $next($request); } }
记得在app/Http/Kernel.php里注册这个中间件,然后在需要的路由上使用。
方案二:测试时临时启用Session(仅用于测试,不推荐生产)
如果只是为了通过测试,不想修改业务逻辑,可以在测试类里给请求手动绑定Session:
<?php namespace Tests\Feature; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ConfirmPasswordControllerTest extends TestCase { use RefreshDatabase; public function test_confirm_password_confirms() { $user = User::factory()->create(['password' => bcrypt('password')]); // 模拟认证用户并绑定空Session $this->actingAs($user, 'sanctum')->withSession([]); $response = $this->postJson('/api/password-confirmation', [ 'password' => 'password', ]); $response->assertStatus(200); // 其他断言逻辑 } }
这个方法只是临时解决测试问题,不符合API无状态的设计理念,生产环境千万别用。
内容的提问来源于stack exchange,提问作者user12003867
相关产品推荐
相关产品推荐

