手册教程~

YII 客户端认证(Auth Clients)

用于 Yii 2 的 AuthClient 扩展

该扩展为 Yii framework 2.0 添加 OpenIDOAuth 和 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]]: 同时定义 requiredAttributesoptionalAttributes.

  • [[yii\authclient\OAuth1]] 和 [[yii\authclient\OAuth2]]: 定义 scope 域。注意, 不同的提供商对于范围的格式定义可能不同。

提示:如果你正在使用若干个不同的客户端,你可以使用 [[yii\authclient\BaseClient::normalizeUserAttributeMap]] 统一返回属性的列表。

通过补充 API 调用获取额外数据

[[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,
        ];
    }
}

根据实际使用的基类,你需要重新声明不同的域和方法。

[[yii\authclient\OpenId]]

你要做的所有工作就是通过重新声明 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',
    ];
}

[[yii\authclient\OAuth2]]

你需要指定:

  • 验证 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 标准的差别, 可能需要为实现这些可客户端做额外的工作。

[[yii\authclient\OAuth1]]

你需要指定:

  • 验证 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 标准的差别, 可能需要为实现这些可客户端做额外的工作。