在 Laravel 11 中使用 Aws cognito 的含义是使用池的 user_id 作为应用程序的用户 ID。

我正在实现一个系统,该系统有一个 Django 系统和一个 Laravel 中的用户管理面板,以后这可能是或可能不是一个成熟的后台办公室(目前情况未知,我没有进一步的规格)。

因此,我选择了 AWS cognito,原因是我希望在 Laravel 应用程序和 Django 系统中都有一个唯一的 user_id 引用。首先实现了 Django 系统,并使用 MongoDb 进行数据存储。

Cognito 为我提供了一种管理身份验证的独特方法。

在 Laravel 端我使用了 `laravel/socialite` 和 `socialiteproviders/cognito`。但是我需要解决一些问题:

怪癖 1:用户应始终引用数据库

就我而言,我需要慢慢来,只使用面板来操作存在于 aws cognito 上的数据。**不**我在将默认的“会话”提供程序与引用 aws cognito 上的模型的自定义用户模型一起使用时遇到了麻烦。

就我而言,我只想有一个简单的前端,使用 AWS api 进行 cognito,并在管理员用户登录后操作数据。如上所述,这是不可行的。

最后我制作了这个控制器:

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\User;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;

class UserController extends Controller
{

    // This is my login handler
    public function login(Request $request)
    {
        $loggedin = Auth::check();
        $requestHasCode = $request->has('code');
        if(!$requestHasCode) {
            if($loggedin){
                // User is already authenticated redirect
                return $this->authRedirect();
            }

            // Logout prompts user back to login screen
            return $this->logout($request);
        }

        $socialiteUser = null;
        try {
            $socialiteUser = Socialite::driver('cognito')->stateless()->user();
        } catch (\Exception $e) {
            return Socialite::driver('cognito')->redirect();
        }

        if($socialiteUser != null){
            $user = User::createBySocialiteUser($socialiteUser);
            Auth::login($user,true);
            return $this->authRedirect();
        }

        return Socialite::driver('cognito')->redirect();
    }
}

在默认用户模型“App\Models\User”上我执行了:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    const USER_ADMIN='ADMIN';
    const USER_CLIENT='CLIENT';

    // Read bellow in the article regarding this
    public $incrementing = false;


 public static function createBySocialiteUser (\SocialiteProviders\Manager\OAuth2\User $user): ?self
    {
        $dataToUpdate = [
            'email' => $user->user['email'],
            'id' => $user->user['sub'], // Ensure this is the correct value
            'name' => $user->name ?? "Unknown User",
        ];

        // Use the correct key for the first argument
        $user=User::firstOrNew(['id' => $dataToUpdate['id']], $dataToUpdate);
        // First or New Does mto set the USer Id
        $user->id = $dataToUpdate['id'];
        $user->save();

        return $user;
    }
}

如您所见,我将用户“sub”映射为用户 ID。因此,我不需要对用户表中的“id”增加整数。

为了使我的模型符合新规范(“sub”是“user_id”),在迁移时我将 id 设置为字符串,该项目是全新的,尚未部署在任何环境(开发、暂存或生产)中。

因此我直接修改了 laravel 提供的迁移:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->string('id', 36)->primary();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password')->nullable();
            $table->enum('role', ['ADMIN', 'CLIENT'])->default('CLIENT');
            $table->rememberToken();
            $table->timestamps();
        });

        // Rest of migration goes here
    }
};

上述迁移给我带来了另一个影响,如下所述:

怪癖 2:会话和 CSRF 失效

此时,我确信一切都没问题,但你猜怎么着,**不行**。让我解释一下。我在 blade 视图上创建了一个带有典型 csrf 内容的简单表单:

@extends('layout.somelayout')

@section('main')

@csrf
@endsection

我在处理提交的典型控制器上发布了一些传统的旧内容:

Route::post('/somepath',function(){
 // Stuff submission here
})->name('myroute');

但提交后,laravel 返回了 419 状态代码的响应。调试时,我直接转到了 `vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php`。

我发现每次提交表单时都会创建一个新的 csrf 令牌:

protected function tokensMatch($request)
{

    $token = $this->getTokenFromRequest($request);

    return is_string($request->session()->token()) &&
               is_string($token) &&
               hash_equals($request->session()->token(), $token);
}

原因是因为我使用数据库作为会话驱动程序,在可扩展性和不需要在堆栈中部署额外的东西之间取得了良好的平衡。

我使用默认表因为我没有理由更改它:

'table' => env('SESSION_TABLE', 'sessions'),

但是此表的迁移包含用户 ID 作为大整数:

Schema::create('sessions', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->foreignId('user_id')->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->longText('payload');
            $table->integer('last_activity')->index();
        });

这:

$table->foreignId('user_id')->nullable()->index();

在表中创建 `user_id` 作为 **大整数**。一旦用户成功登录,会话将无法与 `user_id` 关联,这意味着每次提交时都会生成一个新的 csrf 令牌。

修复方法是将“user_id”变成字符串:

Schema::create('sessions', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->string('user_id',36)->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->longText('payload');
            $table->integer('last_activity')->index();
        });

结论

  • 如果使用 OAuth 身份验证,则绕过(即不使用,如果需要,可以自己实现)默认保护或在 DB 上设置登录用户信息
  • 如果您更改用户表中的主键类型,请确保也更改会话表中的类型。