Yii2 RESTful API Auth Mechanism

| | | |~filters/
| | | | |~auth/
| | | | | |-AuthInterface.php
| | | | | |-AuthMethod.php
| | | | | |-CompositeAuth.php
| | | | | |-HttpBasicAuth.php
| | | | | |-HttpBearerAuth.php
| | | | | `-QueryParamAuth.php

一、HttpBearer 驗證方式:

<?php
namespace app\controllers;
use yii\rest\ActiveController;
use yii\filters\auth\HttpBasicAuth;
use yii\helpers\ArrayHelper;
#use yii\filters\auth\CompositeAuth;
#use yii\filters\auth\HttpBasciAuth;
use yii\filters\auth\HttpBearerAuth;
#use yii\filters\auth\QueryParamAuth;

class UsersController extends ActiveController
{
    public $modelClass = 'app\models\User2';

	public function behaviors()
	{
		return ArrayHelper::merge(parent::behaviors(), [
			'authenticator' => [
				'class' => HttpBearerAuth::className(),
				#這個地方使用`ComopositeAuth` 混合認證
				#'class' => CompositeAuth::className(),
				#`authMethods` 中的每一個元素都應該是 一種 認證方式的類或者一個 配置數組
				//'authMethods' => [
					//HttpBasicAuth::className(),
					//HttpBearerAuth::className(),
					//QueryParamAuth::className(),
				//]
			]
		]);
	}

下面來分析相關的源碼,看看 yii2/rest 是如何處理權限驗證的。

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\filters\auth;

use Yii;
use yii\web\UnauthorizedHttpException;

/**
 * HttpBearerAuth is an action filter that supports the authentication method based on HTTP Bearer token.
 *
 * You may use HttpBearerAuth by attaching it as a behavior to a controller or module, like the following:
 *
 * public function behaviors()
 * {
 *     return [
 *         'bearerAuth' => [
 *             'class' => \yii\filters\auth\HttpBearerAuth::className(),
 *         ],
 *     ];
 * }
 * 
 * @author Qiang Xue <[email protected]>
 * @since 2.0
 */
class HttpBearerAuth extends AuthMethod
{
    /**
     * @var string the HTTP authentication realm
     */
    public $realm = 'api';

    /**
     * @inheritdoc
     */
    public function authenticate($user, $request, $response)
    {
        $authHeader = $request->getHeaders()->get('Authorization');
        if ($authHeader !== null && preg_match("/^Bearer\\s+(.*?)$/", $authHeader, $matches)) {
            $identity = $user->loginByAccessToken($matches[1], get_class($this));
            if ($identity === null) {
                $this->handleFailure($response);
            }
            return $identity;
        }

        return null;
    }

    /**
     * @inheritdoc
     */
    public function handleFailure($response)
    {
        $response->getHeaders()->set('WWW-Authenticate', "Bearer realm=\"{$this->realm}\"");
        throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
    }
}

要想順利通過此驗證,需要在 Header 中加入一項:
Authorization:Bearer ganiks-token

如果沒有如上提供正確的access-token, 則會得到一個:

401 Unauthorized
Www-Authenticate:Bearer realm="api"

二、HttpBasic 驗證方式:

class HttpBasicAuth extends AuthMethod
{
    /**
     * @var string the HTTP authentication realm
     */
    public $realm = 'api';
    /**
     * @var callable a PHP callable that will authenticate the user with the HTTP basic auth information.
     * The callable receives a username and a password as its parameters. It should return an identity object
     * that matches the username and password. Null should be returned if there is no such identity.
     *
     * The following code is a typical implementation of this callable:
     *
     * function ($username, $password) {
     *     return \app\models\User::findOne([
     *         'username' => $username,
     *         'password' => $password,
     *     ]);
     * }
     *
     * If this property is not set, the username information will be considered as an access token
     * while the password information will be ignored. The [[\yii\web\User::loginByAccessToken()]]
     * method will be called to authenticate and login the user.
     */
    public $auth;


    /**
     * @inheritdoc
     */
    public function authenticate($user, $request, $response)
    {
        $username = $request->getAuthUser();
        $password = $request->getAuthPassword();

        if ($this->auth) {
            if ($username !== null || $password !== null) {
                $identity = call_user_func($this->auth, $username, $password);
                if ($identity !== null) {
                    $user->switchIdentity($identity);
                } else {
                    $this->handleFailure($response);
                }
                return $identity;
            }
        } elseif ($username !== null) {
            $identity = $user->loginByAccessToken($username, get_class($this));
            if ($identity === null) {
                $this->handleFailure($response);
            }
            return $identity;
        }

        return null;
    }

    /**
     * @inheritdoc
     */
    public function handleFailure($response)
    {
        $response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
        throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
    }
}

如果要順利通過此驗證,有2種方式:

  1. access-token 作爲 Basci Authusername 一起請求,至於password則不必管
  2. 定義下 use yii\filters\auth\HttpBasciAuth$auth爲一個驗證的方法,來根據請求中的username:password來返回一個 identity

默認的, $auth是沒有定義的,此時,在Header中加入:
Authorization:Basic Z2FuaWtzLXRva2VuOg==
這個碼是由username: ganiks-token 經過base64編碼得到的,可以得到正確的響應
反而是用username:password(正確的)也無法得到響應

第二種方式,
可以配置 use yii\filters\auth\HttpBasciAuth$auth 爲一個 callable如下:

	public $auth;
	public function auth ($username, $password) {
		return \app\models\User2::findOne([
			'username' => $username,
			'password' => $password,
		]);
	}

這裏是個遺留問題,http://www.yiiframework.com/forum/index.php/topic/56916-how-to-define-a-callable-variable-for-a-class/

下面是一個臨時的解決方案,使用 username:password_hash驗證

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\filters\auth;

use Yii;
use yii\web\UnauthorizedHttpException;
class HttpBasicAuth extends AuthMethod
{
	public $auth;

	public function auth ($username, $password) {
		return \app\models\User2::findOne([
			'username' => $username,
			'password_hash' => $password,
		]);
	}


    /**
     * @inheritdoc
     */
    public function authenticate($user, $request, $response)
    {
        $username = $request->getAuthUser();
        $password = $request->getAuthPassword();

        if ($this->auth($username, $password)) {
            if ($username !== null || $password !== null) {
                //$identity = call_user_func($this->auth, $username, $password);
                $identity = $this->auth($username, $password);
                if ($identity !== null) {
                    $user->switchIdentity($identity);
                } else {
                    $this->handleFailure($response);
                }
                return $identity;
            }
        } elseif ($username !== null) {
            $identity = $user->loginByAccessToken($username, get_class($this));
            if ($identity === null) {
                $this->handleFailure($response);
            }
            return $identity;
        }

        return null;
    }

    /**
     * @inheritdoc
     */
    public function handleFailure($response)
    {
        $response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\"");
        throw new UnauthorizedHttpException('You are requesting with an invalid access token.');
    }
}

三、QueryParam 驗證方式:

此方式最簡單,只需要在請求的URL後面加上?access-toekn=ganiks-token即可驗證

class QueryParamAuth extends AuthMethod
{
    /**
     * @var string the parameter name for passing the access token
     */
    public $tokenParam = 'access-token';

    /**
     * @inheritdoc
     */
    public function authenticate($user, $request, $response)
    {
        $accessToken = $request->get($this->tokenParam);
        if (is_string($accessToken)) {
            $identity = $user->loginByAccessToken($accessToken, get_class($this));
            if ($identity !== null) {
                return $identity;
            }
        }
        if ($accessToken !== null) {
            $this->handleFailure($response);
        }

        return null;
    }

    /**
     * @inheritdoc
     */
    public function handleFailure($response)
    {
        throw new UnauthorizedHttpException(Yii::t('yii', 'You are requesting with an invalid access token.'));
    }

四、compositeAuth綜合驗證方式:

class CompositeAuth extends AuthMethod
{
    /**
     * @var array the supported authentication methods. This property should take a list of supported
     * authentication methods, each represented by an authentication class or configuration.
     *
     * If this property is empty, no authentication will be performed.
     *
     * Note that an auth method class must implement the [[\yii\filters\auth\AuthInterface]] interface.
     */
    public $authMethods = [];


    /**
     * @inheritdoc
     */
    public function beforeAction($action)
    {
        return empty($this->authMethods) ? true : parent::beforeAction($action);
    }

    /**
     * @inheritdoc
     */
    public function authenticate($user, $request, $response)
    {
        foreach ($this->authMethods as $i => $auth) {
            $this->authMethods[$i] = $auth = Yii::createObject($auth);
            if (!$auth instanceof AuthInterface) {
                throw new InvalidConfigException(get_class($auth) . ' must implement yii\filters\auth\AuthInterface');
            }

            $identity = $auth->authenticate($user, $request, $response);
            if ($identity !== null) {
                return $identity;
            }
        }

        if (!empty($this->authMethods)) {
            /* @var $auth AuthInterface */
            $auth = reset($this->authMethods);
            $auth->handleFailure($response);
        }

        return null;
    }
發佈了125 篇原創文章 · 獲贊 22 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章