乐趣区

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

最近在做 RESTful API 认证功能,记录整个过程,方便以后查看。本文参照了 https://segmentfault.com/a/1190000016368603 部分内容,感谢该作者的分享,以下内容根据我的项目实际情况进行了调整。
认证介绍
和 Web 应用不同,RESTful APIs 通常是无状态的,也就意味着不应使用 sessions 或 cookies,因此每个请求应附带某种授权凭证,因为用户授权状态可能没通过 sessions 或 cookies 维护,常用的做法是每个请求都发送一个秘密的 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?acc…,由于大多数服务器都会保存请求参数到日志,这种方式应主要用于 JSONP 请求,因为它不能使用 HTTP 头来发送 access token

OAuth 2 : 使用者从认证服务器上获取基于 OAuth2 协议的 access token,然后通过 HTTP Bearer Tokens 发送到 API 服务器。

上方进行简单介绍,内容来自 Yii Framework 2.0 权威指南
实现步骤
继续上一篇 的内容 (这里暂时使用默认 User 数据表,正式环境请分离不同的数据表来进行认证)
需要添加的数据内容
继上篇的 User 数据表,我们还需要增加一 个 access_token 和 expire_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
{


}
将 commonmodelsLoginForm.php 类拷贝到 apimodels* 目录下,修改命名空间, 并重写 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
维持认证状态
实现认证步骤:

在你的 REST 控制器类中配置 authenticator 行为来指定使用哪种认证方式
在你的 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 来进行携带

http://youdomain/users/test?a…
传递 header 头信息

Authorization:Bearer YYdpiZna0hJGhjsfqwxUeHEgLDfHEjB-
注意 Bearer 和你的 token 中间是有 一个空格的,很多同学在这个上面碰了很多次 以上就是基于 YII2.0 RESTful 认证的内容。
本文参照了 https://segmentfault.com/a/1190000016368603 部分内容,感谢该作者的分享,以上内容根据我的项目实际情况进行了调整。

退出移动版