第一篇博客,最近接触了JWT,所以想着去试试这个,于是就尝试着去做一做。
现在主流的接口认证方式有两种,一种是OAuth2.0(即微信支付宝等的第三方登录在使用的方式),另一种就是JWT了。
我这边在查询资料后,感觉最让我清楚了解JWT的就是《前后端分离之JWT用户认证》这篇博客了。看完之后,让我觉得JWT的本质就是两个可解密的字段加一个验证加密字段拼接的字符串。服务器端可以解密两个可解密的字段获取数据,然后根据两个可解密字段加其他加密方式生成加密字段去验证传递过来的加密字段。成功则直接使用解密的数据,避免查询数据库等方式验证用户信息。
之后又查看到了另一篇博客:《JWT》,这篇博客让我了解了一些JWT的弊端。主要是两个问题,一个是续期问题,如果需要在有效期内登陆会刷新验证时间,则这个问题比较麻烦;另一个是用户数据修改问题,主要是用户修改密码后,则原先的登陆状态应该失效,不过JWT的话还是能验证通过。这个两个问题,我想到的是在Redis的哈希数据里保存用户的数据修改时间和用户下每个JWT的过期时间。不过这样就要每次去查询Reids获取、修改数据。但这样也就加重了服务器的负担,是否可行需要另外再探索。
登录代码
public function actionLogin()
{
$request = Yii::$app->request;
$username = $request->post('username','');//账号
$password = $request->post('password','');//密码
$data['errcode'] = '1';
$data['errmsg'] = '登录失败';
$model = new LoginUserForm();//验证登录模型
$model->username = $username;
$model->password = $password;
if ($model->login()) {//验证登录
//获取redis里用户修改密码等重要数据时间
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
//Yii::$app->user->identity->id 用户ID
$exp = $redis->hGet('JWT_Login_exp',Yii::$app->user->identity->id);
//未设置则设置时间
if(empty($exp)){
$redis->hSet('JWT_Login_exp',Yii::$app->user->identity->id,time());
}
//Yii::$app->params['JWT']['alg'] 公共加密方式
$header = array(
'alg'=>Yii::$app->params['JWT']['alg'],
'typ'=>'JWT'
);
//Yii::$app->params['JWT']['exp'] 过期时间
//iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。
$payload = array(
'iss'=>'jwp.com',
'exp'=>time()+Yii::$app->params['JWT']['exp'],
'sub'=>Yii::$app->user->identity->id,
'iat'=>time(),
);
$header = base64_encode(json_encode($header));
$payload = base64_encode(json_encode($payload));
//Yii::$app->params['JWT']['key'] 加密秘钥
//hash_hmac 用于加密
$sign = hash_hmac(Yii::$app->params['JWT']['alg'],$header.$payload,Yii::$app->params['JWT']['key']);
//拼接JWT返回
$jwt = $header.'.'.$payload.'.'.$sign;
$data['errcode'] = '0';
$data['errmsg'] = '登录成功';
$data['data']['token'] = $jwt;
}else{
$error = $model->getErrors();
$error = reset($error);
$data['errmsg'] = reset($error);
}
echo json_encode($data);
}
验证JWT
/**
* 判断是否登录
*/
public function isLogin(){
$data['errcode'] = '-1';
$data['errmsg'] = '认证信息错误';
if(isset($_SERVER['HTTP_AUTHORIZATION'])){
//分割JWT
$token = explode('.',$_SERVER['HTTP_AUTHORIZATION']);
//JWT需携带三部分数据
if(count($token)==3){
//生成变量
list($header,$payload,$sign)=$token;
$headerData = json_decode(base64_decode($header),true);
$payloadData = json_decode(base64_decode($payload),true);
//判断是否有加密方式
if(isset($headerData['alg'])){
//生成加密字符串
$hash = hash_hmac($headerData['alg'],$header.$payload,Yii::$app->params['JWT']['key']);
if(!empty($hash) && $hash == $sign){
//有效期未过期
if($payloadData['exp']>=time()) {
//获取redis哈希中用户上次修改密码的时间
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$exp = $redis->hGet('JWT_Login_exp',$payloadData['sub']);
//修改密码时间大于创建时间,则jwt失效
if(!empty($exp) && $exp<=$payloadData['iat']){
$this->UserInfo['id'] = $payloadData['iat'];
$data['errcode'] = '0';
$data['errmsg'] = '认证成功';
}else{
$data['errmsg'] = '认证信息以过期';
}
}else{
$data['errmsg'] = '认证信息以过期';
}
}
}
}else{
$data['errmsg'] = '认证信息格式错误';
}
}else{
$data['errmsg'] = '未传递认证信息';
}
return $data;
}
在写完后端程序后,使用Postman做本地测试,结果死活获取不到AUTHORIZATION,在查询许久后才发现,Apache服务器单独把AUTHORIZATION字段删除了,需要另外配置过才可以。参考的博客是:《apache服务器 php获取不到Authorization》。其中第二种方式在我这行不通,不知是什么问题,有清楚的请解答下,谢谢。具体操作如下:
修改.htaccess文件
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
#增加如下内容
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
</IfModule>
在使用JWT中的基本流程就是这样,第一次使用这个,更多细节问题可能还不清楚,需后面再进一步完善了