你連對外介面簽名都不知道?有時間還是要學習學習

老馬嘯西風發表於2021-07-14

背景

週三,18:00。

小明扭了扭微微發酸的脖子,揉了揉盯著螢幕有些乾澀的眼睛。

終於忙完了,臨近下班,整個人心也變得放鬆起來。

“對接方需要我們提供新的服務,下週二上線,需求我發你了,很簡單的。”

產品經理髮過來一條訊息,打破了這份美好。

computer-1185626_1280.jpg

“我可去他的吧,每次需求都是快下班就來了。”小明不免心裡嘀咕了起來,不過手上可沒停。

“好的,我先看下需求。”

回覆完後,點開了需求文件,確實很簡單。

為外部對接方提供一個新增商戶的介面。

保持和內部控臺新增商戶一致。
複製程式碼

確實不太難,小明想了想,內部控臺新增商戶雖然不是自己做的,但是介面應該可以直接複用,程式碼應該也能複用。

不過自己以前沒做過外部對接,不知道有沒有其他的坑。

下週二上線,那麼下週一就需要讓提測,讓測試介入進來。

小明點開了日曆,今天週三,自己介面文件編寫,詳細設計,編碼和自測的時間只剩兩天。

本來應該是充足的,不過平時還會有各種工作瑣事需要處理,會降低整體的工作效率。

還是先問同事要下以前的程式碼和文件吧。

看著時間已經超過了下班時間,小明嘆了一口氣。

介面文件

文件編寫

週四,9:30。

小明來到公司就開始著手處理介面文件的編寫,有以前的文件基礎,寫起來還是很快的。

不過對外的介面還是有些不同,小明按照自己的理解加上了 traceId,requestTime 等欄位,便於問題的排查和定位。

於是基本的介面就寫好了:

請求引數:

序號引數描述是否必填說明
1traceId唯一標識用於唯一定位一筆請求,32 位字串
2requestTime請求引數請求時間,yyyyMMddHHmmssSSS 17 位時間戳
3username使用者名稱最長 64 位字串
4password密碼最長 128 位字串, md5 加密
5address地址最長 128 位字串

響應引數:

序號引數描述是否必填說明
1respCode響應編碼000000 表示成功,其他見響應編碼列舉
2respDesc響應描述請求時間,yyyyMMddHHmmssSSS 17 位時間戳
3userId使用者標識32 位字串,成功建立後使用者的唯一標識

小明詳細的寫下了整個介面的請求方式,注意事項,以及對應的各種列舉值等。

並且把基本的詳細設計文件也整理了一下。

1762 個字,小明看了看總字數,苦澀的笑了笑。

這份文件,顯然沒有需求文件那麼簡潔。

一抬頭,已經 11:30 了,好傢伙,時間過得真快。

於是預訂了下午 14:00 的會議室,準備和產品經理,測試,專案經理過一下文件。

文件評審

會議室 14:00。

小明按時來到會議室,提前插好投影儀,等著大家的到來。

“我昨天提的需求簡單吧。”,未見其人先問其聲,產品經理剛到門口就笑著走了進來。

“是的,還好。”

接著,專案經理和測試也一起走了進來。

“快點過需求文件吧”,專案經理說道。

小明清了清嗓子,講了下整體的專案背景。並且把自己的詳細設計和介面文件過了一遍。

過的時候,產品經理低頭在做其他的事情,這些細節並不需要關心。

測試聽的比較認真,不停的提出自己的疑問,後面需要自己進行驗證。

“還有其他問題嗎?”,小明自己一個人不停說了半個多小時,覺得有些枯燥。

“我沒什麼問題了”,測試說,“我最關心的就是什麼時候可以提測?”

“下週一吧”,小明頓了頓,“我估計要會後才能開始編碼。”

“那還好”,測試回道,並表示自己沒有其他疑問。

“你這個文件寫的挺詳細的”,專案經理略帶讚許的目光看了下小明,“不過有一個問題,你這個介面連簽名都沒有。”

“簽名,什麼簽名?”,小明有點懵。

“你連對外介面簽名都不會知道?有時間還是要學習學習。”,專案經理顯然有些失望。

“好了,不說這些了。”,產品經理這時加入了談話,“這麼簡單的需求下週二上沒問題吧?”

“應該沒問題,只要按時提測就行”,測試看向了小明。

“應該沒問題”,小明腦子裡還在想介面簽名的事情,“我回去看下介面簽名,調整下介面。”

介面簽名

簽名作用

小明去查了查,發現對外的介面,安全性肯定是需要考慮的。

為了保證資料的安全性,防止資訊被篡改,簽名是比較常見的一種方式。

簽名

簽名實現

實現的方式有很多種,比較常用的方式:

(1)將所有引數,除去 checkValue 本身,按引數名字母升序排序。

(2)排序後的引數,按照引數和值的方式拼接為字串

(3)對拼接完的字串,使用雙方約定好的 key 進行 md5 加密,得到 checkValue 值

(4)將對應的值設定到 checkValue 屬性上

當然,在簽名的實現上可能會有差異,但是雙方保持一致即可。

簽名校驗

知道如何進行加簽,校驗也是類似的。

重複上面的 (1)(2)(3)步,得到對應的 checkValue。

然後和對方傳遞的值進行對比,如果一致,說明簽名驗證通過。

介面的調整

在瞭解了簽名的必要性之後,小明在介面文件中新增了 checkValue 這個屬性值。

並且和測試進行了私下的溝通,到這裡,文件才算初步結束。

看著時間已經超過了下班時間,小明嘆了一口氣。

程式碼實現

v1 版本

週五,10:00。

小明來到公司就開始進行編碼,其他的東西處理的差不多之後,就剩下一個簽名的實現問題。

一開始也沒多想,直接實現如下:

/**
 * 手動構建驗簽結果
 * @return 結果
 * @since 0.0.2
 */
public String buildCheckValue() {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(name);
    stringBuilder.append(password);
    // 其他一堆屬性
    return Md5Util.md5(stringBuilder.toString());
}
複製程式碼

當然,作為一個拿來主義者,小明意識到一個問題。

其他專案中肯定有類似的工具類,自己不應該重複造輪子。

v2 版本

從其他應用拷貝了一份工具類過來,大概實現如下:

import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.lang.reflect.ClassUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectFieldUtil;
import com.github.houbb.heaven.util.secrect.Md5Util;

import java.lang.reflect.Field;
import java.util.*;

/**
 * @author binbin.hou
 * @since 1.0.0
 */
public class CheckValueUtils {

    private CheckValueUtils(){}

    public static String buildCheckValue(Object object) {
        Class<?> clazz = object.getClass();

        // 獲取所有欄位的 fieldMap
        Map<String, Field> fieldMap = ClassUtil.getAllFieldMap(clazz);

        // 移除 checkValue 名稱的
        fieldMap.remove("checkValue");

        // 對欄位按名稱排序
        Set<String> fieldNameSet = fieldMap.keySet();
        List<String> fieldNameList = new ArrayList<>(fieldNameSet);
        Collections.sort(fieldNameList);

        // 反射獲取所有字串的值
        StringBuilder stringBuilder = new StringBuilder();
        for(String fieldName : fieldNameList) {
            Object value = ReflectFieldUtil.getValue(fieldName, object);
            // 反射獲取值
            String valueStr = StringUtil.objectToString(value, "");

            // 拼接
            stringBuilder.append(fieldName).append("=").append(valueStr);
        }


        //md5 加簽
        return Md5Util.md5(stringBuilder.toString());
    }
}
複製程式碼

總的來說還是很好用的,而且自己的時間也不多,就直接拿來使用就好。

全部搞定之後,就是自測工作,經過了一次踩坑之後,小明算是把整個介面自測通過了。

“這樣,提測時間也來得及了。”

看著時間已經超過了下班時間,小明嘆了一口氣。

paper-3261159_960_720.jpg

更優雅的加簽實現

一般來說,故事到這裡就結束了。

不過小明的一個想法,讓這個故事繼續走了下去。

工具方法的不足

原有的方法基本可以滿足大部分的需求,不過想要做調整就會變得比較麻煩。

比如,想某些非常大的欄位不參加加簽,加簽欄位的名字不叫 checkValue,而是改成 sign,調整一下欄位排序的方式等等。

這些都會導致原有的工具方法不可用,需要重新複製,修改。

那能不能實現一個更加靈活的加簽工具呢?

答案是肯定的,小明週末花了 2 天的時間,實現了一個加簽工具。

快速開始

maven 引入

<plugin>
    <groupId>com.github.houbb</groupId>
    <artifactId>checksum</artifactId>
    <version>0.0.6</version>
</plugin>
複製程式碼

pojo 物件

  • User.java
public class User {

    @CheckField
    private String name;

    private String password;

    @CheckField(required = false)
    private String address;

    @CheckValue
    private String checksum;

    //Getter & Setter
    //toString()
}
複製程式碼

其中涉及到兩個核心的註解:

@CheckField 表示參與加簽的欄位資訊,預設都是參與加簽的。指定 required=false 跳過加簽。

@CheckValue 表示加簽結果存放的欄位,該欄位型別需要為 String 型別。

後期將會新增一個 String 與不同型別的轉換實現,擴充應用場景。

獲取簽名

所有的工具類方法見 ChecksumHelper,且下面的幾個方法都支援指定祕鑰。

User user = User.buildUser();

final String checksum = ChecksumHelper.checkValue(user);
複製程式碼

該方法會把 User 物件中指定 @CheckField 的欄位全部進行處理,

通過指定排序後進行拼接,然後結合指定加密策略構建最後的驗簽結果。

填充簽名

User user = User.buildUser();

ChecksumHelper.fill(user);
複製程式碼

可以把對應的 checkValue 值預設填充到 @CheckValue 指定的欄位上。

驗證簽名

User user = User.buildUser();

boolean isValid = ChecksumHelper.isValid(user);
複製程式碼

會對當前的 user 物件進行加簽運算,並且將加簽的結果和 user 本身的簽名進行對比。

引導類

ChecksumBs 引導類

為了滿足更加靈活的場景,我們引入了基於 fluent-api 的 ChecksumBs 引導類。

上面的配置預設等價於:

final String checksum = ChecksumBs
        .newInstance()
        .target(user)
        .charset("UTF-8")
        .checkSum(new DefaultChecksum())
        .sort(Sorts.quick())
        .hash(Hashes.md5())
        .times(1)
        .salt(null)
        .checkFieldListCache(new CheckFieldListCache())
        .checkValueCache(new CheckValueCache())
        .checkValue();
複製程式碼

配置說明

上面所有的配置都是可以靈活替換的,所有的實現都支援使用者自定義。

屬性說明
target待加簽物件
charset編碼
checkSum具體加簽實現
sort欄位排序策略
hash字串加密 HASH 策略
salt加密對應的鹽值
times加密的次數
checkFieldListCache待加簽欄位的快取實現
checkValueCache簽名欄位的快取實現

效能

背景

每次我們說到反射第一反應是方便,第二反應就是效能。

有時候往往因為關心效能,而選擇手動一次次的複製,黏貼。

效能

詳情見 BenchmarkTest.java

5874675-ea8aa9dda28feffe.png

本次進行 100w 次測試驗證,耗時如下。

手動處理耗時:2505ms

註解處理耗時:2927ms

小結

簽名在對外介面的通訊中,可以保證資訊不被篡改。

希望這個工具可以幫到你,讓你按時下班。

我是老馬,期待與你的下次重逢。

相關文章