项目级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;
    }

到此,服务端的核心代码完成。


微信搜索黑白色调关注我吧:

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