Yii2.0 RESTful API 认证教程【令牌验证】

2018-12-25  本文已影响0人  willeny

最近在做RESTful API认证功能,记录整个过程,方便以后查看。本文参照了 https://segmentfault.com/a/1190000016368603部分内容,感谢该作者的分享,以下内容根据我的项目实际情况进行了调整。

认证介绍

和Web应用不同,RESTful APIs 通常是无状态的, 也就意味着不应使用 sessionscookies, 因此每个请求应附带某种授权凭证,因为用户授权状态可能没通过 sessionscookies 维护, 常用的做法是每个请求都发送一个秘密的 access token 来认证用户, 由于 access token 可以唯一识别和认证用户,API 请求应通过 HTTPS 来防止man-in-the-middle (MitM) 中间人攻击.

认证方式

上方进行简单介绍,内容来自 Yii Framework 2.0 权威指南

实现步骤

继续上一篇 的内容(这里暂时使用默认User数据表,正式环境请分离不同的数据表来进行认证)

需要添加的数据内容

上篇User 数据表,我们还需要增加一 个 access_tokenexpire_at 的字段,

./yii migrate/create add_column_access_token_to_user
./yii migrate/create add_column_expire_at_to_user
    public function up()
    {
        $ret = $this->db->createCommand("SELECT * FROM information_schema.columns WHERE table_schema = DATABASE()  AND table_name = 'user' AND column_name = 'access_token'")->queryOne();//判断user表是否有'access_token'这个字段
        if (empty($ret)) {
            $this->addColumn('user', 'access_token', $this->string(255)->defaultValue(NULL)->comment('令牌'));
        }
    }

    public function down()
    {
        $this->dropColumn('user', 'access_token');
        return true;
    }
    public function up()
    {
        $ret = $this->db->createCommand("SELECT * FROM information_schema.columns WHERE table_schema = DATABASE()  AND table_name = 'user' AND column_name = 'expire_at'")->queryOne();
        if (empty($ret)) {
            $this->addColumn('user', 'expire_at', $this->integer(11)->defaultValue(NULL)->comment('令牌过期时间'));
        }
    }

    public function down()
    {
        $this->dropColumn('user', 'expire_at');
        return true;
    }
./yii migrate

配置

打开 api\config\main.php

'user' => [
            'identityClass' => 'api\models\User',
            'enableAutoLogin' => true,
            'enableSession'=>false,
            //'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true],
        ],
//        'session' => [
//            // this is the name of the session cookie used for login on the backend
//            'name' => 'advanced-api',
//        ],
<?php
namespace api\models;

use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
...
class User extends ActiveRecord implements IdentityInterface
{
    ...
    ...
}
<?php
namespace api\models;

use Yii;
use yii\base\Model;
...
...

const EXPIRE_TIME = 604800;//令牌过期时间,7天有效

public function login()
{
        if ($this->validate()) {
            //return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
            if ($this->getUser()) {
                $access_token = $this->_user->generateAccessToken();
                $this->_user->expire_at = time() + static::EXPIRE_TIME;
                $this->_user->save();
                Yii::$app->user->login($this->_user, static::EXPIRE_TIME);
                return $access_token;
            }
        }
        return false;
}

namespace api\models;

use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
use yii\web\UnauthorizedHttpException;
...
...
class User extends ActiveRecord implements IdentityInterface
{
    ...
    ...
    
   /**
     * 生成accessToken字符串
     * @return string
     * @throws \yii\base\Exception
     */
    public function generateAccessToken()
    {
        $this->access_token=Yii::$app->security->generateRandomString();
        return $this->access_token;
    }
}
namespace api\controllers;
use api\models\LoginForm;
use yii\rest\ActiveController;
use yii;


class UserController extends ActiveController
{
    public $modelClass = 'api\models\User';

    public function actions()
    {
        $action= parent::actions(); // TODO: Change the autogenerated stub
        unset($action['index']);
        unset($action['create']);
        unset($action['update']);
        unset($action['delete']);
    }

    public function actionIndex()
    {
        //你的代码
    }

    /**
     * 登陆
     * @return array
     * @throws \yii\base\Exception
     * @throws \yii\base\InvalidConfigException
     */
    public function actionLogin()
    {
        $model = new LoginForm();
        if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->login()) {
            return [
                'access_token' => $model->login(),
            ];
        } else {
            return $model->getFirstErrors();
        }
    }
}
'urlManager' => [
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        ['class' => 'yii\rest\UrlRule', 
            'controller' => 'user',
            'extraPatterns'=>[
                'POST login'=>'login',
            ],
        ],
    ],
]

使用一个调试工具来进行测试 http://youdomain/users/login 记住是POST 请求发送,假如用POSTMAN有问题的话指定一下 Content-Type:application/x-www-form-urlencoded
ok,不出意外的话,相信你已经可以收到一个access_token 了,接下来就是如何使用这个token,如何维持认证状态,达到不携带这个token将无法访问,返回 401

维持认证状态

实现认证步骤:

  1. 在你的 REST 控制器类中配置 authenticator 行为来指定使用哪种认证方式
  2. 在你的 user identity class 类中实现 yiiwebIdentityInterface::findIdentityByAccessToken() 方法.

具体实现方式如下:

use yii\helpers\ArrayHelper;
use yii\filters\auth\QueryParamAuth;

...//此处省略一些代码了

    public function behaviors()
    {
        return ArrayHelper::merge(parent::behaviors(), [
            'authenticatior' => [
                'class' => QueryParamAuth::className(), //实现access token认证
                'except' => ['login'], //无需验证access token的方法,注意区分$noAclLogin
            ]
        ]);
    }
...    
...
use yii\web\UnauthorizedHttpException;
...

class User extends ActiveRecord implements IdentityInterface
{
    ...
    ...
    
    /**
     * {@inheritdoc}
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
//        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
        $user = static::find()->where(['access_token' => $token, 'status' => self::STATUS_ACTIVE])->one();
        if (!$user) {
            return false;
        }
        if ($user->expire_at < time()) {
            throw new UnauthorizedHttpException('the access - token expired ', -1);
        } else {
            return $user;
        }
    }
    ...
}
    public function actionTest()
    {
        return ['status'=>'success'];
    }
        'urlManager' => [
            'enablePrettyUrl' => true,
            'enableStrictParsing' => true,
            'showScriptName' => false,
            'rules' => [
                ['class' => 'yii\rest\UrlRule',
                    'controller' => 'user',
                    //'pluralize' => false,    //设置为false 就可以去掉复数形式了
                    'extraPatterns'=>[
                        'GET test'=>'test',
                        'POST login'=>'login',
                    ],
                ],
            ],
        ]

接下来访问一下你的域名 http://youdomain/users/test,不携带任何参数是不是返回 401了?
ok,这里介绍两种访问方式,一种是URL访问,另一种是通过header 来进行携带

  1. http://youdomain/users/test?access-token=YYdpiZna0hJGhjsfqwxUeHEgLDfHEjB-
  2. 传递 header 头信息
Authorization:Bearer YYdpiZna0hJGhjsfqwxUeHEgLDfHEjB-

** 注意 Bearer 和你的token中间是有 一个空格的,很多同学在这个上面碰了很多次**
以上就是基于YII2.0 RESTful 认证的内容。

本文参照了 https://segmentfault.com/a/1190000016368603部分内容,感谢该作者的分享,以上内容根据我的项目实际情况进行了调整。

上一篇下一篇

猜你喜欢

热点阅读