当前位置: 首页 > news >正文

Java应用服务系统安全性,签名和验签浅析

1 前言

随着互联网的普及,分布式服务部署越来越流行,服务之间通信的安全性也是越来越值得关注。这里,笔者把应用与服务之间通信时,进行的的安全性相关,加签验签,进行了一个简单的记录。

2 安全性痛点

  • 网关服务接口,暴漏在公网,被非法调用?
  • 增加了token安全验证,被抓包等其他手段拦截了token,token验证无效?
  • 参数被非法获取,非法调用系统应用的接口?
  • 接口参数被非法获取后,同一个接口被重复多次非法调用?

3 技术选型

3.1 对称加密与非对称加密对比

  • 对称加密

    优点:加密速度快
    缺点:密钥管理分配困难,安全性较低

  • 非对称加密

    优点:安全性较高
    缺点:加密速度慢

对称加密技术加密和解密使用的都是同一个密钥,因此密钥的管理非常困难,在分发密钥的过程中,如果一方密钥被截获,那后面的通信就是不安全的

而非对称加密技术就很好的解决了这一问题,非对称加密技术使用公钥加密,私钥加密。通信前把公钥发布出去,私钥只有自己保留,即便你的公钥被攻击者拿到,没有私钥,就无法进行解密。

那有了非对称加密技术,对称加密是不是就被淘汰了?当然不是,因为非对称加密技术加解密比较慢,不适合对大量数据的加解密。

3.2 MD5是对称加密还是非对称加密?

  • 对称算法有哪些?

对称密码算法又叫传统密码算法,也就是加密密钥能够从解密密钥中推算出来,反过来也成立。在大多数对称算法中,加密解密密钥是相同的。常见的对称算法有:DES、IDEA、AES、SM1和SM4。

  • 非对称算法有哪些?

非对称密钥也叫公开密钥加密,它是用两个数学相关的密钥对信息进行编码。在此系统中,其中一个密钥叫公开密钥,可随意发给期望同密钥持有者进行安全通信的人。公开密钥用于对信息加密。第二个密钥是私有密钥,属于密钥持有者,此人要仔细保存私有密钥。密钥持有者用私有密钥对收到的信息进行解密。常见的非对称算法有:RSA、ECC、SM2。

这个问题有人吐槽过,面试官竟然问MD5是对称加密还是非对称加密?其实,MD5不是加密算法,md5实际上既不是对称算法,也不是非对称加密算法。它是消息摘要(安全散列)算法。

  • 对称加密和非对称加密有哪些优缺点?

    对称加密优点: 速度快,对称性加密通常在消息发送方需要加密大量数据时使用,具有算法公开、计算量小、加密速度快、加密效率高的特点。对称加密算法的优点在于加解密的高速度和使用长密钥时的解密性。

    对称加密的缺点: 密钥的管理和分发非常困难,不够安全。在数据传送前,发送方和接收方必须商定好密钥,并且双方都要保存好密钥,如果一方的密钥被泄露,那么加密信息也就不安全了,安全性得不到保证

    非对称加密优点:安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人

    非对称加密缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密

  • MD5优缺点

    MD5的优点:计算速度快,加密速度快,不需要密钥;可以检查文件的完整性,一旦文件被更改,MD5值会改变;防止被篡改,传输中一旦被篡改,计算出的MD5值也会改变;防止看到明文,公司存放密码存放的是MD5值。

    MD5的缺点:作为散列算法,经过证实,仍然会存在两种不同数据会发生碰撞;MD5的安全性。将用户的密码直接MD5后存储在数据库中是不安全的。很多人使用的密码是常见的组合,威胁者将这些密码的常见组合进行单向哈希,得到一个摘要组合,然后与数据库中的摘要进行比对即可获得对应的密码。

综上所述,md5是消息摘要算法,既不是对称算法也不是非对称算法。大部分情况下使用对称加密具有不错的安全性,如果需要分布式进行密钥分发,那么就考虑使用非对称加密;如果不需要可逆计算,则考虑散列算法(md5)。

参考资料:https://www.eolink.com/news/post/28567.html

具体的加密算法可以参考另一篇博文:浅析对称加密与非对称加密算法
经过分析,最终采用AES加密算法对参数进行加密(有需要的话),加签验签采用RSA非对称加密算法

4 请求头参数

服务端提供签名私钥 private_key、应用id app_id、商户号 merchant_no,另外,所有请求的接口,都需要添加请求头Headers,8个参数如下:

名称类型说明是否必填
method_nameString请求的方法名称
request_methodString请求方式(get or post)
merchant_noString商户号,一个商户可以有多个应用
msg_idString通讯唯一编号
app_idString应用系统唯一编号
sign_timestampString请求时应用端签名时的时间戳(毫秒单位),接入系统的时间误差不能超过3分钟,示例:1898098042342
api_gw_public_keyString网关公钥key,通过该公钥,才可以请求网关服务
request_signString签名信息,用来验签,通过签名机制,防止应用的请求参数被非法篡改,应用系统必须保证相关私钥不被泄露
  • 请求是head头参数示例:

在这里插入图片描述

注:postman自定义参数,在Pre-request Script 输入: pm.environment.set('sign_timestamp',new Date().getTime());
在这里插入图片描述

5 签名以及验签机制

  • 原理
    请求接口的参数中添加sign签名+时间戳(毫秒单位)
    具体实现原理:应用端生成RSA私钥加密签名串,和当前时间戳跟随请求的参数一起发送到后台,后台获取签名进行RSA公钥解密,然后获取系统当前时间戳和前端发送过来的时间戳做比较,如果两者相差超过180s,则认定为非法操作。这种方式既能保证防止请求重放,又能有效节省服务器资源

    注:应用端调用服务端接口时的公钥,必须为PKCS8格式的RSA 2048密钥,该密钥还必须经过Base64转换,服务端提供的API公钥也遵从同样标准。

  • 本文章涉及到的安全机制梳理如下:

    * 原有的token校验,无法做到安全性,因为token一般过期时间都很长,可以token多次访问
    * 原来可以直接简单的http调用,现在必须经过一系列加签过程才可以访问服务资源
    * 加签时timestamp以日为单位,跨日时可能会验签失败,这里可以根据实际业务情况调整
    * api网关秘钥校验,只有有效的key才可以访问网关服务
    * 应用端使用后端分配的私钥(定期修改增加安全性)进行加签,后端进行公钥验签,非对称加解密,增加了更高级别的安全性
    * 服务端分配商户号和应用编号,保证唯一性
    * 应用端加签和服务端验签,`时间戳不允许超过3min(可动态调整)`,保证即使参数被盗,3min过期后仍然无法访问
    * 即使参数被非法获取,也仅仅是那个接口极短时间内有风险,大大提高的系统的防攻击特性
    

5.1 签名

  • 系统采取的签名算法
    SHA256WithRSA签名算法,使用RSA非对称加密算法,可以采用应用端 + 服务端双重签名的方式,来保证系统的安全性。

    Unlike symmetric encryption algorithms, asymmetric encryption algorithms require two keys: public key and private key. The public key and private key are a pair. If the data is encrypted with the public key, only the corresponding private key can be decrypted; If the data is encrypted with a private key, it can only be decrypted with the corresponding public key. Because encryption and decryption use two different keys, this algorithm is called asymmetric encryption algorithm

  • 签名私钥private_key、应用idapp_id、商户号merchant_no由服务端提供

5.2 加签名思路

  • 参数数据是在应用端,使用私钥加密的,这里在服务端只能用相应公钥解密,即可验签是否有效

  • 首先将method_name、request_method、merchant_no、msg_id、app_id、sign_timestamp拼接成一个字符串:

    getUserInfo?method_name=getUserInfo&request_method=get&merchant_no=6666XF20230306001&msg_id=666&app_id=10000000006666001&charset=UTF-8&format=json&sign_type=RSA2&timestamp=20230311
    
  • 把拼接后的字符串,按照ASCII排序

    getUserInfo?app_id=10000000006666001&charset=UTF-8&format=json&merchant_no=6666XF20230306001&method_name=getUserInfo&msg_id=666&request_method=get&sign_type=RSA2&timestamp=20230311
    

    注:ASCII码的值从⼩到⼤为数字、⼤写英⽂字母、⼩写英⽂字母。48~57为0到9⼗个阿拉伯数字,65~90为26个⼤写英⽂字母,97~122号为26个⼩写英⽂字母。

  • 对私钥进行Base64加密

  • 对以上字符串进行SHA256WithRSA签名算法,运算之后得到:

    VN2O+0Kgf9SSQf36BhUo6EqvMcKPlRHEm+6TWBqqQxCXmW5a88NYnVafItEBvWBajY8nR+8w9zhpNrCZZ2dHyg0umPZSDi6cDQL/zVX15fwvZlMRnccNHnMJ4QfDuybEN4NGWUDsOWXiEvlAzTA3/QYrRWivXpyrKS9xlA/CqTOvZIdwlcyJEokGHP55aMrJCfJuVdmKI6oqkPMpNvwHe/fQHi0krhkOJw7aa97WJ0tptqdpmnANz/lvCEvFBmvUIMVFtjpEutPTRSAL1miDZeKHdfPlUIMN0G/qoTdSyFF4yh0Nvk0rSvGd0/tVSxDUBE5sLhbjM5K+gLpo2iOQGQ==
    
  • 最后,将VsMiLe6字符串,添加到sign开头处,再次增加安全性,最后得到:

    VsMiLe6VN2O+0Kgf9SSQf36BhUo6EqvMcKPlRHEm+6TWBqqQxCXmW5a88NYnVafItEBvWBajY8nR+8w9zhpNrCZZ2dHyg0umPZSDi6cDQL/zVX15fwvZlMRnccNHnMJ4QfDuybEN4NGWUDsOWXiEvlAzTA3/QYrRWivXpyrKS9xlA/CqTOvZIdwlcyJEokGHP55aMrJCfJuVdmKI6oqkPMpNvwHe/fQHi0krhkOJw7aa97WJ0tptqdpmnANz/lvCEvFBmvUIMVFtjpEutPTRSAL1miDZeKHdfPlUIMN0G/qoTdSyFF4yh0Nvk0rSvGd0/tVSxDUBE5sLhbjM5K+gLpo2iOQGQ==
    

5.3 验签思路

使用原有的请求入参,以及传递来的sign进行公钥解密,如果解密成功,就说明验签通过,否则验签失败。

注:如果使用公钥加密,则需要私钥进行解密,是双向的。

6 功能核心代码实现

6.1 SDK核心代码

6.1.1 SmileConstants常量类

/*** icbc.com.cn Inc.* Copyright (c) 2004-2016 All Rights Reserved.*/
package cn.smilehappiness.security.constant;/*** <p>* SmileConstants* <p/>** @author* @Date 2023/3/6 19:30*/
public class SmileConstants {public static final String SIGN_TYPE = "sign_type";public static final String SIGN_TYPE_RSA = "RSA";public static final String SIGN_TYPE_RSA2 = "RSA2";public static final String SIGN_TYPE_SM2 = "SM2";public static final String SIGN_TYPE_CA = "CA";public static final String SIGN_TYPE_SM = "SM";public static final String SIGN_TYPE_EM = "EM";public static final String SIGN_TYPE_EM_SM = "EM-SM";public static final String SIGN_SHA1RSA_ALGORITHMS = "SHA1WithRSA";public static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";public static final String ENCRYPT_TYPE_AES = "AES";public static final String METHOD_NAME = "method_name";public static final String REQUEST_METHOD = "request_method";public static final String MERCHANT_NO = "merchant_no";public static final String APP_ID = "app_id";public static final String SIGN_TIMESTAMP = "sign_timestamp";public static final String API_GW_PUBLIC_KEY = "api_gw_public_key";public static final String REQUEST_SIGN = "request_sign";public static final String FORMAT = "format";public static final String TIMESTAMP = "timestamp";public static final String SIGN = "sign";public static final String APP_AUTH_TOKEN = "app_auth_token";public static final String CHARSET = "charset";public static final String NOTIFY_URL = "notify_url";public static final String RETURN_URL = "return_url";public static final String ENCRYPT_TYPE = "encrypt_type";public static final String BIZ_CONTENT_KEY = "biz_content";/*** Default time format**/public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";public static final String YYYY_MM_DD = "yyyyMMdd";/*** Date Default time zone**/public static final String DATE_TIMEZONE = "GMT+8";/*** UTF-8 character set**/public static final String CHARSET_UTF8 = "UTF-8";/*** GBK character set**/public static final String CHARSET_GBK = "GBK";/*** JSON  Format*/public static final String FORMAT_JSON = "json";/*** XML  Format*/public static final String FORMAT_XML = "xml";public static final String CA = "ca";public static final String PASSWORD = "password";public static final String RESPONSE_BIZ_CONTENT = "response_biz_content";/*** Message unique number**/public static final String MSG_ID = "msg_id";/*** sdk The version number in the headerkey*/public static final String VERSION_HEADER_NAME = "APIGW-VERSION";/*** sdk Region number, for overseas institutions*/public static final String ZONE_NO = "Zone-No";/*** Request type*/public static final String REQUEST_Type = "Request-Type";/*** For em-type signatures, send a request to CICC Cryptography*/public static final String EM_CFCA = "CFCA";/*** For em-type signature, send a request to the NC client*/public static final String EM_NC = "NC";/*** Refined information*/public static final String REFINE_INFO = "Apirefined-Info";}

6.1.2 SmileClient类

package cn.smilehappiness.security.api.client;import cn.smilehappiness.security.constant.SmileConstants;
import cn.smilehappiness.security.dto.BaseRequest;
import cn.smilehappiness.security.utils.AesCryptUtil;
import cn.smilehappiness.security.utils.SecurityStringUtil;
import cn.smilehappiness.security.utils.SmileHashMap;
import cn.smilehappiness.security.utils.SmileSignatureUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;/*** <p>* smile client base deal* <p/>** @author* @Date 2023/3/7 11:08*/
public class SmileClient {private static final Logger logger = LoggerFactory.getLogger(SmileClient.class);protected String apiPublicKey;protected String merchantNo;protected String appId;protected String signType = SmileConstants.SIGN_TYPE_RSA;protected String privateKey;protected String publicKey;protected String charset = SmileConstants.CHARSET_UTF8;protected String format = SmileConstants.FORMAT_JSON;protected String encryptType;protected String encryptKey;public SmileClient() {}public SmileClient(String apiPublicKeyParam, String appId, String merchantNo, String signType, String privateKey, String publicKey, String charset, String format, String encryptType, String encryptKey) {this.apiPublicKey = apiPublicKeyParam;this.appId = appId;this.merchantNo = merchantNo;this.signType = signType;this.privateKey = privateKey;this.publicKey = publicKey;this.charset = charset;this.format = format;this.encryptType = encryptType;this.encryptKey = encryptKey;}public SmileClient(String apiPublicKeyParam, String appId, String merchantNo, String privateKey) {this(apiPublicKeyParam, appId, merchantNo, SmileConstants.SIGN_TYPE_RSA, privateKey, null, SmileConstants.CHARSET_UTF8, SmileConstants.FORMAT_JSON, null, null);}/*** <p>* rsa,rsa2,four params* <p/>** @param apiPublicKeyParam* @param appId* @param signType* @param privateKey* @return* @Date 2023/3/7 11:08*/public SmileClient(String apiPublicKeyParam, String appId, String merchantNo, String signType, String privateKey) {this(apiPublicKeyParam, appId, merchantNo, signType, privateKey, null, SmileConstants.CHARSET_UTF8, SmileConstants.FORMAT_JSON, null, null);}/*** <p>* check sign,five params* <p/>** @param apiPublicKeyParam* @param appId* @param signType* @param privateKey* @return* @Date 2023/3/8 15:40*/public SmileClient(String apiPublicKeyParam, String appId, String merchantNo, String signType, String privateKey, String publicKey) {this(apiPublicKeyParam, appId, merchantNo, signType, privateKey, publicKey, SmileConstants.CHARSET_UTF8, SmileConstants.FORMAT_JSON, null, null);}/*** <p>* six params -AES encrypt* <p/>** @param apiPublicKeyParam* @param appId* @param signType* @param privateKey* @param encryptType* @param encryptKey* @return* @Date 2023/3/8 10:19*/public SmileClient(String apiPublicKeyParam, String appId, String merchantNo, String signType, String privateKey, String encryptType, String encryptKey) {this(apiPublicKeyParam, appId, merchantNo, signType, privateKey, null, SmileConstants.CHARSET_UTF8, SmileConstants.FORMAT_JSON, encryptType, encryptKey);}public SmileClient(String apiPublicKeyParam, String appId, String merchantNo, String signType, String privateKey, String publicKey, String encryptType, String encryptKey) {this(apiPublicKeyParam, appId, merchantNo, signType, privateKey, publicKey, SmileConstants.CHARSET_UTF8, SmileConstants.FORMAT_JSON, encryptType, encryptKey);}private boolean checkApiPublicKeyLegal(String apiPublicKeyParam) {if (StringUtils.isBlank(apiPublicKeyParam) || !apiPublicKey.equals(apiPublicKeyParam)) {throw new RuntimeException("apiPublicKey param [" + apiPublicKeyParam + "] is unLegal");}return true;}/*** <p>* prepare params* <p/>** @param request* @return cn.smilehappiness.security.utils.IcbcHashMap* @Date 2023/3/7 13:56*/public SmileHashMap prepareParams(BaseRequest<?> request) {SmileHashMap params = new SmileHashMap();String strToSign = this.prepareParamStr(request, params);logger.info("addSign strToSign: {}", strToSign);if (signType.equals(SmileConstants.SIGN_TYPE_RSA) || signType.equals(SmileConstants.SIGN_TYPE_RSA2)) {String signedStr = SmileSignatureUtil.sign(strToSign, signType, privateKey, charset);params.put(SmileConstants.SIGN, signedStr);} else {// Other signature typeslogger.error("signType {} is not supported", signType);throw new RuntimeException("signType " + signType + " is not supported");}return params;}/*** <p>* prepare param str* <p/>** @param request* @param params* @return java.lang.String* @Date 2023/3/8 16:34*/private String prepareParamStr(BaseRequest<?> request, SmileHashMap params) {Map<String, String> extraParams = request.getExtraParameters();if (extraParams != null) {params.putAll(extraParams);}//appId Is the public variable of the classparams.put(SmileConstants.METHOD_NAME, request.getMethodName());params.put(SmileConstants.REQUEST_METHOD, StringUtils.lowerCase(request.getRequestMethod()));params.put(SmileConstants.MERCHANT_NO, merchantNo);params.put(SmileConstants.APP_ID, appId);params.put(SmileConstants.MSG_ID, request.getMsgId());params.put(SmileConstants.SIGN_TYPE, signType);params.put(SmileConstants.CHARSET, charset);params.put(SmileConstants.FORMAT, format);try {// Get the timestamp, where you can achieve a higher level of control over the time dimensionlong timestamp = System.currentTimeMillis();DateFormat df = new SimpleDateFormat(SmileConstants.YYYY_MM_DD);df.setTimeZone(TimeZone.getTimeZone(SmileConstants.DATE_TIMEZONE));//time error is within 3 minutes, gateway control,this only 1d validityparams.put(SmileConstants.TIMESTAMP, df.format(new Date(timestamp)));} catch (Exception exception) {exception.printStackTrace();}// Core content parameters need to be encryptedString bizContentStr = buildBizContentStr(request);if (request.isEncryptFlag()) {if (SecurityStringUtil.areEmpty(encryptType, encryptKey)) {logger.error("request need be encrypted, encrypt type and encrypt key can not be null");throw new RuntimeException("request need be encrypted, encrypt type and encrypt key can not be null");}if (bizContentStr != null) {params.put(SmileConstants.ENCRYPT_TYPE, encryptType);params.put(SmileConstants.BIZ_CONTENT_KEY, AesCryptUtil.encryptContent(bizContentStr, encryptType, encryptKey, charset));}} else {// Do not encrypt, fill in the requestParam fieldparams.put(SmileConstants.BIZ_CONTENT_KEY, bizContentStr);}// Sort by rulereturn this.buildOrderedSignStr(request.getMethodName(), params);}/*** <p>* build biz content str* <p/>** @param request* @return java.lang.String* @Date 2023/3/7 13:43*/protected String buildBizContentStr(BaseRequest<?> request) {if (ObjectUtils.isEmpty(request.getBizContent())) {return null;}if (this.format.equals(SmileConstants.FORMAT_JSON)) {return JSON.toJSONString(request.getBizContent());} else {logger.error("only support json format, current format is not supported, format: {}", this.format);throw new RuntimeException("only support json format, current format is not supported, format: " + this.format);}}/*** <p>* build ordered sign str* <p/>** @param methodName* @param params* @return java.lang.String* @Date 2023/3/7 11:25*/private String buildOrderedSignStr(String methodName, Map<String, String> params) {Map<String, String> sortedMap = new TreeMap();sortedMap.putAll(params);Set<Map.Entry<String, String>> entries = sortedMap.entrySet();boolean hasParam = false;StringBuilder sb = new StringBuilder(methodName);sb.append("?");Iterator var6 = entries.iterator();while (var6.hasNext()) {Map.Entry<String, String> entry = (Map.Entry) var6.next();String name = entry.getKey();String value = entry.getValue();if (SecurityStringUtil.areNotEmpty(name, value)) {if (hasParam) {sb.append("&");} else {hasParam = true;}sb.append(name).append("=").append(value);}}return sb.toString();}/*** <p>* signCheck* <p/>** @param request* @param signStr* @return T* @Date 2023/3/8 13:29*/public <T> T signCheck(BaseRequest<T> request, String signStr) {if (StringUtils.isBlank(signStr)) {throw new RuntimeException("sign check fail, signStr param is null");}//check api public key legalthis.checkApiPublicKeyLegal(request.getApiGwPublicKey());SmileHashMap params = new SmileHashMap();String strToSign = this.prepareParamStr(request, params);logger.info("signCheck strToSign: {}", strToSign);int indexOfSignStart = SmileSignatureUtil.SIGN_PREFIX.length();String sign = signStr.substring(indexOfSignStart);boolean passed = SmileSignatureUtil.verifySign(strToSign, this.signType, this.publicKey, this.charset, sign);if (!passed) {logger.error("sign verify not passed, please check");throw new RuntimeException("sign verify not passed, please check");}if (!request.isEncryptFlag()) {return null;}// The signature is base64 encoded, and no comma will appearString startKey = StringUtils.join(SmileConstants.BIZ_CONTENT_KEY, "=");int indexOfStart = strToSign.lastIndexOf(startKey);int indexOfEnd = strToSign.lastIndexOf("&charset");String bizContentStr = strToSign.substring(indexOfStart, indexOfEnd).replace(startKey, "");String bizContentDecryptResult = AesCryptUtil.decryptContent(bizContentStr, this.encryptType, this.encryptKey, this.charset);return (T) JSON.parse(bizContentDecryptResult);}}

6.1.3 签名工具类

package cn.smilehappiness.security.utils;import cn.smilehappiness.security.constant.SmileConstants;
import cn.smilehappiness.security.utils.crypt.RSAUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.UnsupportedEncodingException;/*** <p>* smile signature util* <p/>** @author* @Date 2023/3/7 14:04*/
public class SmileSignatureUtil {private static final Logger logger = LoggerFactory.getLogger(SmileSignatureUtil.class);public static final String SIGN_PREFIX = "VsMiLe6";private SmileSignatureUtil() {}public static String sign(String content, String signType, String privateKey, String charset) {return sign(content, signType, privateKey, charset, null);}/*** <p>* sign* <p/>** @param content* @param signType* @param privateKey* @param charset* @param password* @return java.lang.String* @Date 2023/3/7 14:15*/public static String sign(String content, String signType, String privateKey, String charset, String password) {try {byte[] contentBytes = content.getBytes(charset);if (signType.equals(SmileConstants.SIGN_TYPE_RSA)) {String signStr = RSAUtil.sign(contentBytes, Base64.decodeBase64(privateKey), SmileConstants.SIGN_SHA1RSA_ALGORITHMS);return StringUtils.join(SIGN_PREFIX, signStr);} else if (signType.equals(SmileConstants.SIGN_TYPE_RSA2)) {String signStr = RSAUtil.sign(contentBytes, Base64.decodeBase64(privateKey), SmileConstants.SIGN_SHA256RSA_ALGORITHMS);return StringUtils.join(SIGN_PREFIX, signStr);}logger.error("not support signType");throw new RuntimeException("not support signType.");} catch (UnsupportedEncodingException e) {logger.error("get content charset exception, content: {} , charset:{} ,error info: {}", content, charset, e);throw new RuntimeException("get content charset exception. content: " + content + " charset: " + charset + e);} catch (Exception e) {logger.error("sign exception.", e);throw new RuntimeException("sign exception." + e);}}/*** <p>* verify sign* <p/>** @param content* @param signType* @param publicKey* @param charset* @param sign* @return boolean* @Date 2023/3/7 14:15*/public static boolean verifySign(String content, String signType, String publicKey, String charset, String sign) {try {byte[] contentBytes = content.getBytes(charset);if (signType.equals(SmileConstants.SIGN_TYPE_RSA)) {return RSAUtil.verify(contentBytes, Base64.decodeBase64(publicKey), sign, SmileConstants.SIGN_SHA1RSA_ALGORITHMS);} else if (signType.equals(SmileConstants.SIGN_TYPE_RSA2)) {return RSAUtil.verify(contentBytes, Base64.decodeBase64(publicKey), sign, SmileConstants.SIGN_SHA256RSA_ALGORITHMS);}logger.error("not support signType.");throw new RuntimeException("not support signType.");} catch (UnsupportedEncodingException e) {logger.error("get content charset exception. content: " + content + " charset: " + charset, e);throw new RuntimeException("get content charset exception. content: " + content + " charset: " + charset + e);} catch (Exception e) {logger.error("sign verify exception.", e);throw new RuntimeException("sign verify exception." + e);}}}

6.2 加签代码示例

	@Testpublic void testAddSign() {BaseRequest<Map<String, Object>> request = new BaseRequest<>();//sign paramrequest.setMethodName("getUserInfo");request.setRequestMethod("get");request.setMerchantNo("6666XF20230306001");request.setAppId("10000000006666001");request.setMsgId("666");request.setSignTimestamp(System.currentTimeMillis());request.setApiGwPublicKey(smileSecurityConfig.getApiGwPublicKey());SmileClient client = new SmileClient(smileSecurityConfig.getApiGwPublicKey(), smileSecurityConfig.getAppId(), smileSecurityConfig.getMerchantNo(), SmileConstants.SIGN_TYPE_RSA2, smileSecurityConfig.getPrivateKey());SmileHashMap smileHashMap = client.prepareParams(request);System.out.println(JSON.toJSONString(smileHashMap));}

6.3 验签代码示例

package com.itn.idn.gateway.security;import cn.smilehappiness.security.api.client.SmileClient;
import cn.smilehappiness.security.config.SmileSecurityConfig;
import cn.smilehappiness.security.constant.SmileConstants;
import cn.smilehappiness.security.dto.BaseRequest;
import com.alibaba.fastjson.JSON;
import com.itn.idn.gateway.ErrorService;
import com.itn.idn.gateway.enums.GatewayExceptionEnum;
import com.itn.idn.gateway.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.Arrays;
import java.util.Map;/*** <p>* api security interceptor* <p/>** @author* @Date 2023/3/10 17:40*/
@Component
@RefreshScope
@ConfigurationProperties("security.config")
public class ApiSecurityInterceptor implements GlobalFilter, Ordered {private final Logger logger = LoggerFactory.getLogger(ApiSecurityInterceptor.class);private final long ALLOW_INTERVAL_TIMESTAMP = 180000L;@Autowiredprivate ErrorService errorService;@Autowiredprivate SmileSecurityConfig smileSecurityConfig;private String[] skipAuthUrls;public String[] getSkipAuthUrls() {return skipAuthUrls;}public void setSkipAuthUrls(String[] skipAuthUrls) {this.skipAuthUrls = skipAuthUrls;}/*** sign verify filter** @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String url = exchange.getRequest().getURI().getPath();// Skip paths that do not require validationif (StringUtils.matches(url, Arrays.asList(skipAuthUrls))) {return chain.filter(exchange);}String methodName = exchange.getRequest().getHeaders().getFirst(SmileConstants.METHOD_NAME);String requestMethod = exchange.getRequest().getHeaders().getFirst(SmileConstants.REQUEST_METHOD);String merchantNo = exchange.getRequest().getHeaders().getFirst(SmileConstants.MERCHANT_NO);String appId = exchange.getRequest().getHeaders().getFirst(SmileConstants.APP_ID);String msgId = exchange.getRequest().getHeaders().getFirst(SmileConstants.MSG_ID);String signTimestamp = exchange.getRequest().getHeaders().getFirst(SmileConstants.SIGN_TIMESTAMP);String apiGwPublicKey = exchange.getRequest().getHeaders().getFirst(SmileConstants.API_GW_PUBLIC_KEY);String requestSign = exchange.getRequest().getHeaders().getFirst(SmileConstants.REQUEST_SIGN);//head need paramsBaseRequest<Map<String, Object>> request = new BaseRequest<>();//sign paramrequest.setMethodName(methodName);request.setRequestMethod(requestMethod);request.setMerchantNo(merchantNo);request.setAppId(appId);request.setMsgId(msgId);ServerHttpResponse resp = exchange.getResponse();if (StringUtils.isBlank(signTimestamp)) {logger.error("signTimestamp params is null");return errorService.authError(resp, GatewayExceptionEnum.SIGN_CHECK_FAIL);}request.setSignTimestamp(Long.parseLong(signTimestamp));long intervalTimestamp = System.currentTimeMillis() - request.getSignTimestamp();logger.info("request url:{},sign and request interval timestamp:{}", url, intervalTimestamp);if (Math.abs(intervalTimestamp) > ALLOW_INTERVAL_TIMESTAMP) {logger.error("intervalTimestamp {} is greater than 3min", intervalTimestamp);return errorService.authError(resp, GatewayExceptionEnum.SIGN_CHECK_FAIL);}request.setApiGwPublicKey(apiGwPublicKey);logger.info("filter sign check params:{}", JSON.toJSONString(request));SmileClient client = new SmileClient(request.getApiGwPublicKey(), request.getAppId(), request.getMerchantNo(), SmileConstants.SIGN_TYPE_RSA2, null, smileSecurityConfig.getPublicKey());try {//signCheck strToSign: getUserInfo?app_id=10000000006666001&charset=UTF-8&format=json&merchant_no=6666XF20230306001&method_name=getUserInfo&msg_id=666&request_method=get&sign_type=RSA2&timestamp=20230311client.signCheck(request, requestSign);} catch (RuntimeException exception) {return errorService.authError(resp, GatewayExceptionEnum.SIGN_CHECK_FAIL);}return chain.filter(exchange);}@Overridepublic int getOrder() {return -100;}}

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

相关文章:

Java应用服务系统安全性,签名和验签浅析

1 前言 随着互联网的普及&#xff0c;分布式服务部署越来越流行&#xff0c;服务之间通信的安全性也是越来越值得关注。这里&#xff0c;笔者把应用与服务之间通信时&#xff0c;进行的的安全性相关&#xff0c;加签与验签&#xff0c;进行了一个简单的记录。 2 安全性痛点 …...

spring中bean的实例化

构造方法实现实例化 无参构造器实例化 我们之前用的就一直是无参构造器实现实例化&#xff0c;虽然没有在类中写构造器&#xff0c;但是每个类都会有一个默认的无参构造器 有参构造器实例化 相比于无参构造器&#xff0c;我们只需要传入参数就可以了 我们可以通过construc…...

磨皮插件portraiture2023最新中文版

Portraiture滤镜是一款 Photoshop&#xff0c;Lightroom 和 Aperture 插件&#xff0c;DobeLighttroom 的 Portraiture 消除了选择性掩蔽和逐像素处理的繁琐的手工劳动&#xff0c;以帮助您在肖像修整方面取得卓越的效果。它是一个强大的&#xff0c;但用户友好的插件照明.这是…...

记录每日LeetCode 2269.找到一个数组的K美丽值 Java实现

题目描述&#xff1a; 一个整数 num 的 k 美丽值定义为 num 中符合以下条件的 子字符串 数目&#xff1a; 子字符串长度为 k 。 子字符串能整除 num 。 给你整数 num 和 k &#xff0c;请你返回 num 的 k 美丽值。 注意&#xff1a; 允许有 前缀 0 。 0 不能整除任何…...

代码管理--svnadmin工具介绍

1、简介 SVNAdmin2 是一款通过图形界面管理服务端SVN的web程序。正常情况下配置SVN仓库的人员权限需要登录到服务器手动修改 authz 和 passwd 两个文件&#xff0c;当仓库结构和人员权限上了规模后&#xff0c;手动管理就变的非常容易出错&#xff0c;本系统能够识别人员和权限…...

Git的基本使用以及上传到GitHub

GIT的基本使用一、安装并配置GIT二、Git的基本操作三、使用GIT上传至GitHub四、Git分支一、安装并配置GIT 1.安装GIT连接 GIT安装包链接 2.打开GIT 鼠标右键点击Git Bash Here 安装完 Git 之后&#xff0c;第一件事就是设置自己的用户名和邮件地址。因为通过 Git 对项目进行…...

国科大论文latex模板中可能的注意事项

背景 国科大2022年9月发布了毕业论文的LaTeX模板&#xff0c;它是在ucasthesis上修改而来的&#xff0c;但近日使用国科大发布版本时发现有几点不同以及需要注意的地方。本人只会简单使用latex&#xff0c;但并不熟悉latex样式编辑&#xff0c;因此以下介绍与方法仅供参考。仅…...

ABAP 怎样将XML和JSON格式转换为HTML格式显示

ABAP 怎样将XML和JSON格式转换为HTML格式显示 一、将JSON格式转换为HTML格式 BAP接口程序开发中时常会用到JSON格式来传输数据&#xff0c;在监控传输的JSON串内容时&#xff0c;把JSON转换为HTML格式来显示会很便利。下面提供一个简单例子来实现JSON转化为HTML并显示的功能。…...

基础课DP

DP 背包问题01背包问题完全背包问题多重背包问题多重背包问题II分组背包问题线性DP数字三角形最长上升子序列最长上升子序列II最长公共子序列编组距离区间DP石子合并计数类DP整数划分数位统计DP计数问题状态压缩DP蒙德里安的梦想最短Ha路径树形DP没有上司的舞会...

基于支持向量机SVM的风电场NWP数据预测,SVM的详细原理

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 SVM应用实例,基于SVM的风电场NWP预测 结果分析 展望 支持向量机SVM的详细原理 SVM的定义 支持向量机(support vector machines, SVM)是一种二分类模型,它的基本模型是定…...

webRtc概念

webRtc概念 以下的文档整理来自此链接 文档整理了一系列实现web通用接口的ECMAScript APIs &#xff0c;这些接口是为了支持浏览器或者一些其他实现了实时交换协议的设备进行媒体信息和程序数据交换。 1、实现点对点通信的规范&#xff1a; NAT穿透实现与远端节点链接比如&a…...

数据结构与算法基础(王卓)(16):KMP算法详解(代码实现)

实现代码的过程中 具体细节、问题&#xff1a; &#xff08;1&#xff09;&#xff1a;关于写Get_next函数的标题&#xff1a; 现象&#xff1a; PPT上写的是&#xff1a; void get_next(SString T, int &next[]) 然而并不能运行&#xff0c;而当我们去掉了引用符号&…...

九龙证券|盘前直接腰斩,银行巨头紧急“拔网线”!美股银行股又崩了?

见证历史了&#xff0c;又有一家银行巨子倒下&#xff1f; 美股银行股团体暴降 上一交易日暴降超60%的硅谷银行持续面对腥风血雨。盘前&#xff0c;硅谷银行跌幅超50%&#xff0c;随后&#xff0c;公司宣布盘前暂停交易&#xff0c;等待刊发消息。 而最新消息显现&#xff0c…...

接口优化常用思路

空间换时间 计算机程序中最大的矛盾是空间和时间的矛盾&#xff0c;那么&#xff0c;从这个角度出发逆向思维来考虑程序的效率问题&#xff0c;我们就有了解决问题的第1招–以空间换时间 合理使用缓存就是一个很好的例子&#xff0c;针对一些频繁使用且不频繁变更的数据&#…...

【SpringCloud】SpringCloud面试题整理

文章目录1、什么是Spring Cloud&#xff1f;2、Spring Cloud和Dubbo的区别3、REST和RPC的区别4、SpringCloud如何实现服务的注册和发现5、什么是服务熔断和服务降级&#xff1f;6、项目中zuul常用的功能7、服务网关的作用8、ribbon和feign区别9、ribbon的负载均衡策略10、简述什…...

一些数据库知识点总结

DB2数据库&#xff1a;从数据库表中第I条记录开始检索J条记录SELECT * FROM (SELECT A.*, ROW_NUMBER() OVER() AS NFROM (SELECT * FROM table_name) AS A)WHERE N > I AND N < J;Oracle数据库&#xff1a;从数据库表中第M条记录开始检索N条记录SELECT * FROM (SELECT R…...

Python unittest 模块

一、Unittest 的几个基本概念 TestCase &#xff1a;要写的具体的测试用例TestSuite&#xff1a; 多个测试用例集合&#xff08;或测试套件/测试集&#xff09;TestLoader&#xff1a;用来加载 TestCase 到 TestSuite中的&#xff08;更通俗一点&#xff0c;就是用来把符合我们…...

Spring - Spring IoC 容器相关面试题总结

文章目录01. Spring IoC 和依赖注入是什么&#xff1f;02. Spring IoC 的优点和缺点分别是什么&#xff1f;03. Spring IoC 有什么作用和功能&#xff1f;04. Spring 依赖注入的方式&#xff1f;05. Spring 构造器注入和 setter 方法注入的区别&#xff1f;06. Spring 依赖注入…...

顺序表来喏!!!

前言&#xff1a;还记得前面的文章&#xff1a;《通讯录的实现》吗&#xff1f;通讯录的完成就借助了顺序表这种数据结构&#xff01;&#xff01;&#xff01;那么今天我们就来介绍我们的顺序表介绍顺序表前&#xff0c;我们来了解一下线性表的概念线性表&#xff1a;线性表&a…...

【H2实践】之 SpringBoot 与 H2 数据交互

一、目标 本文是【H2实践】之认识 H2&#xff0c;【H2实践】之 SpringBoot 整合的后续。前文分别介绍了 H2 及其简单使用&#xff0c;并完成了 H2 与 SpringBoot 的整合。本文将紧接 【H2实践】之 SpringBoot 整合 探索实用 SpringBoot 结合 JPA 通过 web 接口操作 H2 数据库的…...

LeetCode 424. Longest Repeating Character Replacement

LeetCode 424. Longest Repeating Character Replacement https://leetcode.com/problems/longest-repeating-character-replacement/ 题目描述 You are given a string s and an integer k. You can choose any character of the string and change it to any other upperc…...

建立自己的博客(记录-不推荐)

环境安装&#xff1a; w10系统安装 第一步&#xff1a;安装git Git 官网: https://git-scm.com/ 第二步&#xff1a;安装Node.js Node.js官网&#xff1a;https://nodejs.org/zh-cn/ 使用cmd检测&#xff1a; node -v 第三步&#xff1a;安装Hexo Hexo官网&#xff1a;htt…...

hashmap存储方式 hash碰撞及其解决方式

1.Map的存储特点 在Map这个结构中&#xff0c;数据是以键值对&#xff08;key-value&#xff09;的形式进行存储的&#xff0c;每一个存储进map的数据都是一一对应的。 创建一个Map结构可以使用new HashMap()以及new TreeMap()两种方式&#xff0c;两者之间的区别是&#xff1a…...

Amazon GuardDuty 的新增功能 – Amazon EBS 卷的恶意软件检测

亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术&#xff0c;观点&#xff0c;和项目&#xff0c;并将中国优秀开发者或技术推荐给全球云社区。如果你还没有关注/收藏…...

YOLOv7 pytorch

yolov7主干部分结构图&#xff1a;yolov7主干 yolov7数据集处理代码&#xff1a;yolov7数据集处理代码 yolov7训练参数解释&#xff1a;yolov7训练参数【与本文代码有区别】 yolov7训练代码详解&#xff1a;yolov7训练代码详解 目录 训练自己的训练集 训练自己的训练集 此…...

JDK自带JVM分析工具

一、JDK自带工具盘点&#xff1a; jstat&#xff1a;性能分析-查看gc情况&#xff1b; jmap&#xff1a;内存分析-堆信息&#xff1b; jstack&#xff1a;线程分析-栈信息&#xff1b; jinfo&#xff1a;参数查看及配置&#xff1b; jstatd&#xff1a;启动jvm监控服务。它…...

IO多路复用--[select | poll | epoll | Reactor]

因为在简历上写了netty的项目&#xff0c;因此还是将网络底层的那点东西搞清楚。 首先希望明确的是&#xff0c;BIO、NIO、IO多路复用这是不同的东西&#xff0c; 我会在本文中详细讲出来。 本文参考资料&#xff1a; JAVA IO模型 IO多路复用 select poll epoll介绍 从BIO到epo…...

pod的requests、limits解读、LimitRange资源配额、Qos服务质量等级、资源配额管理 Resource Quotas

前言 环境&#xff1a;k8s-v1.22.17 docker-20.10.9 centos-7.9 目录前言什么是可计算资源CPU、Memory计量单位pod资源请求、限额方式pod定义requests、limits查看节点资源情况pod使用request、limits示例LimitRange限制命名空间下的pod的资源配额Qos服务质量等级资源配额管理…...

R语言基础(六):函数

R语言基础(一)&#xff1a;注释、变量 R语言基础(二)&#xff1a;常用函数 R语言基础(三)&#xff1a;运算 R语言基础(四)&#xff1a;数据类型 R语言基础(五)&#xff1a;流程控制语句 7. 函数 函数是一组完成特定功能的语句。 7.1 内置函数 R语言系统中提供许多内置函数&…...

[C++] 简单序列化

前言 序列化(Serialization) 是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间&#xff0c;对象将其当前状态写入到临时或持久性存储区。以后&#xff0c;可以通过从存储区中读取或反序列化对象的状态&#xff0c;重新创建该对象。 使用 序列化 std::array&…...

大学关工委加强自身建设网站宣传/关键词优化软件

vue框架介绍 框架,framework,是能够让程序开发人员更好的专注于业务逻辑的开发,而无需关心底层功能的实现。 vue是一个渐进式 JavaScript 框架,Vue (读音 /vjuː/,类似于 **view**) 是一套用于构建用户界面的**渐进式框架**。与其它大型框架不同的是,Vue 被设计为可以自底…...

合肥网站建设搜王道下拉/seo网站优化技术

rabbitmq实现延迟消息的方案 1. 使用延时队列 单机不考虑拓展的情况下&#xff0c;可以使用java.util.concurrent包的DelayQueue, 但插入的对象需实现Delayed接口&#xff0c;并实现其getDelay方法。 优点&#xff1a;针对任意消息队列均可使用 缺点&#xff1a;可拓展性差&a…...

wordpress 主题生成/营销型网站建设ppt

1 UI界面代码; 看着多。实际上真正布局用到的地方,也是非常有限的。 <Page x:Class="Wpf.Jie.Views.Jie1_Page1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&qu…...

网站建设首选云端高科/汤阴县seo快速排名有哪家好

重写父类方法&#xff0c;如&#xff1a; 当有一个通用的daoImpl时&#xff0c;有的时候需要重写这个类里面的方法。 然后又需要调用这个类的这个被重写的方法&#xff0c; 使用super来调用。...

扬州做机床公司网站/电商运营基础知识

知乎link&#xff1a;概率论中的“矩”是什么意思 后续继续补全吧...

wordpress secondary title/青岛网站seo服务

续博文《u-boot之u-boot-2009.11启动过程分析》 Linux内核启动及文件系统载入过程 当u-boot開始运行bootcmd命令。就进入Linux内核启动阶段&#xff0c;与u-boot类似&#xff0c;普通Linux内核的启动过程也能够分为两个阶段&#xff0c;但针对压缩了的内核如uImage就要包含内核…...