项目级Java接口签名与验证实现

原创
2020/06/05 16:24
阅读数 6.5K

在一些项目中,客户端在调用服务的接口时,通常需要设置签名验证,以保证对客户端的认证。在签名过程中一般每个公司都有自己的签名规则和签名算法,广泛使用的是使用非对称加密算法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;
    }

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

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部