欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

hashids保护数据库主键

程序员文章站 2022-06-02 16:14:02
...

数据库主键一般是借助数据库ID生成策略生成有序自增一个整型数值,极易被爬虫抓取数据,作为应用开发者,这是不应该的,你辛辛苦苦收集的数据转眼之间被其他人给抓取了,是不是很大的损失?

Hashids的介绍
官方地址:https://hashids.org/,其中包含如下简述:

generate short unique ids from integers

翻译成中文字面意思为:从整数生成简短的唯一id, 可以理解为数字编码库即可,几乎支持市面上所有语言。
它的原理就是从数字经过一个加盐(salted)算法产生一个哈希(hash)字符串。这样算法就是通过混淆使结果具有不可预测性,而唯一性依然由数字本身来达成,从而得到足够短,不可预测且唯一的 ID。

available in JavaScript, Ruby, Python, Java, Scala, PHP, Perl, Perl 6, Swift, Clojure, Objective-C, C, C++11, D, F#, Go, Erlang, Lua, Haskell, OCaml, Elixir, Rust, Smalltalk, ColdFusion, Groovy, Kotlin, Nim, VBA, Haxe, Crystal, Elm, ActionScript, CoffeeScript, Bash, R, TSQL, PostgreSQL and for
PHP使用

$hashids = new Hashids\Hashids('this is my salt');
$id = $hashids->encode(1, 2, 3);
$numbers = $hashids->decode($id);

注意
该库并不是一个加密库,所以不建议用来加密敏感数据,数据库主键ID并不是业务上的敏感数据,所以这个没关系。

陷阱:
- 尽管仅仅加密一个数字,返回的也是数组
- 不支持负数加密
- 如加密的不是数组,则返回空字符串

Yii2使用
由于该编解码是独立与业务之外的,所以需要处理的地方在下面:

接收请求数据的自动解码
响应数据的自动编码(本文只针对JSON响应处理,有需要的可以添加ResponseFormatter自行处理)
这两个步骤不应该出现在控制器中,控制器拿到的数据是解码好的,响应的数据是原始数据,然后我们在响应中处理。

代码
助手类(HashidsHelper)

class HashidsHelper {
         public static function encode($id)
         {
             $hashids = new \Hashids\Hashids('salt',16);
             return $hashids->encode($id);
         }

    public static function decode($hash)
    {
        $hashids = new \Hashids\Hashids('salt',16);
        $data= $hashids->decode($hash);
        return empty($data)?null:$data;
    }

    public static function decodeArray(array $hashes)
    {
        return array_map([HashidsHelper::class, 'decode'], $hashes);
    }

   /**
     * 递归编码
     * @param array $data
     */
    public static function encodeRecursive(array &$data)
    {
        foreach ($data as $key => &$value) {
            if (is_array($value)) {
                self::encodeRecursive($value);
                continue;
            }
            if (strpos($key, 'id') !== false && is_numeric($value)) {
                $data[$key] = static::encode($value);
            }
        }
    }

    /**
     * 递归解码
     * @param array $data
     */
    public static function decodeRecursive(array &$data)
    {
        foreach ($data as $key => &$value) {
            if (is_array($value)) {
                self::decodeRecursive($value);
                continue;
            }
            if (strpos($key, 'id') !== false) {
                if (is_string($value)) {
                    $id = static::decode($value);
                    $data[$key] = $id ?? $value;
                } elseif (is_array($value)) {
                    $data[$key] = static::decodeArray($value);
                }
            }
        }
    }
}

处理请求数据(POST,_PUT,$_GET)提交过来的数据
1.新建JsonParser继承Yii自带的JsonParser,代码如下

class JsonParser extends \yii\web\JsonParser
{
    /**
     * @inheritDoc
     */
    public function parse($rawBody, $contentType)
    {
        $data = parent::parse($rawBody, $contentType);
        if ($data !== null) {
            HashidsHelper::decodeRecursive($data);
        }
        return $data;
    }
}

2.新建Request集成Yii自带的Request,重写getQueryParams,代码如下:

 public function getQueryParams()
    {
        $data = parent::getQueryParams();
        if ($data !== null) {
            HashidsHelper::decodeRecursive($data);
        }
        return $data;
    }

3.配置web.php的components,更改为我们自定义的处理器

     'request' => [
            'class' => \app\components\Request::class,
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => '123456',
            'enableCsrfValidation' => false,
            'parsers' => [
                'application/json' => \app\components\web\JsonParser::class
            ]
        ],

处理响应数据
1.新建JsonResponseFormatter继承Yii的JsonResponseFormatter,代码如下:

class JsonResponseFormatter extends \yii\web\JsonResponseFormatter
{
    /**
     * @inheritDoc
     */
    public function format($response)
    {
        if ($response->data !== null) {
            HashidsHelper::encodeRecursive($response->data);
        }
        parent::format($response);
    }
}

2.配置web.php的components,替换response组件

    'response' => [
            'class' => \app\components\web\Response::class,
            'format' => Response::FORMAT_JSON,
            'formatters' => [
                'json' => [
                    'class' => \app\components\web\JsonResponseFormatter::class,
                    'prettyPrint' => YII_DEBUG
                ]
            ]
        ],

测试
1.SiteController添加方法

    public function actionA($corporation_id)
    {
        $data = Yii::$app->request->post();
        var_dump($data, $corporation_id);
    }

    public function actionB()
    {
        return [
            'app_id' => 1,
            'app' => [
                'app_id' => 2
            ]
        ];
    }

2.请求测试,这个加密过的hash读者可能解不开,因为我们用的salt不一样,替换为你自己的即可

POST /site/a?corporation_id=XaYeAV2q80pkB4KL

{
  "corporation_id": "XaYeAV2q80pkB4KL",
  "applet":{
    "id":"XaYeAV2q80pkB4KL",
    "appid":"xxxxxx"
  }
}

3.响应的内容如下:

array(2) {
  ["corporation_id"]=>
  int(1)
  ["applet"]=>
  array(2) {
    ["id"]=>
    int(1)
    ["appid"]=>
    string(6) "xxxxxx"
  }
}
int(1)

4.响应测试

GET /site/b

5.响应内容如下

{
    "app_id": "XaYeAV2q80pkB4KL",
    "app": {
        "app_id": "LOnMp3QR5lryDgRK"
    }
}

本文核心在于两个递归方法,其他语言类似,像nodejs可以使用中间件来处理。