欢迎大家访问我的博客 blog.ayla1688.cool
### 传统加密方式-有状态加密
用户登录后,我们分配给用户一个token, 然后将token 放在redis中一份,放在数据库中一份; 当用户携带token 访问接口时,我们一般会做以下判断:
> token是否正确
> token是否过期
通过以上的判断,我们可以确认用户的有效性。这中间就涉及到一个问题,我们需要取出自己保存的token, 先从redis中获取,如果redis中key过期或者redis宕机,我们就要从数据库中获取,无论从哪获取都涉及到IO。
这种方式就是有状态的加密。
### 无状态加密
用户登录之后,我们返回用户一些用于加密的字段, 用户访问接口时,使用接口入参,加密字段和固定字段key进行加密,生成不可你解析token ,一般使用md5加密, 服务器端拿到token,入参后, 通过同样的规则, 使用接口入参,加密字段和固定字段key进行加密得到tokenVerify, 对比token 和tokenVerify , 相同则用户有效,不同则用户无效。 无需访问redis和数据库。
这种方式就是无状态的加密。
###下面上代码,在实际应用中看下实现方式
首先我们有一个BaseController是基类控制器,做用户验证等基础工作
```
<?php
/**
*
* File Base
* author mselect
* DateTime 2019-12-01
* @return
*/
namespace app\wxmini\controller;
use think\App;
use think\exception\HttpResponseException;
use think\Response;
class BaseController extends \think\Controller
{
//用户ID
protected $user_id = 0;
//请求头
protected $header = array(
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Headers' => 'X-Requested-With,Content-Type,Api-Token,Api-Version,Api-Timestamp,Api-User,Api-Sign',
'Access-Control-Allow-Methods' => 'GET,POST,OPTIONS',
);
//original 准过滤白名单
public $whiteOrigin = array();
protected $whiteList = array();
//api版本号
protected $versionToKey = [
'1.0.0' => '77fruit',
];
protected $codeMark = [
'SUCCESS' => 1, //返回成功
'ERROR' => -1, //返回失败
'NOMORE' => -50, //没有要加载的更多数据
'NODATA' => -100, //没有数据
'NOTLOGIN' => -1000, //未登录
'DATAERROR' => -2000, //数据错误
'SIGNEMPTY' => -2001, //签名为空
'SIGNERROR' => -2002, //签名错误
'LOGINERROR' => -2010, //登陆失败
'SECURITYERROR' => -10000, //非法登陆
'SYSTEMERROR' => -2333, //系统错误
];
public function __construct(App $app = null)
{
parent::__construct($app);
if(in_array($this->request->action(), $this->whiteList)){
return true;
}
$this->_checkPostData();
$this->_initUser();
}
/**
* 用户信息初始化
* Function _initUser
* author mselect
* DateTime 2019-12-01
*/
public function _initUser(){
$token = (string)$this->request->header('Api-Token', '');
$user_id = (string)$this->request->header('Api-User', 0);
$timestamp = (string)$this->request->header('Api-Timestamp', '');
$version = (string)$this->request->header('Api-Version', '');
if( empty($this->versionToKey[$version])){
$this->outPut('SECURITYERROR', '非法访问');
}
if($token != '' && $user_id >0 && $timestamp != '' && $version != ''){
//均带着登陆所需参数
$newMd5 = md5($this->versionToKey[$version] . $timestamp . $user_id . $version );
if($newMd5 === $token){
//符合登陆规则
$this->user_id = $user_id;
}
}
}
/**
* 检查Post数据
* Function checkPostData
* author mselect
* DateTime 2019-12-01
*/
private function _checkPostData(){
$post = $this->request->post();
$version = (string)$this->request->header('Api-Version', '');
if( empty($post)){
$string = '';
}else {
ksort($post);
$data = [];
foreach($post as $key=>$value){
$data[] = $key ."=". $value;
}
$string = implode('&', $data);
}
$sign = md5($string . "&key=" . $this->versionToKey[$version]);
$signature = (string)$this->request->header('Api-Sign', '');
if( $signature == ''){
$this->outPut('SIGNEMPTY', '签名参数错误');
}
if( $signature !== $sign){
$this->outPut('SIGNERROR', '签名错误');
}
return true;
}
/**
* 统一输出
* Function outPut
* author mselect
* DateTime 2019-12-01
*/
public function outPut($code, $msg='', $data = []){
$result = array(
'code' => $this->codeMark[$code],
'msg' => $msg,
'data' => $data,
);
$type = $this->getResponseType();
$response = Response::create($result, $type)->header($this->header);
throw new HttpResponseException($response);
}
/**
* 获取当前的response 输出类型
* @access protected
* @return string
*/
protected function getResponseType()
{
return 'json';
}
}
```
* _checkPostData() 方法就是用来验证用户提交的数据和token 是否能通过验证的
加密方法: md5("post入参按照字母顺序递增拼接成字符串" . "不同的版本号对应的key")
#### 前端保存有我们的版本号和该版本号对应的key; 每次前端访问接口时需要将版本号传给我们,我们根据传递的版本号得到key, 知道使用哪个key进行加密。