先看經常使用的校驗請求合法性的一個方式php
function createToken($params) { $secretKey = 'secretKey'; ksort($params); $query = http_build_query($params); $token = md5($query . $secretKey); return $token; } function createQuery($params) { $params['token'] = createToken($params); $query = http_build_query($params); return $query; } function checkQuery($params) { $token = $params['token']; unset($params['token']); return $token == createToken($params); } $params = [ 'k1' => 'v1', 'k2' => 'v2', 'time' => time() ]; $query = createQuery($params); echo $query, PHP_EOL; parse_str($query, $result); var_dump(checkQuery($result));
輸出java
k1=v1&k2=v%202&time=1537806711&token=e088ac85569f9eb5266026bb8da989b2 bool(true)
client 每一個請求都攜帶一個 token ,token 是由請求參數和一個 secret key 一塊兒md5以後計算出來的, 而後 server 端使用一樣的算法計算token(通常還會校驗time,給 token 一個有效期,我這裏簡化了),而後對比 token ,保證請求的合法性。算法
乍一看,這個算法幾乎完美無缺,恩,我以前也是這麼認爲的,直到我用這個算法和一個 java 的服務交互時,才發現這個寫法有些問題。架構
我在和 java 的同事連調時,有個請求老是報 token 校驗錯誤,可是其餘請求卻沒這個問題,我倆百思不得其解,互相 review 對方代碼,也沒有發現問題,而後和 java 的同事加了不少 log,終於發現了端倪。app
我請求的參數裏,有一個參數的值帶有空格,而後我這邊 url encode 以後ui
echo urlencode(" "); // +
可是他那邊 url encode 以後是編碼
// 僞裝這裏有 java url encode 的代碼 %20
這樣,問題就很清晰了,不是算法問題,而是 url encode 的編碼標準的問題。加密
php的 http_build_query (urlencode也同樣)默認使用的 RFC 1738 (http://php.net/manual/zh/function.http-build-query.php)url
默認使用 PHP_QUERY_RFC1738,若是 enc_type 是 PHP_QUERY_RFC1738,則編碼將會以 RFC 1738 標準和 application/x-> www-form-urlencoded 媒體類型進行編碼,空格會被編碼成加號(+),若是 enc_type 是 PHP_QUERY_RFC3986,將根據 RFC 3986 編碼,空格會被百分號編碼(%20)。.net
而他那邊用的是 RFC 3986 。
那麼怎麼規避這個問題呢?
常見的無非就是這兩種方案,第一,兩邊使用一樣的編碼標準,第二,生成 token 的算法改一下,不要使用 url encode 以後的字符串加密(建議)。
更多架構、PHP、GO相關踩坑實踐技巧請關注個人公衆號:PHP架構師