为 Yii2 RESTful API 添加用户登录1
在Yii中默认附带RESTful风格api模块,由controller+路由规则就可以操作module层进行数据库操作。不考虑请求数量的情况下,跟随官方文档方法实现一个带权限验证的RESTful API demo
前期准备条件
安装框架
本次不考虑高级框架中的widget部件,所以选用使用基础版
在工作目录下使用composer运行
composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic
安装完成后,按照官方文档说明配置Apache/Nginx服务器Nginx更适合做静态资源服务器,推荐使用apache
配置URL-rewrite(apache)
设置文档根目录为 "basic/web"
DocumentRoot "path/to/basic/web"
<Directory "path/to/basic/web">
# 开启 mod_rewrite 用于美化 URL 功能的支持(译注:对应 pretty URL 选项)
RewriteEngine on
# 如果请求的是真实存在的文件或目录,直接访问
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# 如果请求的不是真实文件或目录,分发请求至 index.php
RewriteRule . index.php
# if $showScriptName is false in UrlManager, do not allow accessing URLs with script name
RewriteRule ^index.php/ - [L,R=404]
# ...其它设置...
</Directory>
通常情况下,此时已经开启url-rewrite功能了,但是很有可能在你的服务器下有多个网站,此时就要针对每一个网站的web目录配置单独的 .htaccess
文件
在app/web
目录下新建<code>
.htaccess`文件,写入以下内容
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
对于开启rest服务后,GET可以请求到数据,POST显示400错误。是因rewrite规则未生效,应该在Apache中htpp.conf中开启
AllowOverride all
,使单独配置生效
使用数据库进行用户登录验证
连接数据库
在app/config/db
中配置Mysql用户名和密码。
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=你的数据库名',
'username' => '你的用户名',
'password' => '你的密码',
'charset' => 'utf8',
];
创建数据表
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`pid` int(11) NOT NULL DEFAULT '0' COMMENT '父id',
`username` char(70) NOT NULL COMMENT '用户名',
`password` char(70) NOT NULL COMMENT '密码',
`type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '用户类型(0:会员,1:管理员)',
`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:0禁止;1正常',
`update_password` int(10) NOT NULL DEFAULT '0' COMMENT '修改密码次数',
`token` varchar(32) DEFAULT NULL COMMENT '32位hash',
`accessToken` varchar(32) DEFAULT NULL COMMENT 'access_token32位MD5Hash',
`authKey` varchar(32) DEFAULT NULL COMMENT 'authKey',
`created_at` int(11) DEFAULT NULL COMMENT 'created_at-yii自带字段',
`updated_at` int(11) DEFAULT NULL COMMENT 'updated_at_yii自带字段'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='登录管理表';
ALTER TABLE `user`
ADD PRIMARY KEY (`id`),
ADD KEY `pid` (`pid`),
ADD KEY `username` (`username`),
ADD KEY `type` (`type`),
ADD KEY `status` (`status`);
ALTER TABLE `user`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=6;
在Mysql中随意插入两条数据即可。
>
其中created_at
updated_at
两字段为Yii框架自带字段,长度为11的timestemp,这两个字段的缺失不会对查询用户影响,即不影响登录功能;但是在RESTful的UPDATE操作中缺失将引发500错误,此处需要打开,进行用户注册部分的验证。
使用数据库进行用户登录
在Yii中默认拥有用户鉴权IdentityInterface
接口类,在活动记录中继承ActiveRecord
并implements IdentityInterface
即可在module中实现安全的用户鉴权查询。Yii框架在安装后已经给我们一个登录框架的最小实现
包括cookie管理、form表单的_csrf认证等,但是并没有连接数据库,而是在登录用户modelsapp/models/User.php
中以数组代替数据库查询。
为了连接数据库进行用户登录,应满足活动记录Active Record规则。
- 重命名原app/models/User.php
为app/models/User.php.bac
- 使用Gii工具生成models/User.php
- 实现User.php中关于用户鉴权活动记录的构造方法: findIdentity()
、findIdentityByAccessToken()
、findByUsername()
、 getId()
、getAuthKey()
、validateAuthKey()
、validatePassword()
<?php
/*
path:app/models/User.php
*/
namespace app\models;
use Yii;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
use yii\behaviors\TimestampBehavior;
use yii\base\NotSupportedException;
class User extends ActiveRecord implements IdentityInterface
{
public static function tableName()
{
return 'user';
}
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['pid', 'type', 'status', 'update_password'], 'integer'],
[['username', 'password'], 'required'],
[['created_time'], 'safe'],
[['username', 'password'], 'string', 'max' => 70],
[['token', 'accessToken', 'authKey'], 'string', 'max' => 32],
];
}
public function attributeLabels()
{
return [
'id' => 'ID',
'pid' => 'Pid',
'username' => 'Username',
'password' => 'Password',
'type' => 'Type',
'created_time' => 'Created Time',
'status' => 'Status',
'update_password' => 'Update Password',
'token' => 'Token',
'accessToken' => 'Access Token',
'authKey' => 'Auth Key',
];
}
/**
* {@inheritdoc}
* 以下为UserQuery活动查询生成器ActiveQuery的配置项,当需要多表联查的时候,配置项在ActiveQuery中配置。若gii生成代码时未选择ActiveQuery此项不生成。
* @return UserQuery the active query used by this AR class.
*/
public static function find()
{
return new UserQuery(get_called_class());
}
/**
* 根据给到的ID查询身份。
*
* @param string|integer $id 被查询的ID
* @return IdentityInterface|null 通过ID匹配到的身份对象
*/
public static function findIdentity($id)
{
return static::findOne($id);
}
/**
* 根据 token 查询身份。
*
* @param string $token 被查询的 token
* @return IdentityInterface|null 通过 token 得到的身份对象
*/
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['accessToken' => $token]);
}
/**
* @return int|string 当前用户ID
*/
public function getId()
{
return $this->id;
}
public static function findByUsername($username)
{
return static::findOne(['username' => $username]) || null;
//也可以写成如下,最终目的是在LoginForm中调用findByUsername()时,返回一个数组,如果是个空数组则表示未登录,若登录成功则返回一个包含用户登录信息的数组
// $user = User::find()
// ->where(['username'=>$username])
// ->asArray()
// ->one();
// if ($user){
// return new static($user);
// }
// return null;
}
/**
* {@inheritdoc}
*/
public function getAuthKey()
{
return $this->authKey;
}
/**
* {@inheritdoc}
*/
public function validateAuthKey($authKey)
{
return $this->authKey === $authKey;
}
/**
* Validates password
*
* @param string $password password to validate
* @return bool if password provided is valid for current user
*/
public function validatePassword($password)
{
return $this->password === $password;
}
}
重写 findeByUsername()
方法
其中,User::findeByUsername()
方法在原自带User.php中是使用遍历数组的方式进行查询的,此处应修改为查询数据库操作,在RESTful风格api中,所有用户的登录均为无状态的,所以实现findeByUsername()
方法是必须的,Yii会根据用户的access-token查询username并返回$user
已登录用户的实例方便进行下一步权限控制等操作。
<?php
/*
path:app/models/User.php
*/
namespace app\models;
class User extends ActiveRecord implements IdentityInterface
{
+ public static function findByUsername($username)
+ {
+
+ $user = User::find()
+ ->where(['username'=>$username])
+ ->asArray()
+ ->one();
+ if ($user){
+ return new static($user);
+ }
+ return null;
+ }
}
>
重写findByUsername()
方法在用户进行鉴权的时候,在LoginForm中触发findByUsername()方法,并返回一个$user实例与LoginForm中数据进行对比,对比通过即为登录成功
此时我们已经完成修改登录用户查询数据表的工作。
打开 localhost验证登录完成。
>
RESTFulAPI的特点是无状态,使用token验证用户登录:下面我们先实现一个无验证的最简单RESTful-api,之后再添加登录验证
配置urlManager/json允许
在访问时url,我们的url依旧是常规的参数形式http://localhost/index.php?r=site¶m=xxx
的形式,我们希望使用rest风格的路由格式,则需要配置urlManager
。我们将app/config/web.php
中的urlManager配置项单独成一个配置文件,方便后续修改。
- 打开app/config/web.php
中的urlManager部分的注释
- 在app/config/
目录下新建urlManager.php
文件
<?php
return [
'enablePrettyUrl' => true, //美化url==ture
'enableStrictParsing' => false, //不启用严格解析
'showScriptName' => false, //隐藏index.php
'rules' =>[
/**
* 下一步将在此处针对各个controller配置各自的url重写规则
*/
+ ['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
//以上为关于Usercontroller的rule配置项。
],
];
- 在
app/config/web.php
中引入urlManager
app/config/web.php
$urlManager = require __DIR__.'/urlManager.php';
$config = [
'components' => [
'urlManager' => $urlManager,
],
];
此时访问http://localhost/gii
即可进入gii工具则表示url生效
配置json响应(json request allow)
在启用urlManager后,应该启用json请求允许,方便框架接收json请求。对于框架接收json请求,需要单独配置。
// app/config/web.php
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
'text/json' => 'yii\web\JsonParser',// 在post请求服务端的时候, 根据content-type寻找正确的json解释器,不配置有可能报400错误
]
]
yii默认启用包括json
xml
等格式在内的所有响应格式,随意对于response
项无需配置。在http头中指定A ccept:applcation/json
即可通知Yii返回json格式响应。
UserController
Yii是一个MVC框架,对于RESTful而言,不需要考虑View层的事。只需要完成Model层的数据CURD及Controller层的路由管理即可。
完成UserContrller
在/app/controller/
目录下新建UserControler
控制器。
对于RESTful控制器,Yii实现了yii\rest\ActiveController
基类,UserController
需要继承此类。并在其中$modelClass
指定活动记录表ActiveRecord
。
namespace app\controllers;
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
}
GET请求测试接口完成度
使用curl测试GET请求User控制器即可
$ curl -i -H "Accept:application/json" "http://localhost/users"
若返回User数据表中数据则表示第一步实现REST已经成功。对于某些时候post请求报401错误,则查看Apache/Nginx配置文件中Rewrite规则。