為什麼要保護資料庫主鍵?
資料庫主鍵一般是有序自增主鍵,極易被爬蟲抓取資料,作為應用開發者,這是不應該的,你辛辛苦苦收集的資料轉眼之間被其他人給抓取了,是不是很大的損失?
Hashids的介紹
generate short unique ids from integers
理解為數字編碼庫即可,幾乎支援市面上所有語言。
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 HashidsHashids(`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 HashidsHashids(`salt`,16);
return $hashids->encode($id);
}
public static function decode($hash)
{
$hashids = new HashidsHashids(`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 yiiwebJsonParser
{
/**
* @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` => appcomponentsRequest::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` => appcomponentswebJsonParser::class
]
],
處理響應資料
1.新建JsonResponseFormatter繼承Yii的JsonResponseFormatter,程式碼如下:
class JsonResponseFormatter extends yiiwebJsonResponseFormatter
{
/**
* @inheritDoc
*/
public function format($response)
{
if ($response->data !== null) {
HashidsHelper::encodeRecursive($response->data);
}
parent::format($response);
}
}
2.配置web.php的components,替換response元件
`response` => [
`class` => appcomponentswebResponse::class,
`format` => Response::FORMAT_JSON,
`formatters` => [
`json` => [
`class` => appcomponentswebJsonResponseFormatter::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可以使用中介軟體來處理。