項目級Java接口簽名與驗證實現

在一些項目中,客戶端在調用服務的接口時,通常需要設置簽名驗證,以保證對客戶端的認證。在簽名過程中一般每個公司都有自己的簽名規則和簽名算法,廣泛使用的是使用非對稱加密算法RSA爲核心,在客戶端使用私鑰對參數進行加密生成一個密鑰,傳給服務端,然後在服務端再對這個這個密鑰使用公鑰進行解密,解密出來的字符串參數與客戶端傳過來的參數進行對比,如果一樣,驗證成功。

總結起來就是:

  • 客戶端
        
        -   首先獲取所有的參數,然後對他們進行排序,生成一個字符串
            
        -   對這個字符串MD5加密,然後轉爲大寫
            
        -   然後使用私鑰對MD5再次加密,生成最終的簽名sign
            
        -   把這個簽名sign傳給服務端
  • 服務端
        
        -   獲取所有的參數
            
        -   把參數中籤名sign參數去除,然後排序,生成一個字符串
            
        -   對這個字符串MD5加密,然後轉爲大寫
            
        -   使用公鑰對sign字符串進行解密獲取一個String,然後和第三步中獲取的字符串相對,如果相等,則驗證成功

下面我們就通過以上規則實現客戶端的簽名和服務端的驗證。

客戶端簽名

比如我們這是我們的url:

http://localhost:8080/task/test?name=張三&age=8&timestamp=1591261543133


可以看到url中有三個參數,分別是姓名、年齡和時間戳。首先我們要對這些參數進行排序:

  public static String getSortedContent(Map<String, String> data) {
    StringBuffer content = new StringBuffer();
    List<String> keys = new ArrayList<String>(data.keySet());
    Collections.sort(keys);
    int index = 0;
    for (String key : keys) {
      String value = data.get(key);
      content.append((index == 0 ? "" : "&")).append(key).append("=").append(value);
      index++;
    }
    return content.toString();
  }

然後對排序好的字符串進行加密,然後轉大寫:

String summary = DigestUtils.md5Hex(data).toLowerCase();


最後對summary進行私鑰加密:

    public static String EncryptByRSAPriKey(String content,String priKey) throws Exception {
        try {
            PrivateKey privateKey = SecurityUtil.getRSAPriKey(priKey);
            Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            cipher.update(content.getBytes(SecurityUtil.RSA_CHARSET));
            return SecurityUtil.encodeBase64(cipher.doFinal());
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception();
        }
    }

最終得到我們想要的sign,傳給服務端。

服務端驗證

首先獲取到所有的參數後,去除sign,對剩下的參數排序,MD5加密,轉大寫,

  /**
   *
   * @param params 去除sign的所有參數
   * @param sign
   * @param pubKey 公鑰
   * @return
   * @throws ApiError
   */
  public static boolean verifySign( Map<String, String> params,String sign,String pubKey) throws ApiError {
    if (StringUtil.isEmpty(sign)) {
      return false;
    }
    String signType = params.get(Constants.FN_SIGN_TYPE);;//暫不支持非RSA的簽名
    if (StringUtil.isEmpty(signType) || !signType.equals(Constants.SIGN_TYPE_RSA)) {
      return false;
    }
    //參與簽名的數據
    String data = ApiUtils.getSortedContent(params);
    ApiLogger.getLogger().debug("sign data:" + data);
    String summary = DigestUtils.md5Hex(data).toLowerCase();
    ApiLogger.getLogger().debug("sign summary:" + summary);
​
    String summaryDecode = null;
    try {
      summaryDecode = SecurityUtil.DecryptByRSAPubKey(sign, pubKey);
    } catch (Exception e) {
      throw new ApiError("do_digest_error", e);
    }
    return summary.equals(summaryDecode);
  }

其中SecurityUtil.DecryptByRSAPubKey(sign, pubKey)的方法爲:

    /**
     *
     * 描述:將字符串通過RSA算法公鑰解密
     * @author wangbing
     * @since
     * @param content 需要解密的內容
     * @param pubKey 公鑰
     * @return 解密後字符串
     * @throws Exception
     */
    public static String DecryptByRSAPubKey(String content, String pubKey){
        try {
            PublicKey publicKey = SecurityUtil.getRSAPubKey(priKey);
            Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            cipher.update(SecurityUtil.decodeBase64(content));
            return new String(cipher.doFinal(), SecurityUtil.RSA_CHARSET);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

到此,服務端的核心代碼完成。


微信搜索黑白色調關注我吧:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章