为 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 中,有系统保留范围,这些范围是openid
、email
、phone
、profile
和aws.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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。