目录
一、背景
1.1、RSA算法
1.2、HTTPS
1.2.1、 HTTPS优点
1.2.2、 HTTPS缺点
二、目标
2.1、实现如下示例加签规则
2.2、具体密钥生成方式步骤
第一步:生成私钥命令
第二步:根据私钥生成对应公钥pem文件
第三步:将私钥转换成pkcs8格式
三、准备(order作为A企业服务,product作为B企业服务)
四、代码展示
4.1、order服务
5.1、product服务
五、测试验证
六、源码地址
一、背景
对于程序项目来说,企业间业务对接,少不了http api接口公网对接。而http接口公网对接就必须做到接口安全认证,防止接口或数据被拦截窃取,破解泄露商业信息,甚至黑客攻击。此时就必须做安全措施,如加白名单、数字安全认证证书(https)等。其中,RSA非对称加密进行加签和验证是常用的一种。RSA公钥加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的。RSA取名来自开发他们三者的名字。RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的所有密码攻击,已被ISO推荐为公钥数据加密标准。RSA算法详细请看密码学:RSA加密算法详解_大鱼-CSDN博客_rsa加密算法。
这里大致认识下RSA算法和数字安全认证https:
1.1、RSA算法-
RSA是目前最有影响力和最常用的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。
-
今天只有短的RSA钥匙才可能被强力方式破解。但在分布式计算和量子计算机理论日趋成熟的今天,RSA加密安全性收到了挑战和质疑。
-
RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解缺及其困难,因此可以将乘积公开作为加密密钥。
-
可以自己实现,无需购买,算法公开。
1.2、HTTPS
1.2.1、 HTTPS优点
-
使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。
-
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
-
HTTPS是现行框架下最安全的解决方案,虽然不是觉得安全,但它增加了中间人攻击的成本。
1.2.2、 HTTPS缺点
-
SSL的专业证书需要购买,功能越强大的证书费用越高
-
相同的网络环境下,HTTPS协议会使页面的加载时间延长50%,增加10%-20%的耗电。此外,HTTPS协议还会影响缓存,增加数据开销和功耗。
-
HTTPS协议的安全性是有范围的,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。
-
最关键的是,SSL证书的信用链体系并不安全。特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
二、目标 2.1、实现如下示例加签规则
- 将参数列表中除了sign的字段按照key升序排列,类似get的方式,用”=”和”&”拼接成字符串。
- 将编码得到的字符串使用私钥加密,密文字符串进行base64编码,得到的结果就是sign的值。
- 加密采用非对称RSA密钥对,密钥位数1024位。
-
最后以对象的序列化后的json字符串传输。
交互流程图,如下:
描述:A企业、B企业先生成公私钥,然后互相交换公钥。调用方调用接口前,使用自己的私钥加密; 被调用方接收数据前使用调用方给的公钥解密,解密成功允许调用接口逻辑处理返回数据;解密失败(鉴权失败)不允许调用接口。
2.2、具体密钥生成方式步骤 第一步:生成私钥命令如:openssl genrsa -out rsa_private_key.pem 1024
命令格式:openssl genras -out 私钥文件名 1024
实际 *** 作如下(这里使用git bash界面):
生成一个由”----BEGIN PRIVATE KEY-----”开头,由”-----END PRIVATE KEY-----”结尾的rsa_private_key.pem文件,这就是私钥文件。
第二步:根据私钥生成对应公钥pem文件如:openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
命令格式:openssl rsa -in私钥文件名 -pubout -out 公钥文件名
实际 *** 作如下:
生成一个由”----BEGIN PRIVATE KEY-----”开头,由”-----END PRIVATE KEY-----”结尾的rsa_public_key.pem文件,这就是公钥文件。
第三步:将私钥转换成pkcs8格式如:openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt > rsa_private_key_pkcs8.pem
命令格式:openssl pkcs8 -topk8 -inform PEM -in 私钥文件名 -outform PEM -nocrypt > pkcs8格式私钥文件名
实际 *** 作如下:
生成一个由”----BEGIN PRIVATE KEY-----”开头,由”-----END PRIVATE KEY-----”结尾的rsa_private_key_pkcs8.pem文件,这就是适配java语言开发的私钥文件(第一步生成的私钥是pkcs1格式的文件,像php可以直接使用。但java使用就必须转换成pkcs8格式的文件内容)。
描述:可以发现第三步和第一步都是私钥,他们都是由”----BEGIN PRIVATE KEY-----”开头,由”-----END PRIVATE KEY-----”结尾,密钥内容却不相同。在java代码里,我们读取的密钥体是不包含开头和结尾的,因此我们把第二部和第三步的pem文件去掉开头结尾重新存储下。
三、准备(order作为A企业服务,product作为B企业服务)- 服务order实现一个查询商品接口,商品接口由服务product以http接口形式提供,并实现一个消息转换器,做加密认证 *** 作(基于目前大部分服务都是高可用分布式微服务,所以本次order服务调用product服务接口使用springcloud的feignClient接口实现。注意,这里feignclient不用eureka服务,而是通过配置url直接调用product服务)。
- 服务product实现一个基于spring MVC框架实现Http接口,并实现一个切面拦截被调用接口的请求做解密认证。
- 环境:jdk1.8。
pom.xml配置如下:
4.0.0 org.springframework.boot spring-boot-starter-parent2.5.5 com.example order0.0.1-SNAPSHOT order Demo project for Spring Boot 1.8 2020.0.4 org.springframework.boot spring-boot-starter-weborg.springframework.cloud spring-cloud-starterorg.springframework.cloud spring-cloud-starter-openfeignorg.projectlombok lomboktrue org.springframework.boot spring-boot-starter-testtest com.alibaba fastjson1.2.78 org.springframework.cloud spring-cloud-dependencies${spring-cloud.version} pom import org.springframework.boot spring-boot-maven-pluginorg.projectlombok lombok
application.yml配置文件配置:
注意:这里的rsa.010.private-key就是私钥文件rsa_private_key_pkcs8.pem去掉头尾的内容。
spring: application: name: order server: port: 8081 servlet: context-path: /order #不使用eureka服务 eureka: client: enabled: false #私钥前缀需要取私钥的key保持一致 rsa.010.private-key : MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAONKrJ8MQQlDAye/ sa8xcBauSmlOSlXH8KuBWheS7anovJSlhtPOIqSUuroT0xMcHsiSqYFAp8t2/k3r vwWCXx2HwHPtw240DIQ5IBKSq743GdXFAOFXdZh1epf+NPtpIeYoF+aXlgwplqSG iTdA8WnRQ5OPS0KZUdbK9e9jUodPAgMBAAECgYANBPgCXEdVantByZ8589EB25Xz lkJ3y24jxNMOSqJGe0hiE2E3vLULTGGtyvjqPVAeGRiQiM2TwAstF3XnsOIVyUxF HY60AXtMzlYkBrsyyIGF7FrVBuWaTbRYPE8EFOVMVZy/nziQE/bZKVYLHufqqob7 RZtzMMd9CI8bbuKK4QJBAPmVezMgTI+mdFWANUL27DM9tAJllN+T9bPKTP443xbd JDEoKUzx3tktTnQXqQmUIrNuBZTDi5SN29bj3E+ZiokCQQDpInzDprUQmgGX3VnG JNPx1fcUQF7DQsxm8k8MCbkJetHcIW/TShKL0Dt2viyiW6uapzJJLTxBAK+HFk2W hS0XAkBVohoxQoXCS+RiaajcnwgP1L3sjJn11DhbRbABEdZJa/q8+wCgq+RAM7FV V8DhznfRhJBZqHY9tCaXpnqyvQWxAkEAyqb33PqkmfHFQMVgrCSHN8jOJgRuWz1N gI9Qtx4cgmkI01kdY4UX6gDwL5/QHLGi0aRUyddQcRCvg7WXbCgHsQJBAJMen29/ aQpJC3gOTPjQJowuYRuCLar6YGj3YcPR1DrciNqz7xiFoTtgPJfQLerx+HCFJ0dW Yk6YY4z7Xsu5utg=
controller实现:
package com.example.order.controller; import com.example.order.service.ProductService; import com.example.order.vo.ProductResponse; import com.example.order.vo.Response; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class ProductController { @Resource private ProductService productService; @RequestMapping(value = "/query/{id}") public ResponsequeryById(@PathVariable Integer id){ return Response.success(productService.queryById(id)); } }
service实现:
package com.example.order.service; import com.example.order.feign.ProductMicroServer; import com.example.order.vo.ProductRequest; import com.example.order.vo.ProductResponse; import com.example.order.vo.Response; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Objects; @Service public class ProductService { @Resource private ProductMicroServer productMicroServer; public ProductResponse queryById(Integer id){ ProductRequest productRequest = new ProductRequest(); productRequest.setId(id); productRequest.setAppId("010"); ResponseresponseResponse = productMicroServer.selectByCondition(productRequest); if(Objects.nonNull(responseResponse)){ return responseResponse.getData(); } return null; } }
feignclient接口实现:
package com.example.order.feign; import com.example.order.vo.ProductRequest; import com.example.order.vo.ProductResponse; import com.example.order.vo.Response; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; @FeignClient(name = "product", url="http://localhost:8082/product", fallbackFactory = ProductMicroServerFallbackFactory.class) public interface ProductMicroServer { @PostMapping(value = "/selectByCondition", consumes = MediaType.APPLICATION_JSON_VALUE) ResponseselectByCondition(ProductRequest request); }
package com.example.order.feign; import com.example.order.vo.ProductRequest; import com.example.order.vo.ProductResponse; import com.example.order.vo.Response; import org.springframework.stereotype.Service; import java.math.BigDecimal; @Service public class ProductMicroServerFallback implements ProductMicroServer{ @Override public ResponseselectByCondition(ProductRequest request) { return Response.success(new ProductResponse(0, "棒棒糖(兜底商品)", 1, new BigDecimal(0.5))); } }
package com.example.order.feign; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Slf4j @Service public class ProductMicroServerFallbackFactory implements FallbackFactory{ @Resource private ProductMicroServerFallback productMicroServerFallback; @Override public ProductMicroServer create(Throwable cause) { log.error("ProductMicroServerFallback->selectById(Integer id) exception:", cause); return productMicroServerFallback; } }
自定义转换器的实现(继承org.springframework.http.converter.AbstractHttpMessageConverter抽象类):
package com.example.order.config; import com.alibaba.fastjson.JSON; import com.example.order.common.RsaUtils; import com.example.order.service.GlobalValuesService; import com.example.order.vo.baseRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import javax.annotation.Resource; import java.io.IOException; import java.util.TreeMap; import java.util.UUID; @Slf4j @Component public class HttpGlobalOutMessageConverterextends AbstractHttpMessageConverter { private static final String QUOTE_MARK = """; private String INVOKER_TRACE_ID = "invoke_traceId"; @Resource private GlobalValuesService globalValuesService; public HttpGlobalOutMessageConverter() { //支持的两种媒体类型 super(MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON); } @Override protected boolean supports(Class> clazz) { //表示只支持baseRequest这个类(包括子类) return baseRequest.class.isAssignableFrom(clazz); } @Override protected T readInternal(Class extends T> clazz, HttpInputMessage inputMessage) { throw new RuntimeException("暂不支持"); } @Override protected void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //将请求参数组装成map格式 baseRequest request = t; request.setTimestamp(String.valueOf(System.currentTimeMillis())); TreeMap map = JSON.parseObject(JSON.toJSonString(request), TreeMap.class); //参数Map 转成 字符串(使用&符号key=value的形式拼接) String requestString = requestString(map); //4.签名处理 String sign = RsaUtils.signatureByPrivateKey(requestString, globalValuesService.privateKey(request.getAppId())); map.put("sign", sign); String parameters = JSON.toJSonString(map); //2.trace参数 String headerRid = UUID.randomUUID().toString().replaceAll("-", ""); //5.写入body byte[] bytes = parameters.getBytes(); outputMessage.getHeaders().setContentLength(bytes.length); outputMessage.getHeaders().add(INVOKER_TRACE_ID, headerRid); StreamUtils.copy(bytes, outputMessage.getBody()); log.info("traceId:{}, parameters:{}", headerRid, parameters); } private static String requestString(TreeMap requestMap) { StringBuilder requestStringBuilder = new StringBuilder(); requestMap.forEach((property, value) -> { requestStringBuilder.append(property).append("="); if (value != null) { String string = JSON.toJSonString(value); if (string.startsWith(QUOTE_MARK) && string.endsWith(QUOTE_MARK)) { string = string.substring(1, string.length() - 1); } //去掉多次转义 string = string.replaceAll("\\", ""); requestStringBuilder.append(string); } requestStringBuilder.append("&"); }); if (requestStringBuilder.length() > 0) { requestStringBuilder.deleteCharAt(requestStringBuilder.length() - 1); } return requestStringBuilder.toString(); } }
使用到的工具类:
package com.example.order.common; import lombok.extern.slf4j.Slf4j; import java.util.base64; @Slf4j public class base64Utils { @SuppressWarnings("restriction") public static String encode(byte[] bytes) { return new String(base64.getEncoder().encode(bytes)).replaceAll("[rn]", ""); } @SuppressWarnings("restriction") public static byte[] decode(String str) { return base64.getDecoder().decode(str); } }
package com.example.product.common; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.util.ResourceUtils; import java.io.FileReader; import java.io.IOException; import java.security.KeyFactory; import java.security.Signature; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @Slf4j public class RsaUtils { private static String handleKey(String key) { //1. 去开头结尾符 key = key.replaceAll("--.*--", ""); //2. 去除换行 key = key.replaceAll("[rn]", ""); //3. 去空格 key = key.replaceAll(" ", ""); return key; } //#################### 私钥:签名 public static String signatureByPrivateKey(String data, String privateKey) { if (StringUtils.isBlank(privateKey)) { log.warn("私钥不可为空"); return ""; } privateKey = handleKey(privateKey); try { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(base64Utils.decode(privateKey)); RSAPrivateKey key = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec); Signature signature = Signature.getInstance("SHA1withRSA"); signature.initSign(key); signature.update(data.getBytes()); return base64Utils.encode(signature.sign()); } catch (Exception e) { log.warn("私钥加密失败,data:[{},privateKey:[{}],exception:", data, privateKey,e); return ""; } } //#################### 公钥:验签 public static boolean verifyByPublicKey(String data, String publicKey, String sign) { if (StringUtils.isBlank(publicKey)) { log.warn("公钥钥不可为空"); return false; } publicKey = handleKey(publicKey); try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64Utils.decode(publicKey)); RSAPublicKey rsaPubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec); Signature signature = Signature.getInstance("SHA1withRSA"); signature.initVerify(rsaPubKey); signature.update(data.getBytes()); return signature.verify(base64Utils.decode(sign)); } catch (Exception e) { log.warn("公钥解密失败,sign:[{},publicKey:[{}],exception:", sign, publicKey,e); return false; } } }
package com.example.order.service; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class GlobalValuesService { @Resource private Environment environment; public String privateKey(String appId) { return environment.getProperty(String.format("rsa.%s.private-key", appId)); } }
package com.example.order.vo; import lombok.Getter; import lombok.Setter; @Getter @Setter public class baseRequest { private String appId; private String timestamp; }
package com.example.order.vo; import lombok.Getter; import lombok.Setter; @Getter @Setter public class ProductRequest extends baseRequest { private Integer id; }
package com.example.order.vo; import lombok.AllArgsConstructor; import lombok.Data; import java.math.BigDecimal; @Data @AllArgsConstructor public class ProductResponse { private Integer id; private String name; private Integer num; private BigDecimal price; }
package com.example.order.vo; import lombok.Getter; import lombok.Setter; @Setter @Getter public class Response{ private Integer errorCode; private String errorMsg; private T data; public Response(Integer errorCode, String errorMsg, T data) { this.errorCode = errorCode; this.errorMsg = errorMsg; this.data = data; } public static Response success(T data){ return new Response<>(null, null, data); } }
启动类:
package com.example.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients(value = "com.example.order.feign") @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
项目结构:
5.1、product服务pom.xml配置:
4.0.0 org.springframework.boot spring-boot-starter-parent2.5.5 com.example product0.0.1-SNAPSHOT product Demo project for Spring Boot 1.8 2020.0.4 org.springframework.boot spring-boot-starter-weborg.springframework.cloud spring-cloud-starterorg.projectlombok lomboktrue org.springframework.boot spring-boot-starter-testtest com.alibaba fastjson1.2.78 org.springframework.boot spring-boot-starter-aoporg.springframework.cloud spring-cloud-dependencies${spring-cloud.version} pom import org.springframework.boot spring-boot-maven-pluginorg.projectlombok lombok
application.yml配置:
注意:这里的rsa.010.public-key就是公钥文件rsa_public_key.pem去掉头尾的内容。
spring: application: name: product server: port: 8082 servlet: context-path: /product #不使用eureka服务 eureka: client: enabled: false rsa.010.public-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjSqyfDEEJQwMnv7GvMXAWrkpp TkpVx/CrgVoXku2p6LyUpYbTziKklLq6E9MTHB7IkqmBQKfLdv5N678Fgl8dh8Bz 7cNuNAyEOSASkqu+NxnVxQDhV3WYdXqX/jT7aSHmKBfml5YMKZakhok3QPFp0UOT j0tCmVHWyvXvY1KHTwIDAQAB
controller接口实现:
package com.example.product.controller; import com.example.product.service.ProductService; import com.example.product.vo.ProductRequest; import com.example.product.vo.ProductResponse; import com.example.product.vo.Response; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @Slf4j @RestController public class ProductController { @Resource private ProductService productService; @PostMapping(value = "/selectByCondition", consumes = APPLICATION_JSON_VALUE) public ResponseselectByCondition(@RequestBody ProductRequest request){ log.info("request.sign:{}", request.getSign()); return Response.success(productService.queryById(request.getId())); } }
service实现:
package com.example.product.service; import com.example.product.vo.ProductResponse; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; @Service public class ProductService { private static MapproductHashMap = new HashMap<>(); static { productHashMap.put(1, new ProductResponse(1, "冰箱", 5, new BigDecimal(20000))); productHashMap.put(2, new ProductResponse(2, "空调", 9, new BigDecimal(30000))); productHashMap.put(3, new ProductResponse(3, "洗衣机", 8, new BigDecimal(5000))); } public ProductResponse queryById(Integer id){ return productHashMap.get(id); } }
验签切面类:
package com.example.product.config; import com.example.product.common.GlobalRequestUtils; import com.example.product.service.GlobalValuesService; import com.example.product.common.RsaUtils; import com.example.product.vo.baseRequest; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Objects; import static com.alibaba.fastjson.JSON.toJSONString; @Slf4j @Aspect @Component public class SecretVerifyAspect{ private String INVOKER_TRACE_ID = "invoke_traceId"; @Resource GlobalValuesService globalValuesService; @Before("execution(public * com.example.product.controller.ProductController.*(..))") public void secretVerify(JoinPoint point){ //打印调用者传过来的traceId try { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; if (Objects.isNull(sra)) { log.warn("ServletRequestAttributes is null"); return; } HttpServletRequest request = sra.getRequest(); if (Objects.nonNull(request.getHeader(INVOKER_TRACE_ID))) { //打印调用者的traceId, 出现问题时,方便排查跟踪 log.info("{}:{}", INVOKER_TRACE_ID, request.getHeader(INVOKER_TRACE_ID)); } } catch (Exception e) { log.warn("exception:", e); } //开始验签 Object[] args = point.getArgs(); for (Object arg : args) { if (!(arg instanceof baseRequest)) { continue; } baseRequest baseRequest = (baseRequest) arg; String requestString; try { requestString = GlobalRequestUtils.requestString(baseRequest, true); } catch (IllegalAccessException e) { log.warn("构建签名参数错误,eMsg:", e); throw new RuntimeException("签名错误!!!"); } //校验签名 boolean verify = RsaUtils.verifyByPublicKey(requestString, globalValuesService.didiPublicKey(baseRequest.getAppId()), baseRequest.getSign()); if (!verify) { log.warn("签名校验错误,requestSign [{}],requestString [{}],args [{}]", baseRequest.getSign(), requestString, toJSonString(baseRequest)); throw new RuntimeException("签名错误!!!"); }else{ log.info("签名验证正确."); } } } }
其他工具类:
package com.example.product.common; import lombok.extern.slf4j.Slf4j; import java.util.base64; @Slf4j public class base64Utils { @SuppressWarnings("restriction") public static String encode(byte[] bytes) { return new String(base64.getEncoder().encode(bytes)).replaceAll("[rn]", ""); } @SuppressWarnings("restriction") public static byte[] decode(String str) { return base64.getDecoder().decode(str); } }
package com.example.product.common; import com.alibaba.fastjson.JSON; import com.example.product.vo.baseRequest; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.experimental.UtilityClass; import org.apache.commons.lang.StringUtils; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.URLEncoder; import java.util.Map; import java.util.TreeMap; public class GlobalRequestUtils { private static final String QUOTE_MARK = """; public staticString requestString(T request, boolean filterNull) throws IllegalAccessException { return requestString(requestMap(request), filterNull, false); } public static Map requestMap(T request) throws IllegalAccessException { Map requestMap = new TreeMap<>(); Class> clz = request.getClass(); while (baseRequest.class.isAssignableFrom(clz)) { for (Field field : clz.getDeclaredFields()) { field.setAccessible(true); JsonProperty annotation = field.getAnnotation(JsonProperty.class); if (annotation == null) { //没有 @JsonProperty 注解的属性不予解析(sign属性无需加该注解) continue; } String property = StringUtils.isEmpty(annotation.value()) ? field.getName() : annotation.value(); requestMap.put(property, field.get(request)); } clz = clz.getSuperclass(); } return requestMap; } public static String requestString(Map requestMap, boolean filterNull, boolean urlEncode) { StringBuilder requestStringBuilder = new StringBuilder(); requestMap.forEach((property, value) -> { if (filterNull && value == null) { return; } requestStringBuilder.append(property).append("="); if (value != null) { String string = JSON.toJSonString(value); if (string.startsWith(QUOTE_MARK) && string.endsWith(QUOTE_MARK)) { string = string.substring(1, string.length() - 1); } //去掉多次转义 string = string.replaceAll("\\", ""); if (urlEncode) { try { string = URLEncoder.encode(string, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } requestStringBuilder.append(string); } requestStringBuilder.append("&"); }); if (requestStringBuilder.length() > 0) { requestStringBuilder.deleteCharAt(requestStringBuilder.length() - 1); } return requestStringBuilder.toString(); } }
package com.example.product.common; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import java.security.KeyFactory; import java.security.Signature; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @Slf4j public class RsaUtils { private static String handleKey(String key) { //1. 去开头结尾符 key = key.replaceAll("--.*--", ""); //2. 去除换行 key = key.replaceAll("[rn]", ""); //3. 去空格 key = key.replaceAll(" ", ""); return key; } //#################### 私钥:签名 public static String signatureByPrivateKey(String data, String privateKey) { if (StringUtils.isBlank(privateKey)) { log.warn("私钥不可为空"); return ""; } privateKey = handleKey(privateKey); try { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(base64Utils.decode(privateKey)); RSAPrivateKey key = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec); Signature signature = Signature.getInstance("SHA1withRSA"); signature.initSign(key); signature.update(data.getBytes()); return base64Utils.encode(signature.sign()); } catch (Exception e) { log.warn("私钥加密失败,data:[{},privateKey:[{}],exception:", data, privateKey,e); return ""; } } //#################### 公钥:验签 public static boolean verifyByPublicKey(String data, String publicKey, String sign) { if (StringUtils.isBlank(publicKey)) { log.warn("公钥钥不可为空"); return false; } publicKey = handleKey(publicKey); try { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(base64Utils.decode(publicKey)); RSAPublicKey rsaPubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec); Signature signature = Signature.getInstance("SHA1withRSA"); signature.initVerify(rsaPubKey); signature.update(data.getBytes()); return signature.verify(base64Utils.decode(sign)); } catch (Exception e) { log.warn("公钥解密失败,sign:[{},publicKey:[{}],exception:", sign, publicKey,e); return false; } } }
package com.example.product.service; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component public class GlobalValuesService { @Resource private Environment environment; public String didiPublicKey(String appId) { return environment.getProperty(String.format("rsa.%s.public-key", appId)); } }
package com.example.product.vo; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; @Setter @Getter public class baseRequest { @JsonProperty private String appId; private String sign; @JsonProperty private String timestamp; }
package com.example.product.vo; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; @Getter @Setter public class ProductRequest extends baseRequest { @JsonProperty private Integer id; }
package com.example.order.vo; import lombok.AllArgsConstructor; import lombok.Data; import java.math.BigDecimal; @Data @AllArgsConstructor public class ProductResponse { private Integer id; private String name; private Integer num; private BigDecimal price; }
package com.example.order.vo; import lombok.Getter; import lombok.Setter; @Setter @Getter public class Response{ private Integer errorCode; private String errorMsg; private T data; public Response(Integer errorCode, String errorMsg, T data) { this.errorCode = errorCode; this.errorMsg = errorMsg; this.data = data; } public static Response success(T data){ return new Response<>(null, null, data); } }
项目结构:
五、测试验证第一步:启动order服务。
第二部:启动product服务。
第三步:访问order接口: http://localhost:8081/order/query/1,结果展示:
查看order服务的关键日志展示:
第四步:查看product的关键日志:
六、源码地址https://download.csdn.net/download/u010132847/33493685。
资料参考:密码学:RSA加密算法详解_大鱼-CSDN博客_rsa加密算法
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)