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

Laravel Passport 结合 Tenancy for Laravel 多租户生成令牌报oauth表不存在错误

核心问题定位
  • 重复注册Passport路由引发的上下文冲突:AppServiceProvider中已带租户中间件注册过Passport路由,AuthServiceProvider中又无租户上下文裸注册了一次Passport::routes(),框架加载时会优先采用无租户绑定的路由配置,导致后续令牌生成流程中数据库连接回退到主库。
  • Passport内置模型未绑定租户连接:Passport默认模型会使用应用默认的数据库连接(即主库multi-tenant),即使当前租户上下文正常激活,查询oauth_personal_access_clients这类表时依然走主库连接,自然触发表不存在的报错。
  • 未在租户初始化流程中为每个租户库同步Passport表结构,导致即便连接切到租户库,也找不到对应的oauth相关表。
分步修复方案

1. 清理重复路由注册

直接删除AuthServiceProvider中boot()方法内的Passport::routes();代码,保留AppServiceProvider中带租户中间件的路由配置逻辑。如果不需要自定义Passport内置端点的路径,可直接移除AppServiceProvider中的Passport::routes(...)全局注册逻辑,将所有需要Passport认证的API路由统一放到多租户路由组中,从根源避免全局注册带来的上下文丢失问题。

注意:Passport路由不需要重复注册,同一路由路径被重复绑定会引发中间件优先级混乱,最终导致租户上下文失效。

2. 自定义Passport模型绑定,继承租户连接

不要使用Passport默认的模型连接配置,在AppServiceProvider的boot()方法中追加自定义模型绑定逻辑:

\Laravel\Passport\Passport::useTokenModel(\App\Models\Passport\Token::class);
\Laravel\Passport\Passport::useClientModel(\App\Models\Passport\Client::class);
\Laravel\Passport\Passport::useAuthCodeModel(\App\Models\Passport\AuthCode::class);
\Laravel\Passport\Passport::usePersonalAccessClientModel(\App\Models\Passport\PersonalAccessClient::class);

为每个自定义的Passport模型添加基础配置,不要硬编码数据库连接名,让模型自动继承当前激活的数据库连接:

// app/Models/Passport/PersonalAccessClient.php
namespace App\Models\Passport;

use Laravel\Passport\PersonalAccessClient as BasePersonalAccessClient;

class PersonalAccessClient extends BasePersonalAccessClient
{
    protected $table = 'oauth_personal_access_clients';
    // 不定义$connection属性,自动使用当前默认连接(租户激活时即为租户库连接)
}

关键逻辑:只要模型不硬编码数据库连接,租户激活时框架会自动将默认数据库连接替换为租户独立库的连接,Passport的所有查询都会自动走租户库,不会回退到主库。

3. 锁定令牌生成流程的数据库连接

原手动登录代码报错的核心原因是,createToken执行过程中触发的全局oauth_personal_access_clients查询,会在框架的连接缓存中回退到主库。可以在令牌生成代码块中手动锁定当前租户连接,避免上下文切换:

public function loginManual() 
{
    $credentials = request(['email', 'password']);
    $credentials['active'] = 1;

    if (!Auth::attempt($credentials)) {
        return response()->json(["message" => "Password mismatch"]);
    }

    $user = Auth::user();
    // 手动同步当前租户连接为默认连接,清理旧的连接缓存
    config(['database.default' => tenancy()->database->getConnectionName()]);
    DB::purge(config('database.default'));

    $tokenResult = $user->createToken('Laravel Password Grant Client');
    return response()->json([
        'token' => $tokenResult->accessToken,
        'expires_at' => $tokenResult->token->expires_at,
    ]);
}

4. 确保租户库同步Passport表结构

因为已配置Passport::ignoreMigrations(),需要在租户初始化、创建数据库的流程中,自动对每个租户库运行Passport的表迁移,确保oauth_clients、oauth_personal_access_clients、oauth_access_tokens等表在每个租户库中存在。常见踩坑是仅在主库执行了Passport迁移,租户库无对应表结构,就算连接切对也会报表不存在错误。

绕过内部查询的手动令牌生成方案

如果上述连接绑定逻辑仍存在上下文切换问题,可以直接绕过Passport的自动模型加载,手动查询当前租户库下的个人访问客户端,构造生成令牌:

public function generateTokenForCurrentUser()
{
    $user = Auth::user();
    // 直接用当前租户连接查询,不触发Passport的全局模型绑定
    $personalClient = DB::table('oauth_personal_access_clients')->first();
    
    if (!$personalClient) {
        return response()->json(['message' => '当前租户未配置个人访问客户端'], 500);
    }

    // 手动写入令牌记录
    $token = $user->tokens()->create([
        'id' => \Illuminate\Support\Str::random(40),
        'client_id' => $personalClient->client_id,
        'name' => 'Laravel Password Grant Client',
        'scopes' => [],
        'revoked' => false,
        'expires_at' => now()->addDays(365),
    ]);

    // 用Passport内置逻辑加密生成可校验的访问令牌字符串
    return (string) \Laravel\Passport\TokenRepository::getEncryptedToken($token->id, $personalClient->client_id);
}

内容的提问来源于stack exchange,提问作者Ahmed Adel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.23 11:24:01