directorytree/bartender

使用 Laravel Socialite 验证用户的一种有见地的方案。

v1.2.0 2024-06-04 21:14 UTC

This package is auto-updated.

Last update: 2024-09-20 20:53:13 UTC


README

使用 Laravel Socialite 验证用户的一种有见地的方案。

Bartender 为您提供控制器、路由和默认实现,用于处理与 Laravel Socialite 提供程序的认证。

Bartender 中几乎所有的东西都可以自定义。

索引

要求

  • PHP >= 8.0
  • Laravel >= 9.0
  • Laravel Socialite >= 5.0

安装

您可以通过 composer 安装此包

composer require directorytree/bartender

然后,发布迁移

它在 users 表上创建 provider_idprovider_name 列。

php artisan vendor:publish --provider="DirectoryTree\Bartender\BartenderServiceProvider"

最后,运行迁移

php artisan migrate

设置

使用 Bartender::routes() 注册认证路由。

这将注册 /auth/{driver}/redirect/auth/{driver}/callback 路由。

// routes/web.php

use DirectoryTree\Bartender\Facades\Bartender;

Bartender::routes();

设置您需要的任何 Socialite 提供程序,并将每个提供程序的 redirect URL 更新到您的 services.php 配置文件中

重要

请记住,为每个您希望使用的 Socialite 提供程序完全完成安装步骤。

如果您收到 驱动程序 [X] 不受支持 异常,您尚未完成提供程序的安装步骤。

// config/services.php

return [
    // ...

    'google' => [
        // ...
        'redirect' => '/auth/google/callback',
    ],
    
    'microsoft' => [
        // ...
        'redirect' => '/auth/microsoft/callback',
    ],
];

最后,使用 Bartender::serve() 在您的 AppServiceProvider 中注册 Socialite 提供程序

// app/Providers/AppServiceProvider.php

use DirectoryTree\Bartender\Facades\Bartender;

class AppServiceProvider extends ServiceProvider
{
    // ...

    public function boot(): void
    {
        Bartender::serve('google');
        Bartender::serve('microsoft');
    }
}

如果您的应用程序在 App\Models 命名空间之外使用 User 模型,您可以使用 Bartender 门面来设置它

如果您的应用程序在 App\Models 命名空间中使用默认的 Laravel User 模型,请跳过此步骤。

// app/Providers/AuthServiceProvider.php

use App\User;
use DirectoryTree\Bartender\Facades\Bartender;

class AuthServiceProvider extends ServiceProvider
{
    // ...

    public function boot()
    {
        Bartender::setUserModel(User::class);
    }
}

用法

将用户引导到 /auth/{driver}/redirect 路由以使用指定的驱动程序进行认证

<a href="{{ route('auth.driver.redirect', 'google') }}">
    Login with Google
</a>

<a href="{{ route('auth.driver.redirect', 'microsoft') }}">
    Login with Microsoft
</a>

一旦用户成功认证,他们将被重定向到 /auth/{driver}/callback 路由,该路由将自动创建或更新他们的应用程序用户账户。

重要

如果您在点击登录链接时收到 路由要求 "driver" 不能为空 异常,您已忘记在 AppServiceProvider 中使用 Bartender::serve() 注册您的 Socialite 提供程序。

软删除

使用默认的 UserProviderRepository,如果用户被软删除并且使用其提供程序登录,则用户将被恢复。

要更改此行为,请更换仓库

电子邮件验证

使用默认的 UserProviderRepository,如果用户的电子邮件尚未设置,则其电子邮件将自动验证(通过 email_verified_at 列)。

要更改此行为,请更换仓库

扩展和自定义

Bartender 中几乎所有都可以更换。

如果您想自己处理 OAuth 重定向和回调,您可以创建自己的 ProviderHandler

// app/Socialite/UserProviderHandler.php

namespace App\Socialite;

use Illuminate\Http\Request;
use Laravel\Socialite\Contracts\Provider;
use DirectoryTree\Bartender\ProviderHandler;

class UserProviderHandler implements ProviderHandler
{
    /**
     * Constructor.
     */
    public function __construct(
        protected Request $request
    ) {
    }

    /**
     * Handle redirecting the user to the OAuth provider.
     */
    public function redirect(Provider $provider, string $driver): RedirectResponse
    {
        // Perform additional logic here...
    
        return $provider->redirect();
    }

    /**
     * Handle an OAuth response from the provider.
     */
    public function callback(Provider $provider, string $driver): RedirectResponse
    {
        // Authenticate the user your own way...
    
        return redirect()->route('dashboard');
    }
}

然后,将其提供给 Bartender::serve 方法的第二个参数

// app/Providers/AuthServiceProvider.php

namespace App\Providers;

use App\Socialite\UserProviderHandler;
use DirectoryTree\Bartender\Facades\Bartender;

class AuthServiceProvider extends ServiceProvider
{
    // ...

    public function boot(): void
    {
        Bartender::serve('google', UserProviderHandler::class);
        Bartender::serve('microsoft', UserProviderHandler::class);
    }
}

用户创建和更新

如果您想自定义默认处理程序中用户的创建,您可以创建自己的 ProviderRepository 实现

// app/Socialite/UserProviderRepository.php

namespace App\Socialite;

use App\Models\User;
use Illuminate\Contracts\Auth\Authenticatable;
use DirectoryTree\Bartender\ProviderRepository;
use Laravel\Socialite\Contracts\User as SocialiteUser;

class UserProviderRepository implements ProviderRepository
{
    /**
     * Determine if the user already exists under a different provider.
     */
    public function exists(string $driver, SocialiteUser $user): bool
    {
        return User::withTrashed()->where('...')->exists();
    }

    /**
     * Update or create the socialite user.
     */
    public function updateOrCreate(string $driver, SocialiteUser $user): Authenticatable
    {
        $user = User::withTrashed()->firstOrNew([
            // ...
        ]);
        
        return $user;
    }
}

然后,在您的 AppServiceProvider 中的服务容器中绑定您的实现

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use App\Socialite\UserProviderRepository;
use DirectoryTree\Bartender\ProviderRepository;

class AppServiceProvider extends ServiceProvider
{
    // ...

    public function register(): void
    {
        $this->app->bind(ProviderRepository::class, UserProviderRepository::class);
    }
}

用户重定向和闪存消息

如果您想根据 OAuth 回调的结果自定义默认重定向器和闪存消息的行为,您可以创建自己的 ProviderRedirector 实现

建议在认证后重新生成会话,以防止用户利用会话固定攻击

// app/Socialite/UserProviderRedirector.php

namespace App\Socialite;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

class UserProviderRedirector implements ProviderRedirector
{
    /**
     * Redirect when unable to authenticate the user.
     */
    public function unableToAuthenticateUser(Exception $e, string $driver): RedirectResponse
    {
        report($e);

        return redirect()->route('login')->with('error', 'Unable to authenticate user.');
    }

    /**
     * Redirect when the user already exists.
     */
    public function userAlreadyExists(SocialiteUser $user, string $driver): RedirectResponse
    {
        return redirect()->route('login')->with('error', 'User already exists.');
    }

    /**
     * Redirect when unable to create the user.
     */
    public function unableToCreateUser(Exception $e, SocialiteUser $user, string $driver): RedirectResponse
    {
        report($e);

        return redirect()->route('login')->with('error', 'Unable to create user.');
    }

    /**
     * Handle when the user has been successfully authenticated.
     */
    public function userAuthenticated(Authenticatable $user, SocialiteUser $socialite, string $driver): RedirectResponse
    {
        Auth::login($user);
        
        Session::regenerate();
    
        return redirect()->route('dashboard');
    }
}

然后,在您的 AppServiceProvider 中的服务容器中绑定您的实现

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use App\Socialite\UserProviderRedirector;

class AppServiceProvider extends ServiceProvider
{
    // ...

    public function register(): void
    {
        $this->app->bind(ProviderRedirector::class, UserProviderRedirector::class);
    }
}