<?php
namespace app\merch\controller;
class Index extends Base
{
protected $in_controller=[//需要验证token的类
'index'
];
protected $in_action=[//需要验证token的方法
'menus',
'index'
];
protected $now_action_option=[//需要使用缓存的方法以及名称
'index' => 3600,
'menus' => 3600,
];
/**
* 获取当前登录账号系统左侧菜单
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function menus()
{
$MerchRole = new MerchRole();
return $this->successData($MerchRole->getRoleMenu($this->user->merch_role_id));
}
/**
* 首页数据
* @return \think\response\Json
*/
public function index(){
$data = [];
return $this->successData($data);
}
}
<?php
namespace app\merch\controller;
use rsa\RSA;
use think\App;
use app\BaseController;
use think\exception\HttpResponseException;
/**
* admin控制器继承类
* @package app\controller
*/
class Base extends BaseController
{
// 需要验证token的控制器和单个方法
protected $in_controller=[];
protected $in_action=[];
//登录用户信息
protected $user;
protected $root='';
protected $controller='';
protected $action='';
//当前方法是否需要缓存
private $now_action_is_cache = false;
/*
* 需要缓存的方法配置
* 示例:$now_action_option['index'=>3600,...]
*/
protected $now_action_option=[];
/**
* 构造函数
* Base constructor.
*/
public function __construct (App $app) {
parent::__construct($app);
$this->controller = strtolower(Request()->controller());
$this->action = strtolower(Request()->action());
$this->root =strtolower(request()->root());
// $this->checkSign();
$this->verification();
$this->getPublicCache();
}
/**
* 验证签名
* @return bool
*/
public function checkSign(){
$sign = request()->header('sign');
$sign_id = request()->header('signId');
if(empty($sign) || empty($sign_id)){
throw new HttpResponseException($this->failData('非法请求',502));
}
$rsa = new RSA();
$rsa->init(config('system.merch_sign_public'),config('system.merch_sign_private'));
/*
* $sign=$rsa->encrypt(config('system.merch_sign').$sign_id); //前端$sign加密方法
* $sign_id=$rsa->encrypt($sign_id); //前端$sign_id加密方法
*/
$res = $rsa->decrypt($sign_id);
if(!$res){
throw new HttpResponseException($this->failData('验签id错误',502));
}
$sign_id = $res;
if(cache('sign_id_'.$sign_id)===$sign_id){
throw new HttpResponseException($this->failData('密钥已使用',502));
}
$res = $rsa->decrypt($sign);
if(!$res){
throw new HttpResponseException($this->failData('密钥错误',502));
}else{
cache('sign_id_'.$sign_id,$sign_id,60);
}
return true;
}
/**
* 账号权限检测
* @return bool
*/
public function detection(): bool
{
$menus = (new \app\merch\model\MerchRole)->where('merch_role_id',$this->user->role_id)->value('menus');
$urls = (new \app\merch\model\AdminMenu)->where('platform','merch')->whereIn('id',$menus)->column('url');
if(!in_array($this->controller . '/' . $this->action, $urls, true)){
throw new HttpResponseException($this->failData('您没有权限访问'.$this->controller . '/' . $this->action,502));
}
if(in_array($this->action,array_keys($this->now_action_option))){//方法需要缓存
$this->now_action_is_cache = true;
}
return true;
}
/**
* 获取指定方法缓存
* @return bool
*/
public function getPublicCache(){
if($this->now_action_is_cache){//方法需要缓存
$cacheName = $this->root.'/'.$this->controller . '/' . $this->action.'/'.$this->user->merch_id;
if(cache($cacheName)){
throw new HttpResponseException($this->successData(cache($cacheName)));
}
}
return true;
}
/**
* 设置指定方法缓存
* @param $data
* @return bool
*/
public function setPublicCache($data){
if($this->now_action_is_cache){//方法需要缓存
$cacheName = $this->root.'/'.$this->controller . '/' . $this->action.'/'.$this->user->merch_id;
if(!cache($cacheName)){
cache($cacheName,$data,$this->now_action_option[$this->action]);
}
}
return true;
}
/**
* 验证需要登录的方法及token
* @return bool
*/
public function verification(): bool
{
if(in_array($this->controller,$this->in_controller, true) && in_array($this->action,$this->in_action, true)){
$token = request()->header('token');
if(empty($token)){
throw new HttpResponseException($this->failData('非法请求',502));
}
$check = $this->checkToken($token);
if($check!==true){
throw new HttpResponseException($this->failData($check,502));
}
//账号权限检测
$this->detection();
}
return true;
}
/**
* 接口成功返回
* @param $data
* @param int $code
* @return \think\response\Json
*/
public function successData ($data=true,$code=200): \think\response\Json
{
$this->setPublicCache($data);
return json([
'data' => $data,
'code' => $code
]);
}
/**
* 接口请求失败
* @param int $code
* @param string $msg
* @return \think\response\Json
*/
public function failData($msg='接口报错提示',$code=501): \think\response\Json
{
return json([
'code' => $code,
'msg' => $msg
]);
}
/**
* 生成token
* @param $data
* @return string
*/
public function getToken($data): string
{
//用户名、此时的时间戳,并将过期时间拼接在一起
$admin = $data; //获取前台传来的用户账号
$time = time();
$end_time = time()+86400;
$info = $admin. '.' .$time.'.'.$end_time;//设置token过期时间为一天
//根据以上信息信息生成签名(密钥为 siasqr)
$signature = hash_hmac('md5',$info,config('system.merch_token_key'));
//最后将这两部分拼接起来,得到最终的Token字符串
return $signature . '.' . $info;
}
/**
* 验证token
* @param $token
* @return bool|string
*/
public function checkToken($token)
{
/**** api传来的token ****/
if(!isset($token) || empty($token))
{
return '非法请求';
}
//对比token
$explode = explode('.',$token);//以.分割token为数组
if(!empty($explode[0]) && !empty($explode[1]) && !empty($explode[2]) && !empty($explode[3]) )
{
$info = $explode[1].'.'.$explode[2].'.'.$explode[3];//信息部分
$true_signature = hash_hmac('md5',$info,config('system.merch_token_key'));//正确的签名
if(time() > intval($explode[3]))
{
return 'Token已过期,请重新登录';
}
if ($true_signature === $explode[0])
{
$this->user = (new \app\merch\model\MerchAdmin())->where('merch_id',intval($explode[1]))->find();
return true;
}
return 'Token不合法';
}
return 'Token不合法';
}
}
RSA.php
<?php
namespace rsa;
//$private_key_file = "./cert/private_key.pem";
//$public_key_file = "./cert/public_key.pem";
//$RSA = new RSA();
//$key = $RSA->generate();
//file_put_contents($private_key_file, $key['private_key'], LOCK_EX);
//file_put_contents($public_key_file, $key['public_key'], LOCK_EX);
//exit;
/**
* RSA算法类
* 签名及密文编码:base64字符串/十六进制字符串/二进制字符串流
* 填充方式: PKCS1Padding(加解密)/NOPadding(解密)
*
* Notice:Only accepts a single block. Block size is equal to the RSA key size!
* 如密钥长度为1024 bit,则加密时数据需小于128字节,加上PKCS1Padding本身的11字节信息,所以明文需小于117字节
*/
class RSA
{
private $pubKey = null;
private $priKey = null;
/**
* 构造函数
*/
public function __construct()
{
// 需要开启openssl扩展
if (!extension_loaded("openssl")) {
$this->_error("需要开启openssl扩展");
}
}
/**
* 读取公钥和私钥
* @param string $public_key_file 公钥文件(验签和加密时传入)
* @param string $private_key_file 私钥文件(签名和解密时传入)
*/
public function init($public_key_file = '', $private_key_file = '')
{
if ($public_key_file) {
$this->_getPublicKey($public_key_file);
}
if ($private_key_file) {
$this->_getPrivateKey($private_key_file);
}
}
/**
* 自定义错误处理
*/
private function _error($msg)
{
die('RSA Error:' . $msg); //TODO
}
/**
* 检测填充类型
* 加密只支持PKCS1_PADDING
* 解密支持PKCS1_PADDING和NO_PADDING
*
* @param int 填充模式
* @param string 加密en/解密de
* @return bool
*/
private function _checkPadding($padding, $type)
{
if ($type == 'en') {
switch ($padding) {
case OPENSSL_PKCS1_PADDING:
$ret = true;
break;
default:
$ret = false;
}
} else {
switch ($padding) {
case OPENSSL_PKCS1_PADDING:
case OPENSSL_NO_PADDING:
$ret = true;
break;
default:
$ret = false;
}
}
return $ret;
}
private function _encode($data, $code)
{
switch (strtolower($code)) {
case 'base64':
$data = base64_encode('' . $data);
break;
case 'hex':
$data = bin2hex($data);
break;
case 'bin':
default:
}
return $data;
}
private function _decode($data, $code)
{
switch (strtolower($code)) {
case 'base64':
$data = base64_decode($data);
break;
case 'hex':
$data = $this->_hex2bin($data);
break;
case 'bin':
default:
}
return $data;
}
private function _getPublicKey($file)
{
$key_content = $this->_readFile($file);
if ($key_content) {
$this->pubKey = openssl_get_publickey($key_content);
}
}
private function _getPrivateKey($file)
{
$key_content = $this->_readFile($file);
if ($key_content) {
$this->priKey = openssl_get_privatekey($key_content);
}
}
private function _readFile($file)
{
$ret = false;
if (!file_exists($file)) {
$this->_error("The file {$file} is not exists");
} else {
$ret = file_get_contents($file);
}
return $ret;
}
private function _hex2bin($hex = false)
{
$ret = $hex !== false && preg_match('/^[0-9a-fA-F]+$/i', $hex) ? pack("H*", $hex) : false;
return $ret;
}
/**
* 生成Rsa公钥和私钥
* @param int $private_key_bits 建议:[512, 1024, 2048, 4096]
* @return array
*/
public function generate(int $private_key_bits = 1024)
{
$rsa = [
"private_key" => "",
"public_key" => ""
];
$config = [
"digest_alg" => "sha512",
"private_key_bits" => $private_key_bits, #此处必须为int类型
"private_key_type" => OPENSSL_KEYTYPE_RSA,
];
//创建公钥和私钥
$res = openssl_pkey_new($config);
//提取私钥
openssl_pkey_export($res, $rsa['private_key']);
//生成公钥
$rsa['public_key'] = openssl_pkey_get_details($res)["key"];
/*Array
(
[bits] => 512
[key] =>
[rsa] =>
[type] => 0
)*/
return $rsa;
}
/**
* 生成签名
*
* @param string 签名材料
* @param string 签名编码(base64/hex/bin)
* @return bool|string 签名值
*/
public function sign($data, $code = 'base64')
{
$ret = false;
if (openssl_sign($data, $ret, $this->priKey)) {
$ret = $this->_encode($ret, $code);
}
return $ret;
}
/**
* 验证签名
*
* @param string 签名材料
* @param string 签名值
* @param string 签名编码(base64/hex/bin)
* @return bool
*/
public function verify($data, $sign, $code = 'base64')
{
$ret = false;
$sign = $this->_decode($sign, $code);
if ($sign !== false) {
switch (openssl_verify($data, $sign, $this->pubKey)) {
case 1:
$ret = true;
break;
case 0:
case -1:
default:
$ret = false;
}
}
return $ret;
}
/**
* 加密
*
* @param string 明文
* @param string 密文编码(base64/hex/bin)
* @param int 填充方式(貌似php有bug,所以目前仅支持OPENSSL_PKCS1_PADDING)
* @return string 密文
*/
public function encrypt($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING)
{
$ret = false;
if (!$this->_checkPadding($padding, 'en')) $this->_error('padding error');
if (openssl_public_encrypt($data, $result, $this->pubKey, $padding)) {
$ret = $this->_encode($result, $code);
}
return $ret;
}
/**
* 解密
*
* @param string 密文
* @param string 密文编码(base64/hex/bin)
* @param int 填充方式(OPENSSL_PKCS1_PADDING / OPENSSL_NO_PADDING)
* @param bool 是否翻转明文(When passing Microsoft CryptoAPI-generated RSA cyphertext, revert the bytes in the block)
* @return string 明文
*/
public function decrypt($data, $code = 'base64', $padding = OPENSSL_PKCS1_PADDING, $rev = false)
{
$ret = false;
$data = $this->_decode($data, $code);
if (!$this->_checkPadding($padding, 'de')) $this->_error('padding error');
if ($data !== false) {
if (openssl_private_decrypt($data, $result, $this->priKey, $padding)) {
$ret = $rev ? rtrim(strrev($result), "\0") : '' . $result;
}
}
return $ret;
}
}
php接口一次一签 token验证 权限验证 接口缓存类
于 2022-11-16 14:47:56 首次发布