使用Hashids來保護你的數據庫主鍵

爲何要保護數據庫主鍵?

數據庫主鍵通常是有序自增主鍵,極易被爬蟲抓取數據,做爲應用開發者,這是不該該的,你辛辛苦苦收集的數據轉眼之間被其餘人給抓取了,是否是很大的損失?php

Hashids的介紹

generate short unique ids from integers

理解爲數字編碼庫便可,幾乎支持市面上全部語言。node

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並非業務上的敏感數據,因此這個不要緊。web

Yii2的使用

因爲該編解碼是獨立與業務以外的,因此須要處理的地方在下面:數據庫

  1. 接收請求數據的自動解碼
  2. 響應數據的自動編碼(本文只針對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,代碼以下json

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,代碼以下:segmentfault

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

3.配置web.php的components,更改成咱們自定義的處理器cookie

'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,代碼以下:app

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組件yii

'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"
    }
}

寫在最後

不知道這個算不算AOP編程?我的以爲算,在業務邏輯以外處理,業務層對外部輸入和自身輸出是透明的(理解爲業務層本身不知道加解密)。

本文核心在於兩個遞歸方法,其餘語言相似,像nodejs可使用中間件來處理。

原文地址:https://segmentfault.com/a/1190000015704969

相關文章
相關標籤/搜索