Laravel 10+独立Vue3客户端的Sanctum令牌安全漏洞解决问询
Laravel Sanctum + Vue3 独立客户端认证加固方案
针对你遇到的令牌可被提取滥用的安全问题,以下是一套务实的加固方案,覆盖存储、生命周期、权限、请求验证等核心环节:
1. 切换至HttpOnly Cookie存储令牌(替代localStorage)
Sanctum原生支持基于Cookie的SPA认证,HttpOnly Cookie无法被前端JS读取,从根源上避免XSS攻击窃取令牌:
- 后端配置:
- 在
config/sanctum.php中,将Vue客户端域名加入stateful列表:'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))), - 在
config/session.php中,生产环境设置:'same_site' => 'lax', 'secure' => env('APP_ENV') === 'production',
- 在
- 前端配置:
- Axios开启
withCredentials,自动携带Cookie:import axios from 'axios'; axios.defaults.baseURL = 'https://your-api-domain.com'; axios.defaults.withCredentials = true; - 登录前先获取CSRF令牌:
await axios.get('/sanctum/csrf-cookie'); const loginRes = await axios.post('/login', { email, password });
- Axios开启
2. 启用短期访问令牌+刷新令牌机制
避免永久令牌泄露后被长期滥用:
- 登录接口返回双令牌:
public function login(Request $request) { if (!Auth::attempt($request->only('email', 'password'))) { return response()->json(['message' => 'Invalid credentials'], 401); } $user = Auth::user(); // 15分钟有效期的访问令牌,存在前端内存(如Pinia) $accessToken = $user->createToken('access_token', ['view:posts', 'edit:own-posts'], now()->addMinutes(15))->plainTextToken; // 7天有效期的刷新令牌,存在HttpOnly Cookie $refreshToken = $user->createToken('refresh_token', ['refresh'])->plainTextToken; return response()->json(['access_token' => $accessToken]) ->withCookie(cookie()->forever('refresh_token', $refreshToken, null, null, env('APP_ENV') === 'production', true, true, 'lax')); } - 实现刷新令牌接口:
Route::post('/refresh-token', function (Request $request) { $refreshToken = $request->cookie('refresh_token'); if (!$refreshToken) { return response()->json(['message' => 'Refresh token missing'], 401); } $tokenInstance = \Laravel\Sanctum\PersonalAccessToken::findToken($refreshToken); if (!$tokenInstance || !$tokenInstance->tokenCan('refresh')) { return response()->json(['message' => 'Invalid refresh token'], 401); } // 吊销旧刷新令牌,避免重复使用 $tokenInstance->delete(); $user = $tokenInstance->user; // 生成新的双令牌 $newAccessToken = $user->createToken('access_token', ['view:posts', 'edit:own-posts'], now()->addMinutes(15))->plainTextToken; $newRefreshToken = $user->createToken('refresh_token', ['refresh'])->plainTextToken; return response()->json(['access_token' => $newAccessToken]) ->withCookie(cookie()->forever('refresh_token', $newRefreshToken, null, null, env('APP_ENV') === 'production', true, true, 'lax')); })->middleware('auth:sanctum'); - 前端自动刷新逻辑:
axios.interceptors.response.use( res => res, async err => { const originalReq = err.config; if (err.response.status === 401 && !originalReq._retry) { originalReq._retry = true; try { const refreshRes = await axios.post('/refresh-token'); useAuthStore().setToken(refreshRes.data.access_token); originalReq.headers['Authorization'] = `Bearer ${refreshRes.data.access_token}`; return axios(originalReq); } catch { useAuthStore().logout(); router.push('/login'); } } return Promise.reject(err); } );
3. 细粒度令牌权限控制
不给令牌全局权限,仅分配必要操作权限:
- 创建令牌时指定
abilities:$token = $user->createToken('user_token', ['view:own-profile', 'edit:own-posts'])->plainTextToken; - 路由中用abilities中间件校验:
Route::get('/posts', [PostController::class, 'index'])->middleware('auth:sanctum', 'abilities:view:own-posts');
4. 额外安全加固措施
- 强制HTTPS:生产环境所有请求使用HTTPS,避免令牌明文传输。
- 严格CORS配置:在
config/cors.php中限制仅允许Vue客户端域名访问:'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], 'supports_credentials' => true, - 令牌吊销机制:提供用户注销接口,主动吊销当前令牌:
Route::post('/logout', function (Request $request) { $request->user()->currentAccessToken()->delete(); // 清除刷新令牌Cookie return response()->json(['message' => 'Logged out'])->withoutCookie('refresh_token'); })->middleware('auth:sanctum'); - 请求频率限制:给API路由添加
throttle中间件,防止暴力滥用:Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () { // 受保护的API路由 });
内容的提问来源于stack exchange,提问作者Sarwar Hasan
相关产品推荐
相关产品推荐

