美文网首页
Yii2.0 RESTful API 认证教程【令牌验证】

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

作者: willeny | 来源:发表于2018-12-25 15:25 被阅读0次

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

    认证介绍

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

    认证方式

    • HTTP 基本认证 :access token 当作用户名发送,应用在access token可安全存在API使用端的场景, 例如,API使用端是运行在一台服务器上的程序。
    • 请求参数: access token 当作API URL请求参数发送,例如 https://example.com/users?access-token=xxxxxxxx, 由于大多数服务器都会保存请求参数到日志, 这种方式应主要用于JSONP 请求,因为它不能使用HTTP头来发送 access token
    • OAuth 2 : 使用者从认证服务器上获取基于 OAuth2 协议的 access token, 然后通过 HTTP Bearer Tokens 发送到 API 服务器。

    上方进行简单介绍,内容来自 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
    
    • 打开 你的项目目录 /console/migrations/m181224_075747_add_column_access_token_user.php 修改如下内容:
        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;
        }
    
    • 打开 你的项目目录 /console/migrations/m181224_092333_add_column_expire_at_user.php 修改如下内容:
        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 应用组件:
    'user' => [
                'identityClass' => 'api\models\User',
                'enableAutoLogin' => true,
                'enableSession'=>false,
                //'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true],
            ],
    
    • 将 session 组件注释掉,或删掉
    //        'session' => [
    //            // this is the name of the session cookie used for login on the backend
    //            'name' => 'advanced-api',
    //        ],
    
    • 编写 api\models\User.php 实现认证类,继承 IdentityInterface
      common\models\User 类拷贝到 api\models\ 目录下,修改命名空间为 api\models
    <?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
    {
        ...
        ...
    }
    
    • common\models\LoginForm.php 类拷贝到 *api\models* 目录下,修改命名空间,并重写 login 方法:
    <?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;
    }
    
    
    • 上方代码给 User 模型添加了一个 generateAccessToken() 方法,因此我们到 api\models\User.php 中添加此方法
    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;
        }
    }
    
    • 接下来在api\controllers\新加一个控制器 命名为 UserController 并继承 yii\rest\ActiveController,编写登录 Login 方法,具体代码如下:
    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();
            }
        }
    }
    
    • 最后新增一条 URL 规则
      打开 api\config\main.php 修改 components 属性,添加下列代码:
    '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() 方法.

    具体实现方式如下:

    • 打开之前的 User 控制器(** api\controllers\UserController.php** ),增加以下内容:
    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
                ]
            ]);
        }
    ...    
    
    • 实现 findIdentityByAccessToken() 方法:
      打开 api\models\User.php 重写 findIdentityByAccessToken() 方法
    ...
    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;
            }
        }
        ...
    }
    
    • 打开 ** api\controllers\UserController.php** ,增加 Test方法,用于测试令牌验证
        public function actionTest()
        {
            return ['status'=>'success'];
        }
    
    • 修改 api\config\main.php
            '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部分内容,感谢该作者的分享,以上内容根据我的项目实际情况进行了调整。

    相关文章

      网友评论

          本文标题:Yii2.0 RESTful API 认证教程【令牌验证】

          本文链接:https://www.haomeiwen.com/subject/mpuflqtx.html