1.准备一个基本参数的文本,这里为licence.info
{
"permittedFor": "00:0c:29:f0:03:4d", //颁发单位,这里为机器mac地址
"expiredAt":"", //过期时间
"pptLimit":0, //购买的PPT识别服务的使用次数(这里具体情况而定,业务不同)
"voiceLimit":0, //购买的同声传译识别服务的使用次数(这里具体情况而定,业务不同)
"issuedAt":"2020/2/18 12:00:00", //颁发时间
"issuedBy":"xxxx.com" //颁发单位
}
注释:这里为具体用户的购买情况的文本,我们记录下来,因为会根据参数来生成对应的密钥key。
2.准备一个空文件key.lic用来存放密钥
3.准备一个空文件licence.lic来存方加密的文本信息
如图:
4.在console下新建控制器ReduceController继承自\yii\console\Controller
public function actionEncrypt()
{
// $data = [
// 'permittedFor' => \Yii::$app->helper->getMAC(),//颁发给的机器mac,可以数组或字符串
// 'expiredAt' => date('Y-m-d H:i:s'),//过期时间
// 'pptLimit' => 30,//ppt任务数限制
// 'voiceLimit' => 20,//同声传译任务数限制
// 'issuedAt' => date('Y-m-d H:i:s'),//颁发时间
// 'issuedBy' => 'xxxx.cn',//颁发单位
// 'id'=>\Yii::$app->getSecurity()->generateRandomString()
// ];
$licenceDir = ArrayHelper::getValue(Yii::$app->params, 'licenceFiles');
if (empty($licenceDir)) {
$licenceDir = '.';
}
$keyFile = $licenceDir . DIRECTORY_SEPARATOR . 'key.lic';
$licenceFile = $licenceDir . DIRECTORY_SEPARATOR . 'licence.lic';
$registerFile = $licenceDir . DIRECTORY_SEPARATOR . 'licence.info';
$encryptString = file_get_contents($registerFile);
$encryptData = json_decode($encryptString, true);
$encryptData['id'] = Yii::$app->getSecurity()->generateRandomString();
$encryptString = json_encode($encryptData);
$key = md5($encryptString);
$encrypt = Yii::$app->getSecurity()->encryptByKey($encryptString, $key);
file_put_contents($licenceFile, $encrypt);
file_put_contents($keyFile, $key);
$this->stdout('done');
$this->stdout(PHP_EOL);
return ExitCode::OK;
}
建立好后我们需要启动命令,生成一个licence.lic和key.lic文件,这是加密后的密文和解密的密钥
下面开始解密授权:
<?php
namespace common\components;
use yii\base\BaseObject;
use yii\helpers\ArrayHelper;
class Licence extends BaseObject
{
private $licenceFile;
private $keyFile;
private $registerFile;
private $key;
private $licence;
private $macAddresses;
private $taskCount = [];
public function init()
{
parent::init(); // TODO: Change the autogenerated stub
$licenceDir = ArrayHelper::getValue(\Yii::$app->params, 'licenceFiles');
if (empty($licenceDir)) {
$licenceDir = '../..';
}
$this->keyFile = $licenceDir . DIRECTORY_SEPARATOR . 'key.lic';
$this->licenceFile = $licenceDir . DIRECTORY_SEPARATOR . 'licence.lic';
$this->registerFile = \Yii::getAlias('@common') . DIRECTORY_SEPARATOR . 'register.info';
if (!file_exists($this->keyFile)) {
throw new \Exception('授权文件缺失');
}
if (!file_exists($this->licenceFile)) {
throw new \Exception('授权文件缺失');
}
$key = file_get_contents($this->keyFile);
$licence = file_get_contents($this->licenceFile);
$encryptedString = \Yii::$app->getSecurity()->decryptByKey($licence, $key);
if (empty($encryptedString)) {
throw new \Exception('授权licence无效');
}
$licenceData = json_decode($encryptedString, true);
if (empty($licenceData)) {
throw new \Exception('授权licence无效');
}
if (empty($licenceData['id'])) {
throw new \Exception('授权licence格式无效');
}
if (empty($licenceData['issuedAt'])) {
throw new \Exception('授权licence格式无效');
}
if (!isset($licenceData['expiredAt'])) {
throw new \Exception('授权licence格式无效');
}
if (!isset($licenceData['permittedFor'])) {
throw new \Exception('授权licence格式无效');
}
if (!isset($licenceData['pptLimit'])) {
throw new \Exception('授权licence格式无效');
}
if (!isset($licenceData['voiceLimit'])) {
throw new \Exception('授权licence格式无效');
}
$issuedAt = strtotime(ArrayHelper::getValue($licenceData, 'issuedAt'));
$now = time();
if ($now < $issuedAt) {
throw new \Exception('licence生成时间错误,授权验证失败');
}
$expiredAt = ArrayHelper::getValue($licenceData, 'expiredAt');
if (!empty($expiredAt)) {
$expiredAtTime = strtotime($expiredAt);
if ($now >= $expiredAtTime) {
throw new \Exception('授权licence已过期,授权验证失败');
}
}
$permittedFor = ArrayHelper::getValue($licenceData, 'permittedFor');
if (!empty($permittedFor)) {
$macAddress = $this->getMAC();
if (empty($macAddress)) {
throw new \Exception('无法获取服务器mac地址,授权验证失败');
}
$this->macAddresses = $macAddress;
if (is_array($permittedFor)) {
$intersect = array_intersect($permittedFor, $macAddress);
if (empty($intersect)) {
throw new \Exception('服务器mac地址不匹配,授权验证失败');
}
} else {
if (!in_array($permittedFor, $macAddress)) {
throw new \Exception('服务器mac地址不匹配,授权验证失败');
}
}
}
$this->licence = $licenceData;
$this->key = $key;
}
private function getMAC()
{
$return_array = [];
switch (strtolower(PHP_OS)) {
case "linux":
$return_array = $this->forLinux();
break;
case "unix":
case "aix":
case "solaris":
break;
default:
$return_array = $this->forWindows();
break;
}
$mac_addr = [];
$temp_array = array();
foreach ($return_array as $value) {
if (preg_match("/[0-9a-f][0-9a-f][:-]" . "[0-9a-f][0-9a-f][:-]" . "[0-9a-f][0-9a-f][:-]" . "[0-9a-f][0-9a-f][:-]" . "[0-9a-f][0-9a-f][:-]" . "[0-9a-f][0-9a-f]/i", $value, $temp_array)) {
$mac_addr[] = $temp_array[0];
}
}
unset($temp_array);
return $mac_addr;
}
private function forWindows()
{
if (!function_exists('exec')) {
throw new \Exception('exec方法不可用,无法获取服务器mac地址');
}
@exec("ipconfig /all", $return_array);
if ($return_array)
return $return_array;
else {
$ipconfig = $_SERVER["WINDIR"] . "system32ipconfig.exe";
if (is_file($ipconfig))
@exec($ipconfig . " /all", $return_array);
else
@exec($_SERVER["WINDIR"] . "systemipconfig.exe /all", $return_array);
return $return_array;
}
}
private function forLinux()
{
if (!function_exists('exec')) {
throw new \Exception('exec方法不可用,无法获取服务器mac地址');
}
@exec('ifconfig -a', $output, $returnArray);
if ($returnArray === 0) {
return $output;
}
@exec("/usr/sbin/ifconfig -a", $output, $returnArray);
if ($returnArray === 0) {
return $output;
}
throw new \Exception('无法获取服务器mac地址,exec状态码:' . $returnArray);
}
/**
* @return mixed
* @throws \Exception
*/
public function getMacAddress()
{
if (empty($this->macAddresses)) {
$this->macAddresses = $this->getMAC();
if (empty($this->macAddresses)) {
throw new \Exception('获取服务器mac地址失败');
}
}
$macAddress = reset($this->macAddresses);;
return $macAddress;
}
}
我们重写了yii\base\BaseObject的init方法,所以会在项目初始化去验证init内容
如果key.lic和licence.lic能够匹配上,通过\Yii::$app->getSecurity()->decryptByKey($licence, $key);这一串代码,那么就实现了授权。为什么需要mac地址,因为这里布置在多个的docker容器里面,ppt和同声传译服务需要调度,这里是为后面的业务做唯一性。