最近在作RESTful API認證功能,記錄整個過程,方便之後查看。本文參照了 http://www.javashuo.com/article/p-uzsydcfj-mh.html部份內容,感謝該做者的分享,如下內容根據個人項目實際狀況進行了調整。php
和Web應用不一樣,RESTful APIs 一般是無狀態的, 也就意味着不該使用 sessions 或 cookies, 所以每一個請求應附帶某種受權憑證,由於用戶受權狀態可能沒經過 sessions 或 cookies 維護, 經常使用的作法是每一個請求都發送一個祕密的 access token 來認證用戶, 因爲 access token 能夠惟一識別和認證用戶,API 請求應經過 HTTPS 來防止man-in-the-middle (MitM) 中間人攻擊.web
上方進行簡單介紹,內容來自 Yii Framework 2.0 權威指南segmentfault
繼續上一篇 的內容(這裏暫時使用默認User數據表,正式環境請分離不一樣的數據表來進行認證)api
繼上篇的 User 數據表,咱們還須要增長一 個 access_token 和 expire_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', // ],
將 common\models\User 類拷貝到 api\models\ 目錄下,修改命名空間爲 api\modelscookie
<?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(); } } }
打開 api\config\main.php 修改 components 屬性,添加下列代碼:session
'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將沒法訪問,返回 401app
實現認證步驟:dom
具體實現方式以下:
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 ] ]); } ...
打開 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; } } ... }
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 來進行攜帶
Authorization:Bearer YYdpiZna0hJGhjsfqwxUeHEgLDfHEjB-
注意 Bearer 和你的token中間是有 一個空格的,不少同窗在這個上面碰了不少次
以上就是基於YII2.0 RESTful 認證的內容。
本文參照了 http://www.javashuo.com/article/p-uzsydcfj-mh.html部份內容,感謝該做者的分享,以上內容根據個人項目實際狀況進行了調整。