该扩展为 Yii framework 2.0 添加 OpenID、OAuth 和 OAuth2 客户端。
要安装该扩展,请使用 Composer。运行
composer require --prefer-dist yiisoft/yii2-authclient "~2.1.0"
或在你的 composer.json 文件的“require”一节添加以下代码:
"yiisoft/yii2-authclient": "~2.1.0"
该扩展安装后,你需要设置验证客户端集合应用程序组件:
return [ 'components' => [ 'authClientCollection' => [ 'class' => 'yii\authclient\Collection', 'clients' => [ 'google' => [ 'class' => 'yii\authclient\clients\Google', 'clientId' => 'google_client_id', 'clientSecret' => 'google_client_secret', ], 'facebook' => [ 'class' => 'yii\authclient\clients\Facebook', 'clientId' => 'facebook_client_id', 'clientSecret' => 'facebook_client_secret', ], // etc. ], ] // ... ], // ... ];
提供了以下几个立即可用的客户端:
[[\yii\authclient\clients\Facebook|Facebook]].
[[yii\authclient\clients\GitHub|GitHub]].
Google (通过 [[yii\authclient\clients\Google|OAuth]] 和 [[yii\authclient\clients\GoogleHybrid|OAuth Hybrid]]).
[[yii\authclient\clients\LinkedIn|LinkedIn]].
[[yii\authclient\clients\Live|Microsoft Live]].
[[yii\authclient\clients\Twitter|Twitter]].
[[yii\authclient\clients\VKontakte|VKontakte]].
[[yii\authclient\clients\Yandex|Yandex]].
配置每个客户端稍有不同。对于 OAuth 客户端需要从服务端获取客户端 ID 和密钥。而对于 OpenID 客户端,大多数情况下不需要调整。
为了识别由外部服务验证的用户,我们需要保存首次验证时获得的 ID,以及用于接下来验证时检查该 ID。 并不建议将登录选项限于只能用外部登录,自身却不提供一种供用户登录的方法。 最好的做法是既提供原始的账号\密码登录方式,也提供外部验证方式。
如果我们要将用户信息存入数据库,则数据库模式可以是:
class m??????_??????_auth extends \yii\db\Migration { public function up() { $this->createTable('user', [ 'id' => $this->primaryKey(), 'username' => $this->string()->notNull(), 'auth_key' => $this->string()->notNull(), 'password_hash' => $this->string()->notNull(), 'password_reset_token' => $this->string()->notNull(), 'email' => $this->string()->notNull(), 'status' => $this->smallInteger()->notNull()->defaultValue(10), 'created_at' => $this->integer()->notNull(), 'updated_at' => $this->integer()->notNull(), ]); $this->createTable('auth', [ 'id' => $this->primaryKey(), 'user_id' => $this->integer()->notNull(), 'source' => $this->string()->notNull(), 'source_id' => $this->string()->notNull(), ]); $this->addForeignKey('fk-auth-user_id-user-id', 'auth', 'user_id', 'user', 'id', 'CASCADE', 'CASCADE'); } public function down() { $this->dropTable('auth'); $this->dropTable('user'); } }
上述 SQL 中 user
表在高级项目模板中用于保存用户信息。
每个用户可以由多重外部服务验证,因此每个 user
记录可以关联多个 auth
记录。
在 auth
表中 source
是验证提供商的名称 source_id
是外部服务在该用户成功登录后提供的唯一 ID。
使用上述创建的数据表后我们可以生成 Auth
模型。无须进一步调整。
下一步是向 Web 控制器中添加 [[yii\authclient\AuthAction]],然后实现 successCallback
方法,
该方法与你的实际需要保持一致。典型的最终控制器类似如下代码:
class SiteController extends Controller { public function actions() { return [ 'auth' => [ 'class' => 'yii\authclient\AuthAction', 'successCallback' => [$this, 'onAuthSuccess'], ], ]; } public function onAuthSuccess($client) { $attributes = $client->getUserAttributes(); /* @var $auth Auth */ $auth = Auth::find()->where([ 'source' => $client->getId(), 'source_id' => $attributes['id'], ])->one(); if (Yii::$app->user->isGuest) { if ($auth) { // 登录 $user = $auth->user; Yii::$app->user->login($user); } else { // 注册 if (isset($attributes['email']) && User::find()->where(['email' => $attributes['email']])->exists()) { Yii::$app->getSession()->setFlash('error', [ Yii::t('app', "User with the same email as in {client} account already exists but isn't linked to it. Login using email first to link it.", ['client' => $client->getTitle()]), ]); } else { $password = Yii::$app->security->generateRandomString(6); $user = new User([ 'username' => $attributes['login'], 'email' => $attributes['email'], 'password' => $password, ]); $user->generateAuthKey(); $user->generatePasswordResetToken(); $transaction = $user->getDb()->beginTransaction(); if ($user->save()) { $auth = new Auth([ 'user_id' => $user->id, 'source' => $client->getId(), 'source_id' => (string)$attributes['id'], ]); if ($auth->save()) { $transaction->commit(); Yii::$app->user->login($user); } else { print_r($auth->getErrors()); } } else { print_r($user->getErrors()); } } } } else { // 用户已经登陆 if (!$auth) { // 添加验证提供商(向验证表中添加记录) $auth = new Auth([ 'user_id' => Yii::$app->user->id, 'source' => $client->getId(), 'source_id' => $attributes['id'], ]); $auth->save(); } } } }
当用户由外部服务验证通过后调用 successCallback
方法。通过 $client
实例我们可以检索收到的信息。
在我们的例子中,我们可以:
若用户是访客,且在验证信息中找到该用户,则登录该用户。
若用户是访客,却在验证信息中找不到该用户,则创建一个新用户,并添加记录到验证表中,然后登录。
若用户已登录,却在验证信息中找不到该用户,则尝试额外账户建立连接(将该用户的数据保存到验证表中)。
注意:不同的验证客户端在验证通过时可能需要不同的方法。例如:Twitter 不返回用户电子邮件, 所以你需要单独处理此种情况。
尽管每个客户端不尽相同,但它们都共享同一个基础接口 [[yii\authclient\ClientInterface]], 该接口包含了通用的 API。
每个客户端都有一些描述性的数据,分别用于不同的目的:
id
- 唯一客户 ID,用于与其它客户端区分,它可以用于 URL 和日志等。
name
- 外部验证提供商名称,与该客户端匹配。不同的客户端可以使用相同的名称,
即它们指的是同一个外部验证提供商。
例如:谷歌 Google 客户端和 Google Hybrid 客户端有一个相同的名称 “google”。
该树形可以用在数据库内部、CSS 样式中等等。
title
- 用户友好的外部验证提供商名称,用于在验证客户端的视图层展示。
每个验证客户端都有不同的验证流程,但是它们都支持 getUserAttributes()
方法,
可在验证通过后调用。
该方法允许你获取外部用户账户的信息,如 ID、电子邮件账户、全名、首选语言等等。 注意:对于每个提供商,可用域的名称和存在性可能不同。
定义属性列表,用于通知外部验证提供商应当返回列表,根据不同的客户端类型:
[[yii\authclient\OpenId]]: 同时定义 requiredAttributes
和 optionalAttributes
.
[[yii\authclient\OAuth1]] 和 [[yii\authclient\OAuth2]]: 定义 scope
域。注意,
不同的提供商对于范围的格式定义可能不同。
提示:如果你正在使用若干个不同的客户端,你可以使用 [[yii\authclient\BaseClient::normalizeUserAttributeMap]] 统一返回属性的列表。
[[yii\authclient\OAuth1]] 和 [[yii\authclient\OAuth2]] 均提供了 api()
方法,
可以用于访问外部服务提供商的 REST API。然而该方法比较基础,可能并不足以访问
所有外部 API 功能。该方法主要用于取回外部用户账户数据。
要使用 API 调用,你需要根据 API 说明设置 [[yii\authclient\BaseOAuth::apiBaseUrl]]。 之后就可以调用 [[yii\authclient\BaseOAuth::api()]] 方法了:
use yii\authclient\OAuth2; $client = new OAuth2; // ... $client->apiBaseUrl = 'https://www.googleapis.com/oauth2/v1'; $userInfo = $client->api('userinfo', 'GET');
[[yii\authclient\widgets\AuthChoice]] 小部件用于视图中:
<?= yii\authclient\widgets\AuthChoice::widget([ 'baseAuthUrl' => ['site/auth'], 'popupMode' => false, ]) ?>
你可以为任意的外部验证服务商创建你自己的验证客户端,且支持 OpenId 或 OAuth 协议。 若要这么做,首先,你需要确认外部验证服务商支持哪些协议,以下为你的扩展提供的基类的名称:
对于 OAuth 2 使用 [[yii\authclient\OAuth2]].
对于 OAuth 1/1.0a 使用 [[yii\authclient\OAuth1]].
对于 OpenID 使用 [[yii\authclient\OpenId]].
在此步骤中,你可以决定验证客户端的默认名称、标题和视图选项,声明相应的方法:
use yii\authclient\OAuth2; class MyAuthClient extends OAuth2 { protected function defaultName() { return 'my_auth_client'; } protected function defaultTitle() { return 'My Auth Client'; } protected function defaultViewOptions() { return [ 'popupWidth' => 800, 'popupHeight' => 500, ]; } }
根据实际使用的基类,你需要重新声明不同的域和方法。
你要做的所有工作就是通过重新声明 authUrl
域指定验证 URL。
你可能还需要设置默认的必需属性,以及可选属性。
例如:
use yii\authclient\OpenId; class MyAuthClient extends OpenId { public $authUrl = 'https://www.my.com/openid/'; public $requiredAttributes = [ 'contact/email', ]; public $optionalAttributes = [ 'namePerson/first', 'namePerson/last', ]; }
你需要指定:
验证 URL(通过重新声明 authUrl
域)。
令牌请求 URL(通过重新声明 tokenUrl
域)。
API 基础 URL(通过重新声明 apiBaseUrl
域)。
User 属性取回策略(通过重新声明 initUserAttributes()
方法)。
例如:
use yii\authclient\OAuth2; class MyAuthClient extends OAuth2 { public $authUrl = 'https://www.my.com/oauth2/auth'; public $tokenUrl = 'https://www.my.com/oauth2/token'; public $apiBaseUrl = 'https://www.my.com/apis/oauth2/v1'; protected function initUserAttributes() { return $this->api('userinfo', 'GET'); } }
你也可以指定默认的验证范围。
注意:某些 OAuth 提供商可能并不严格遵循 OAuth 标准,或没有清晰说明与 OAuth 标准的差别, 可能需要为实现这些可客户端做额外的工作。
你需要指定:
验证 URL(通过重新声明 authUrl
域)。
请求令牌 URL(通过重新声明 requestTokenUrl
域)。
访问令牌 URL(通过重新声明 accessTokenUrl
域)。
API 基础 URL(通过重新声明 apiBaseUrl
域)。
User 属性取回策略(通过重新声明 initUserAttributes()
方法)。
例如:
use yii\authclient\OAuth1; class MyAuthClient extends OAuth1 { public $authUrl = 'https://www.my.com/oauth/auth'; public $requestTokenUrl = 'https://www.my.com/oauth/request_token'; public $accessTokenUrl = 'https://www.my.com/oauth/access_token'; public $apiBaseUrl = 'https://www.my.com/apis/oauth/v1'; protected function initUserAttributes() { return $this->api('userinfo', 'GET'); } }
你也可以指定默认的验证范围。
注意:某些 OAuth 提供商可能并不严格遵循 OAuth 标准,或没有清晰说明与 OAuth 标准的差别, 可能需要为实现这些可客户端做额外的工作。