You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 });
      

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 02:58:30