Yii2.0 RESTful API 认证教程【令牌验证】
认证介绍
和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 认证的内容。