为 Laravel Socialite 实现自定义驱动程序

Laravel Socialite 是 Laravel 的官方拓展包,用于向OAuth提供商进行身份验证。它支持使用 Facebook, Twitter, LinkedIn, Google, GitHub, 和 Bitbucket 进行身份验证。 但是,如果你想用不同的驱动呢?

在本例中,我们希望使用 AWS Cognito 作为身份验证提供者。AWS Cognito 允许你使用不同的提供者进行身份验证, 并且存储了统一的用户数据,这些数据你能在不同的设备上使用。

第一件事就是安装 Laravel Socialite,如下所示:

composer require laravel/socialite

现在,我们创建一个 CognitoProvider 的类,它继承了 \Socialite\Two\AbstractProvider 。 我们需要实现以下方法,这样驱动就会如期运行。

// ...
use Laravel\Socialite\Two\AbstractProvider;

class SocialiteCognitoProvider extends AbstractProvider
{

    protected function getAuthUrl($state)
    {
        // TODO: 实现 getAuthUrl() 方法。
    }

    protected function getTokenUrl()
    {
        // TODO: 实现 getTokenUrl() 方法。
    }

    protected function getUserByToken($token)
    {
        // TODO: 实现 getUserByToken() 方法。
    }

    protected function mapUserToObject(array $user)
    {
        // TODO: 实现 mapUserToObject() 方法。
    }
}

在 Laravel Socialite 文档 中,我们必须创建一个 redirect 路由,它基本上从选中的对象中调用 redirect() 方法驱动程序,像这样:

use Laravel\Socialite\Facades\Socialite;

Route::get('/auth/redirect', function () {
    return Socialite::driver('cognito')->redirect();
});

redirect() 方法在用户被重定向到第三方提供商身份验证页面的后台调用 getAuthUrl() 方法。所以,我们需要在这个方法中提供这个 URL。我们还提供了如何以不同的方法获取基本 URL,方便我们将在不同的地方使用它:

/**
 * @return string
 */
public function getCognitoUrl()
{
    return config('services.cognito.base_uri') . '/oauth2';
}

/**
 * @param string $state
 *
 * @return string
 */
protected function getAuthUrl($state)
{
    return $this->buildAuthUrlFromBase($this->getCognitoUrl() . '/authorize', $state);
}

内部 buildAuthUrlFromBase() 方法使用所有必要参数构建身份验证 URL。

一旦用户通过第三方提供商的身份验证,他们就会被重定向到我们在应用程序中定义的回调 URL。 这取决于你想在这个控制器方法上做什么,你可能会调用 user() socialite 方法,如下所示:

Route::get('/auth/callback', function () {
    $user = Socialite::driver('cognito')->user();

    // $user->token
});

当调用此方法时,它会调用 getTokenUrl() 方法从回调 url 参数中获取具有给定代码的访问令牌。 所以我们需要提供这个网址:

/**
 * @return string
 */
protected function getTokenUrl()
{
    return $this->getCognitoUrl() . '/token';
}

现在我们有了访问令牌,我们可以获取经过身份验证的用户,我们将在 getUserByToken() 方法中执行此操作。在我们的例子中,我们需要像这样执行一个 POST 请求:

/**
 * @param string $token
 *
 * @throws GuzzleException
 *
 * @return array|mixed
 */
protected function getUserByToken($token)
{
    $response = $this->getHttpClient()->post($this->getCognitoUrl() . '/userInfo', [
        'headers' => [
            'cache-control' => 'no-cache',
            'Authorization' => 'Bearer ' . $token,
            'Content-Type' => 'application/x-www-form-urlencoded',
        ],
    ]);

    return json_decode($response->getBody()->getContents(), true);
}

最后,我们从前面的方法中得到一个用户对象,我们需要将该对象映射到一个新的 User 类中。 在我们的例子中,使用 Laravel\Socialite\Two\User,并使用 mapUserToObject() 映射到 User,如下所示:

/**
 * @return User
 */
protected function mapUserToObject(array $user)
{
    return (new User())->setRaw($user)->map([
        'id' => $user['sub'],
        'email' => $user['email'],
        'username' => $user['username'],
        'email_verified' => $user['email_verified'],
        'family_name' => $user['family_name'],
    ]);
}

现在,在 callback() 方法中,可以执行以下操作:

Route::get('/auth/callback', function () {
    try {
        $cognitoUser = Socialite::driver('cognito')->user();
        $user = User::query()->whereEmail($cognitoUser->email)->first();

        if (!$user) {
            return redirect('login');
        }

        Auth::guard('web')->login($user);

        return redirect(route('home'));
    } catch (Exception $exception) {
        return redirect('login');
    }
});

根据 Provider 的不同,如果需要向身份验证请求添加一些范围。范围是一种限制用户访问应用程序的机制。

在 AWS Cognito 中,有系统保留范围,这些范围是openidemailphoneprofileaws.cognito.signin.user.admin。 要了解有关这些范围的更多信息, 请查看。你还可以在 Cognito 中创建自定义范围, 更多相关信息 请查看

SocialiteCognitoProvider 类中,你可以通过覆盖 $scopes$scopeSeparator 内部变量来自定义范围,如下所示:

class SocialiteCognitoProvider extends AbstractProvider
{
    /**
    * @var string[]
    */
    protected $scopes = [
        'openid',
        'profile',
        'aws.cognito.signin.user.admin',
    ];

    /**
    * @var string
    */
    protected $scopeSeparator = ' ';

    // ...
}

要了解有关 AWS Cognito 范围的更多信息,请查看 官方文档

最后的类文件将如下所示:

// ...

use Laravel\Socialite\Two\User;
use GuzzleHttp\Exception\GuzzleException;
use Laravel\Socialite\Two\AbstractProvider;

class SocialiteCognitoProvider extends AbstractProvider
{
    /**
     * @var string[]
     */
    protected $scopes = [
        'openid',
        'profile',
        'aws.cognito.signin.user.admin',
    ];

    /**
     * @var string
     */
    protected $scopeSeparator = ' ';

    /**
     * @return string
     */
    public function getCognitoUrl()
    {
        return config('services.cognito.base_uri') . '/oauth2';
    }

    /**
     * @param string $state
     *
     * @return string
     */
    protected function getAuthUrl($state)
    {
        return $this->buildAuthUrlFromBase($this->getCognitoUrl() . '/authorize', $state);
    }

    /**
     * @return string
     */
    protected function getTokenUrl()
    {
        return $this->getCognitoUrl() . '/token';
    }

    /**
     * @param string $token
     *
     * @throws GuzzleException
     *
     * @return array|mixed
     */
    protected function getUserByToken($token)
    {
        $response = $this->getHttpClient()->post($this->getCognitoUrl() . '/userInfo', [
            'headers' => [
                'cache-control' => 'no-cache',
                'Authorization' => 'Bearer ' . $token,
                'Content-Type' => 'application/x-www-form-urlencoded',
            ],
        ]);

        return json_decode($response->getBody()->getContents(), true);
    }

    /**
     * @return User
     */
    protected function mapUserToObject(array $user)
    {
        return (new User())->setRaw($user)->map([
            'id' => $user['sub'],
            'email' => $user['email'],
            'username' => $user['username'],
            'email_verified' => $user['email_verified'],
            'family_name' => $user['family_name'],
        ]);
    }
}

但是,Socialite 是如何识别授权驱动的呢? 我们需要在 AppServiceProvider 中添加一些代码:

// ...
use Laravel\Socialite\Contracts\Factory;

/**
 * @throws BindingResolutionException
 */
public function boot()
{
    $socialite = $this->app->make(Factory::class);

    $socialite->extend('cognito', function () use ($socialite) {
        $config = config('services.cognito');

        return $socialite->buildProvider(SocialiteCognitoProvider::class, $config);
    });
}

boot 方法中,我们在 Socialite 管理器中注册我们的驱动程序,因此当调用 Socialite::driver('cognito') 时,它会实例化我们的 SocialiteCognitoProvider 类。

就是这样!这就是为 Laravel Socialite 实现新的自定义驱动程序的方式。为了更容易理解,我们为自定义 Cognito 驱动程序创建了一个小程序包,可以查看 此处.

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://laravel-news.com/laravel-sociali...

译文地址:https://learnku.com/laravel/t/66081

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!