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

如何限制Laravel Passport令牌仅允许白名单前端访问?

问题描述

技术栈:

  • ReactJs(前端)
  • Laravel(后端-API)

当前使用Laravel Passport的Personal Access Tokens实现API认证,登录代码如下:

public function login(LoginRequest $request)
{
    $request->authenticate(); // 根据请求中的email和password验证用户
    
    /** @var User $user */
    $user = Auth::user();
    $token = $user->createToken('auth');

    return response([
        'message' => 'success',
        'user' => $user,
        'token' => $token->accessToken,
    ]);
}

该方案运行正常,但存在问题:任何人都可通过Postman等工具获取令牌并访问受保护路由,不符合仅允许白名单前端(如React SPA)访问的需求。曾尝试在config/cors.php中配置域名白名单,该方法仅对现代浏览器有效,Postman等工具仍可正常访问,寻求可行实现方案。

可行解决方案

1. 使用Laravel Passport的Authorization Code Grant(授权码模式)+ PKCE

这是SPA场景下的标准安全方案,相比Personal Access Token,它要求前端必须通过规范的授权流程获取令牌,PKCE机制能有效防止授权码被拦截,大幅提升安全性。

配置步骤:

  • 在Laravel中创建SPA类型的Passport客户端:
    php artisan passport:client --spa
    
  • 前端实现PKCE授权流程:生成随机的code_verifier和code_challenge,跳转到Laravel的授权页面,用户登录后获取授权码,再用授权码兑换令牌。
  • 后端配置:确保Passport路由正确注册,在AuthServiceProvider的boot方法中添加:
    Passport::routes();
    Passport::enableImplicitGrant();
    
  • 此方案下,只有白名单域名下的前端能完成完整授权流程,Postman等工具无法模拟SPA的PKCE流程(伪造成本极高)。

2. 验证请求来源的Origin和Referer头(补充防护)

虽然Postman可以伪造这些请求头,但结合其他机制能进一步提升防护等级:

  • 创建自定义中间件VerifyFrontendOrigin:
    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    
    class VerifyFrontendOrigin
    {
        protected $allowedOrigins = [
            'https://your-react-app-domain.com',
            'http://localhost:3000', // 开发环境域名
        ];
    
        public function handle(Request $request, Closure $next)
        {
            $origin = $request->header('Origin');
            $referer = $request->header('Referer');
    
            // 检查Origin是否在白名单,或Referer包含白名单域名
            if (!$origin || !in_array($origin, $this->allowedOrigins)) {
                if (!$referer || !collect($this->allowedOrigins)->contains(fn($domain) => str_contains($referer, $domain))) {
                    return response()->json(['message' => 'Unauthorized origin'], 403);
                }
            }
    
            return $next($request);
        }
    }
    
  • 在app/Http/Kernel.php中注册中间件,并应用到受保护的API路由组:
    protected $routeMiddleware = [
        // ...其他中间件
        'verify.origin' => \App\Http\Middleware\VerifyFrontendOrigin::class,
    ];
    
  • 路由配置示例:
    Route::middleware(['auth:api', 'verify.origin'])->group(function () {
        // 所有受保护的API路由
    });
    

3. 令牌绑定前端设备/会话信息

在生成令牌时绑定前端的唯一标识,每次请求时验证该标识,即使令牌被窃取,也无法在其他环境使用:

  • 修改登录代码,添加额外信息到令牌:
    $token = $user->createToken('auth', [], [
        'frontend_id' => $request->header('X-Frontend-ID'), // 前端生成的唯一ID,存储在localStorage中
        'user_agent' => $request->userAgent(),
    ]);
    
  • 创建验证中间件:
    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use Laravel\Passport\Token;
    
    class VerifyTokenBinding
    {
        public function handle(Request $request, Closure $next)
        {
            $token = $request->bearerToken();
            if (!$token) {
                return response()->json(['message' => 'Unauthorized'], 401);
            }
    
            $accessToken = Token::findToken($token);
            if (!$accessToken) {
                return response()->json(['message' => 'Invalid token'], 401);
            }
    
            $frontendId = $request->header('X-Frontend-ID');
            $userAgent = $request->userAgent();
    
            if ($accessToken->getAttribute('frontend_id') !== $frontendId || $accessToken->getAttribute('user_agent') !== $userAgent) {
                return response()->json(['message' => 'Token not valid for this device'], 403);
            }
    
            return $next($request);
        }
    }
    
  • 前端在每次请求时携带X-Frontend-ID头,确保令牌与当前前端环境绑定。

内容的提问来源于stack exchange,提问作者Subham Manna

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:45:37