使用 Sanctum 模拟用户

Impersonate users with Sanctum

我正在使用 Sanctum 为 Laravel 实施一个简单的模拟系统。早些时候我使用 Tymondesigns/jwt-auth with Rickycezar/laravel-jwt-impersonate,但我们最近放弃了 Tymon JWT 转而使用 Sanctum。

我没有用 Sanctum 实现 laravel-jwt-impersonate,或者它是从中分叉出来的原始 404labfr/laravel-impersonate。所以...我决定自己尝试实现一个非常简单的模拟系统。

这就是我现在要做的事情:

当管理员调用 impersonate() 函数时,我为被模拟的用户创建了一个令牌。此令牌 returned 到前端并用作不记名令牌。它似乎运行良好,在执行此操作后,应用程序就像我是模拟用户一样(因为我实际上是作为该用户“登录”的)。

public function impersonate($user)
{
    $user = User::find($user);
    return $user->createToken('IMPERSONATE token');
}

下一步是我想知道的。如何结束模拟。如果我注销,创建的模拟令牌将被删除,所以一切都很好......但这意味着管理员现在已注销并且必须重新登录。

我想使用之前的令牌重新登录管理员。但我不知道如何 return 管理员登录时使用的令牌。

所以我的问题:

  1. 你会如何解决这个问题?
  2. 您看到哪些明显的安全隐患(只能从管理员帐户进行模拟,并且只能模拟非管理员帐户)?

你就快完成了!我们有一个应用程序也使用 Sanctum,但使用会话而不是令牌。在我们模拟用户之前,我们将 admin/actual 用户的 ID 放入会话中:

$request->session()->put('impersonate', true); // if you need to check if session is impersonated or not
$request->session()->put('impersonate_admin_id', Auth::id());
Auth::login($user); // then impersonate

因此我们可以使用用户 ID:

在 logout/exit 模拟中重新登录到管理员
Auth::loginUsingId($request->session()->get('impersonate_admin_id'));

尽管我的示例是基于会话的,但您明白了要点。由于您的是基于令牌的,并且您不能将其存储在会话或 cookie 中,因此我建议使用 DB 或 Redis/Cache.

这是我寻求的解决方案。结果比最初预期的要大一点。它似乎与我们将 Tymondesigns/jwt-auth 与 Rickycezar/laravel-jwt-impersonate 一起使用时一样有效。我对响应使用相同的结构,因此前端不需要进行任何更改。

我还在 this blog post 中概述了更多内容。

迁移

public function up()
{
    Schema::create('impersonations', function (Blueprint $table) {
        $table->id();
        $table->bigInteger('personal_access_token_id')->unsigned();
        $table->bigInteger('user_id')->unsigned();
        $table->timestamps();

        $table->foreign('personal_access_token_id')->references('id')->on('personal_access_tokens')->cascadeOnDelete();
        $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
    });
}

用户模型得到这三个函数

public function canImpersonate()
{
    return $this->is_superadmin;
}


public function canBeImpersonated()
{
    return !$this->is_superadmin;
}


public function isImpersonated() {
    $token = $this->currentAccessToken();
    return $token->name == 'IMPERSONATION token';
}

模拟 函数。 把它放在有意义的地方。对我来说,它在我的超级管理员控制器中

public function impersonate($userId)
{
    $impersonator = auth()->user();
    $persona = User::find($userId);

    // Check if persona user exists, can be impersonated and if the impersonator has the right to do so.
    if (!$persona || !$persona->canBeImpersonated() || !$impersonator->canImpersonate()) {
        return false;
    }

    // Create new token for persona
    $personaToken = $persona->createToken('IMPERSONATION token');

    // Save impersonator and persona token references
    $impersonation = new Impersonation();
    $impersonation->user_id = $impersonator->id;
    $impersonation->personal_access_token_id = $personaToken->accessToken->id;
    $impersonation->save();

    // Log out impersonator
    $impersonator->currentAccessToken()->delete();

    $response = [
        "requested_id" => $userId,
        "persona" => $persona,
        "impersonator" => $impersonator,
        "token" => $personaToken->plainTextToken
    ];

    return response()->json(['data' => $response], 200);
}

离开冒充

public function leaveImpersonate()
{
    // Get impersonated user
    $impersonatedUser = auth()->user();

    // Find the impersonating user
    $currentAccessToken = $impersonatedUser->currentAccessToken();
    $impersonation = Impersonation::where('personal_access_token_id', $currentAccessToken->id)->first();
    $impersonator = User::find($impersonation->user_id);
    $impersonatorToken = $impersonator->createToken('API token')->plainTextToken;

    // Logout impersonated user
    $impersonatedUser->currentAccessToken()->delete();

    $response = [
        "requested_id" => $impersonator->id,
        "persona" => $impersonator,
        "token" => $impersonatorToken,
    ];

    return response()->json(['data' => $response], 200);
}