first commit

This commit is contained in:
abcv7
2026-02-25 15:08:40 +08:00
commit 7f1d83ada7
2003 changed files with 144362 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>sfbx-cloud</artifactId>
<groupId>com.itheima.sfbx</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--三方sdk接口封装-->
<artifactId>sfbx-apache-httpclient</artifactId>
<name>sfbx-apache-httpclient</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,58 @@
package com.itheima.sfbx;
import com.itheima.sfbx.auth.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.execchain.ClientExecChain;
/**
* @ClassName BoleeHttpClientBuilder.java
* @Description 保乐三方请求HttpClient对象构建
*/
public class BoleeHttpClientBuilder extends HttpClientBuilder {
//系统相关属性
private static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");
private static final String VERSION = System.getProperty("java.version");
//凭证构建
private Credentials credentials;
//校验者
private Validator validator;
public static BoleeHttpClientBuilder create() {
return new BoleeHttpClientBuilder();
}
private BoleeHttpClientBuilder() {
String userAgent = String.format("sfbx-Apache-HttpClient/%s (%s) Java/%s", this.getClass().getPackage().getImplementationVersion(), OS, VERSION == null ? "Unknown" : VERSION);
this.setUserAgent(userAgent);
}
public BoleeHttpClientBuilder withCredentials(String appId,String privateKey) {
this.credentials = new BoleeCredentials(appId, new PrivateKeySigner(privateKey));
return this;
}
public BoleeHttpClientBuilder withValidator(String publicKey) {
this.validator = new BoleeValidator(new PublicKeyVerifier(publicKey));
return this;
}
public CloseableHttpClient build() {
if (this.credentials == null) {
throw new IllegalArgumentException("缺少身份认证信息");
} else if (this.validator == null) {
throw new IllegalArgumentException("缺少签名验证信息");
} else {
return super.build();
}
}
protected ClientExecChain decorateProtocolExec(ClientExecChain requestExecutor) {
return new DecorateClientExecChain(this.credentials, this.validator, requestExecutor);
}
}
@@ -0,0 +1,19 @@
package com.itheima.sfbx;
import org.apache.http.client.methods.HttpRequestWrapper;
import java.io.IOException;
/**
* @ClassName Credentials.java
* @Description 认证凭证创建处理
*/
public interface Credentials {
/***
* @description 创建请求认证凭证
* @param request 请求对象
* @return: java.lang.String 凭证字符串
*/
String createCredentials(HttpRequestWrapper request) throws IOException;
}
@@ -0,0 +1,75 @@
package com.itheima.sfbx;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.util.EntityUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import static org.apache.http.HttpHeaders.AUTHORIZATION;
import static org.apache.http.HttpStatus.SC_MULTIPLE_CHOICES;
import static org.apache.http.HttpStatus.SC_OK;
/**
* @ClassName SignatureExec.java
* @Description 请求执行者
*/
@Slf4j
public class DecorateClientExecChain implements ClientExecChain {
private final ClientExecChain mainExec;
private final Credentials credentials;
private final Validator validator;
protected DecorateClientExecChain(Credentials credentials, Validator validator, ClientExecChain mainExec) {
this.credentials = credentials;
this.validator = validator;
this.mainExec = mainExec;
}
protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
response.setEntity(new BufferedHttpEntity(entity));
}
}
@Override
public CloseableHttpResponse execute(HttpRoute httpRoute, HttpRequestWrapper httpRequestWrapper,
HttpClientContext httpClientContext, HttpExecutionAware httpExecutionAware) throws IOException, HttpException {
//请求头添加认证令牌
httpRequestWrapper.addHeader(AUTHORIZATION,credentials.createCredentials(httpRequestWrapper));
//执行请求
CloseableHttpResponse response = mainExec.execute(httpRoute, httpRequestWrapper, httpClientContext, httpExecutionAware);
//对成功应答验签
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() >= SC_OK && statusLine.getStatusCode() < SC_MULTIPLE_CHOICES) {
convertToRepeatableResponseEntity(response);
if (!validator.validate(response)) {
throw new HttpException("应答的签名验证失败");
}
}
return response;
}
}
@@ -0,0 +1,16 @@
package com.itheima.sfbx;
import com.itheima.sfbx.dto.EncodeDTO;
import java.io.IOException;
public interface Encryptor {
/***
* @description 加密body体消息的方法
* @param body 请求消息体
* @return: boolean 校验结果
*/
boolean encode(EncodeDTO body) throws IOException;
}
@@ -0,0 +1,163 @@
package com.itheima.sfbx;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itheima.sfbx.auth.BoleeEncryptor;
import com.itheima.sfbx.constants.BoleeSecurityConstant;
import com.itheima.sfbx.dto.CredentialsDTO;
import com.itheima.sfbx.dto.EncodeDTO;
import com.itheima.sfbx.utils.SecurityUtil;
import lombok.Builder;
import lombok.Data;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
/**
* RequestTemplate
*
* @author: wgl
* @describe: 对外统一发送模板--组装各个模组
* 加密模组
* 解密模组组装
* @date: 2022/12/28 10:10
*/
@Data
public class RequestTemplate {
private String privateKey;
private String publicKey;
private String appId;
private URIBuilder uriBuilder;
private RequestConfig requestConfig;
@Builder
public RequestTemplate(String privateKey, String publicKey, String appId, URIBuilder uriBuilder,RequestConfig requestConfig) {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.appId = appId;
this.uriBuilder = uriBuilder;
this.requestConfig = requestConfig;
}
public <T> T doRequest(Object params,Class<T> t) throws URISyntaxException, IOException {
CloseableHttpClient httpclient = BoleeHttpClientBuilder.create()
.withCredentials(appId, privateKey)
.withValidator(publicKey)
.build();
//=============基础配置===================
RequestConfig requestConfig = null;
if(ObjectUtil.isNull(requestConfig)) {
requestConfig = RequestConfig.custom()
.setSocketTimeout(100000)
.setConnectTimeout(100000)
.build();
}else{
requestConfig = this.requestConfig;
}
try {
URI url = uriBuilder.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
EncodeDTO encodeDTO = new EncodeDTO();
JSONObject body = JSONUtil.parseObj(params);
encodeDTO.setBody(body.toString());
if(new BoleeEncryptor(privateKey).encode(encodeDTO)){
StringEntity stringEntity = new StringEntity(encodeDTO.getEncodeBody(), "utf-8");
httpPost.setEntity(stringEntity);
httpPost.addHeader(BoleeSecurityConstant.HEAD_NAME_BODY_KEY,encodeDTO.getHeadAESSecurityKey());
httpPost.addHeader("Content-Type","application/json;charset=utf-8");
CloseableHttpResponse apiRes = httpclient.execute(httpPost);
//验签完毕后需要对数据进行解密
HttpEntity entity = apiRes.getEntity();
String content = EntityUtils.toString(entity);
String responseJson = decryptRequestBody(content, apiRes.getFirstHeader(BoleeSecurityConstant.HEAD_NAME_BODY_KEY).getValue());
if(StrUtil.isEmpty(responseJson)){
throw new RuntimeException("参数解析为空");
}
return JSONUtil.toBean(responseJson, t);
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return null;
}
/**
* 解密请求body体
*
* @param dody 返回数据对象
* @param aesKeyRSA 获取请求头中加密后的AES密钥
* @return
*/
private String decryptRequestBody(String dody, String aesKeyRSA) {
try {
//先使用rsa解密aes秘钥
RSA rsa = SecureUtil.rsa(null, publicKey);
String aesKey = SecurityUtil.decryptFromStringRSAPublicKey(rsa, aesKeyRSA);
//使用aes进行解密
// 使用解密后的AES密钥进行AES解密请求体数据
String requestBody = SecurityUtil.decryptFromStringAES(dody, Mode.CBC, Padding.ZeroPadding,aesKey);
return requestBody;
} catch (Exception e) {
e.printStackTrace();
// 解密失败,可以根据你的需求进行异常处理
return null;
}
}
/**
* 构建请求参数
* 1694953272
* xHct36JgHbj6tXBx
* Modified: {"msg":"ok","code":"100","data":null}
*
* @param authorization
* @param response
* @return
*/
private CredentialsDTO buildRequestParam(String authorization, CloseableHttpResponse response) {
try {
authorization = authorization.replaceAll("\"", "");
String[] authorizations = authorization.split(",");
Map<String, String> authMap = new HashMap<>();
for (String authorizationIndex : authorizations) {
String[] split = authorizationIndex.split("=");
authMap.put(split[0], split[1].substring(0, split[1].length()));
}
CredentialsDTO credentialsDTO = new CredentialsDTO();
credentialsDTO.setNonce(authMap.get("nonce_str"));
credentialsDTO.setTimestamp(authMap.get("timestamp"));
credentialsDTO.setSignature(authMap.get("signature"));
// credentialsDTO.setSecurityBody(getPostData(req));
return credentialsDTO;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,11 @@
package com.itheima.sfbx;
/**
* @ClassName Signer.java
* @Description 签名加密者
*/
public interface Signer {
String sign(byte[] message);
}
@@ -0,0 +1,27 @@
package com.itheima.sfbx;
import com.itheima.sfbx.notify.NotifyRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import java.io.IOException;
/**
* @ClassName Validator.java
* @Description 签名验证处理
*/
public interface Validator {
/***
* @description 验证应答签名
* @param response 响应结果
* @return: boolean 校验结果
*/
boolean validate(CloseableHttpResponse response) throws IOException;
/***
* @description 验证通知请求签名
* @param notifyRequest 响应结果
* @return: boolean 校验结果
*/
boolean validateNotify(NotifyRequest notifyRequest);
}
@@ -0,0 +1,17 @@
package com.itheima.sfbx;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import org.apache.commons.codec.binary.Base64;
/**
* @ClassName Verifier.java
* @Description 验签解密者
*/
public interface Verifier {
boolean verify(byte[] data,byte[] signature);
}
@@ -0,0 +1,49 @@
package com.itheima.sfbx.auth;
import com.itheima.sfbx.Credentials;
import com.itheima.sfbx.Signer;
import com.itheima.sfbx.utils.ClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.HttpRequestWrapper;
import java.io.IOException;
/**
* @ClassName BoleeCredentials.java
* @Description 认证凭证创建处理实现
*/
@Slf4j
public class BoleeCredentials implements Credentials {
//应用id
protected final String appId;
//签名者
protected final Signer signer;
public BoleeCredentials(String appId, Signer signer) {
this.appId = appId;
this.signer = signer;
}
@Override
public String createCredentials(HttpRequestWrapper request) throws IOException {
//临时字符串
String nonceStr = ClientUtils.generateNonceStr();
//时间戳
long timestamp = ClientUtils.generateTimestamp();
//请求数据
String message = ClientUtils.requestMessage(nonceStr, timestamp, request);
log.debug("authorization data:{}", message);
//生成签名字符串
String signature = signer.sign(Base64.decodeBase64(message));
//返回认证令牌对象
String credentials = "appId=\"" + this.appId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "signature=\"" + signature + "\"";
log.debug("authorization credentials=[{}]", credentials);
return credentials;
}
}
@@ -0,0 +1,59 @@
package com.itheima.sfbx.auth;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import com.itheima.sfbx.Encryptor;
import com.itheima.sfbx.constants.BoleeSecurityConstant;
import com.itheima.sfbx.dto.EncodeDTO;
import com.itheima.sfbx.utils.ClientUtils;
import com.itheima.sfbx.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.HttpRequestWrapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* BoleeEncryptor
*
* @author: wgl
* @describe: TODO
* @date: 2022/12/28 10:10
*/
@Slf4j
public class BoleeEncryptor implements Encryptor {
//私钥签名
protected String privateKeyBase64;
public BoleeEncryptor(String privateKey) {
this.privateKeyBase64 = privateKey;
}
/**
* 对数据进行加密
* @param body 请求消息体
* @return
* @throws IOException
*/
@Override
public boolean encode(EncodeDTO body){
//获取AES加密秘钥
String bodySecurity = ClientUtils.generateNonceStr();
//使用AES加密 body体里的数据
String aesSecurity = SecurityUtil.encryptFromStringAES(body.getBody(), Mode.CBC, Padding.ZeroPadding,bodySecurity);
body.setEncodeBody(aesSecurity);
//使用RSA加密AES的对称加密秘钥
RSA rsa = new RSA(privateKeyBase64, null);
String securityToken = SecurityUtil.encryptFromStringRSAPrivateKey(rsa, bodySecurity);
body.setHeadAESSecurityKey(securityToken);
//将新生产的AES的body返回
return true;
}
}
@@ -0,0 +1,48 @@
package com.itheima.sfbx.auth;
import com.itheima.sfbx.Validator;
import com.itheima.sfbx.Verifier;
import com.itheima.sfbx.notify.NotifyRequest;
import com.itheima.sfbx.utils.ClientUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @ClassName BoleeValidator.java
* @Description 签名验证处理实现
*/
public class BoleeValidator implements Validator {
protected final Verifier verifier;
public BoleeValidator(Verifier verifier) {
this.verifier = verifier;
}
@Override
public boolean validate(CloseableHttpResponse response) throws IOException {
//校验请求参数
ClientUtils.validateParameters(response);
//构建响应体信息
String message = ClientUtils.responseMessage(response);
//获得签名信息
String signature = response.getFirstHeader("signature").getValue();
//验证签名
return verifier.verify(message.getBytes(StandardCharsets.UTF_8), Base64.decodeBase64(signature));
}
@Override
public boolean validateNotify(NotifyRequest notifyRequest) {
//校验请求参数
ClientUtils.validateParametersNotify(notifyRequest);
//构建响应体信息
String message = ClientUtils.notifyMessage(notifyRequest);
//获得签名信息
String signature = notifyRequest.getSignature();
//验证签名
return verifier.verify(message.getBytes(StandardCharsets.UTF_8), Base64.decodeBase64(signature));
}
}
@@ -0,0 +1,29 @@
package com.itheima.sfbx.auth;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.itheima.sfbx.Signer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
/**
* @ClassName PrivateKeySigner.java
* @Description TODO
*/
@Slf4j
public class PrivateKeySigner implements Signer {
protected final String privateKey;
public PrivateKeySigner(String privateKey) {
this.privateKey = privateKey;
}
@Override
public String sign(byte[] message) {
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,Base64.decodeBase64(privateKey),null);
return Base64.encodeBase64String(sign.sign(message));
}
}
@@ -0,0 +1,26 @@
package com.itheima.sfbx.auth;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.itheima.sfbx.Verifier;
import org.apache.commons.codec.binary.Base64;
/**
* @ClassName PublicKeyVerifier.java
* @Description 公钥验证
*/
public class PublicKeyVerifier implements Verifier {
protected final String publicKey;
public PublicKeyVerifier(String publicKey) {
this.publicKey = publicKey;
}
@Override
public boolean verify(byte[] data,byte[] signature){
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,null, Base64.decodeBase64(publicKey));
return sign.verify(data,signature);
}
}
@@ -0,0 +1,36 @@
package com.itheima.sfbx.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* SecurityConfig
*
* @author: wgl
* @describe: 客户端秘钥配置
* @date: 2022/12/28 10:10
*/
@ConfigurationProperties(prefix = "itheima.security")
@Data
public class SecurityConfig {
/**
* 客户appId
*/
@Value("${itheima.security.client.appid}")
private String appId;
/**
* 客户端私钥
*/
@Value("${itheima.security.client.privateKey}")
private String privateKey;
/**
* 服务端公钥
*/
@Value("${itheima.security.server.publicKey}")
private String publicKey;
}
@@ -0,0 +1,16 @@
package com.itheima.sfbx.constants;
/**
* BoleeSecurityConstant
*
* @author: wgl
* @describe: 宝乐保险秘钥常量
* @date: 2022/12/28 10:10
*/
public class BoleeSecurityConstant {
/**
* 宝乐保险中body体中的信息
*/
public static String HEAD_NAME_BODY_KEY = "bolee_security_key";
}
@@ -0,0 +1,65 @@
package com.itheima.sfbx.dto;
import lombok.Data;
/**
* RequestParamDTO
*
* @author: wgl
* @describe: TODO
* @date: 2022/12/28 10:10
*/
@Data
public class CredentialsDTO {
/**
* 请求方式
*/
private String methodd;
/**
* 接口路径
*/
private String uriPath;
/**
* 时间戳
*/
private String timestamp;
/**
* 签名数据
*/
private String signature;
/**
* 密文数据
*/
private String securityBody;
/**
* 随机字符串
*/
private String nonce;
/**
* 构建签名字符串
* return request.getRequestLine().getMethod() + "\n"
* + canonicalUrl + "\n"
* + timestamp + "\n"
* + nonce + "\n"
* + body + "\n";
* @return
*/
public String buildSignBody(){
StringBuilder sb = new StringBuilder();
sb.append(methodd + "\n");
sb.append(uriPath + "\n");
sb.append(timestamp + "\n");
sb.append(nonce + "\n");
sb.append(securityBody + "\n");
return sb.toString();
}
}
@@ -0,0 +1,29 @@
package com.itheima.sfbx.dto;
import lombok.Data;
/**
* EncodeDTO
*
* @author: wgl
* @describe: TODO
* @date: 2022/12/28 10:10
*/
@Data
public class EncodeDTO {
/**
* 原始body参数
*/
private String body;
/**
* 加密后的密文
*/
private String encodeBody;
/**
* head中rsa加密后的aes密钥
*/
private String headAESSecurityKey;
}
@@ -0,0 +1,22 @@
package com.itheima.sfbx.dto;
import lombok.Data;
import java.util.Map;
/**
* ResponseDTO
*
* @author: wgl
* @describe: 返回结果对象
* @date: 2022/12/28 10:10
*/
@Data
public class ResponseDTO {
private String msg;
private String code;
private Map data;
}
@@ -0,0 +1,64 @@
package com.itheima.sfbx.notify;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.itheima.sfbx.Validator;
import com.itheima.sfbx.Verifier;
import com.itheima.sfbx.auth.BoleeValidator;
import com.itheima.sfbx.auth.PublicKeyVerifier;
import java.io.IOException;
/**
* @ClassName NotifiyHandler.java
* @Description 通知验证及解析出来
*/
public class NotifiyHandler {
//校验者
private Validator validator;
private static final ObjectMapper objectMapper = new ObjectMapper();
public NotifiyHandler(String publicKey) {
this.validator = new BoleeValidator(new PublicKeyVerifier(publicKey));
}
/**
* 解析通知请求结果
* @param notifyRequest 微信支付通知请求
* @return 微信支付通知报文解密结果@StreamListener(FileSink.FILE_INPUT)
* public void onMessage(@Payload MqMessage message,
* @Header(AmqpHeaders.CHANNEL) Channel channel,
* @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
* String jsonConten = message.getcontent();
* log.info("[onMessage][线程编号:{} 消息内容:{}]", Thread.currentThread().getId(), message);
* FileVO fileVO= JSONObject.parseObject(jsonConten,FileVO.class);
* Boolean responseWrap = fileBusinessFeign.clearFileById(fileVO.getId());
* channel.basicAck(deliveryTag,false);
*
* }
*/
public NotifyResponse parse(NotifyRequest notifyRequest) throws IOException {
// 验签
boolean validate = validator.validateNotify(notifyRequest);
if (!validate){
throw new RuntimeException("验证签名失败");
}
// 解析请求体
return parseBody(notifyRequest.getBody());
}
/**
* 解析请求体
*
* @param body 请求体
* @return 解析结果
* @throws IOException 解析body失败
*/
private NotifyResponse parseBody(String body) throws IOException {
ObjectReader objectReader = objectMapper.reader();
NotifyResponse notifyResponse = objectReader.readValue(body, NotifyResponse.class);
return notifyResponse;
}
}
@@ -0,0 +1,31 @@
package com.itheima.sfbx.notify;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName NotificationRequest.java
* @Description 通知请求对象
*/
@Data
@NoArgsConstructor
public class NotifyRequest {
//时间戳
private String timestamp;
//随机字符串
private String nonce;
//签名
private String signature;
//请求体
private String body;
@Builder
public NotifyRequest(String timestamp, String nonce, String signature, String body) {
this.timestamp = timestamp;
this.nonce = nonce;
this.signature = signature;
this.body = body;
}
}
@@ -0,0 +1,117 @@
package com.itheima.sfbx.notify;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @ClassName NotifyResponse.java
* @Description TODO
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class NotifyResponse {
@JsonProperty("id")
private String id;
@JsonProperty("create_time")
private String createTime;
@JsonProperty("event_type")
private String eventType;
@JsonProperty("resource_type")
private String resourceType;
@JsonProperty("summary")
private String summary;
@JsonProperty("")
private Resource resource;
private String decryptData;
@Override
public String toString() {
return "Notification{" +
"id='" + id + '\'' +
", createTime='" + createTime + '\'' +
", eventType='" + eventType + '\'' +
", resourceType='" + resourceType + '\'' +
", decryptData='" + decryptData + '\'' +
", summary='" + summary + '\'' +
", resource=" + resource +
'}';
}
public String getId() {
return id;
}
public String getCreateTime() {
return createTime;
}
public String getEventType() {
return eventType;
}
public String getDecryptData() {
return decryptData;
}
public String getSummary() {
return summary;
}
public String getResourceType() {
return resourceType;
}
public Resource getResource() {
return resource;
}
public void setDecryptData(String decryptData) {
this.decryptData = decryptData;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class Resource {
@JsonProperty("algorithm")
private String algorithm;
@JsonProperty("ciphertext")
private String ciphertext;
@JsonProperty("associated_data")
private String associatedData;
@JsonProperty("nonce")
private String nonce;
@JsonProperty("original_type")
private String originalType;
public String getAlgorithm() {
return algorithm;
}
public String getCiphertext() {
return ciphertext;
}
public String getAssociatedData() {
return associatedData;
}
public String getNonce() {
return nonce;
}
public String getOriginalType() {
return originalType;
}
@Override
public String toString() {
return "Resource{" +
"algorithm='" + algorithm + '\'' +
", ciphertext='" + ciphertext + '\'' +
", associatedData='" + associatedData + '\'' +
", nonce='" + nonce + '\'' +
", originalType='" + originalType + '\'' +
'}';
}
}
}
@@ -0,0 +1,153 @@
package com.itheima.sfbx.utils;
import com.itheima.sfbx.notify.NotifyRequest;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
/**
* @ClassName ClientUtils.java
* @Description 客户端工具类
*/
public class ClientUtils {
//随机字符串处理
protected static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
//随机函数
protected static final SecureRandom RANDOM = new SecureRandom();
/***
* @description 时间戳创建
* @return: long 返回时间戳
*/
public static long generateTimestamp() {
return System.currentTimeMillis() / 1000;
}
/***
* @description 随机字符串
* @return: java.lang.String 字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[16];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/***
* @description 构建请求信息: 请求方式+请求路径+请求时间戳+随机字符串+请求体
* @param nonce 随机字符串
* @param timestamp 时间戳
* @param request 请求对象
* @return: java.lang.String 请求信息
*/
public static String requestMessage(String nonce, long timestamp, HttpRequestWrapper request) throws IOException {
URI uri = request.getURI();
String canonicalUrl = uri.getRawPath();
if (uri.getQuery() != null) {
canonicalUrl += "?" + uri.getRawQuery();
}
String body = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity(), StandardCharsets.UTF_8);
return request.getRequestLine().getMethod() + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
/***
* @description 检测应答参数
* @param response 应答对象
*/
public static final void validateParameters(CloseableHttpResponse response) {
//返回头中存在的内容:签名字符串,随机字符串,时间戳
String[] headers = {"signature","nonce_str","timestamp"};
Header header = null;
for (String headerName : headers) {
header = response.getFirstHeader(headerName);
if (header == null) {
throw new RuntimeException("验证返回参数"+headerName+"不全");
}
}
//获得最后时间戳,判断应答时间戳到当前时间超过5分钟则认为应答失败
String timestampStr = header.getValue();
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期应答
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
throw new RuntimeException("应答超时");
}
} catch (DateTimeException | NumberFormatException e) {
throw new RuntimeException("应答时间处理异常");
}
}
/***
* @description 构建应答信息
* @param response 应答结果
* @return: java.lang.String 应答信息
*/
public static String responseMessage(CloseableHttpResponse response) throws IOException {
String timestamp = response.getFirstHeader("timestamp").getValue();
String nonce = response.getFirstHeader("nonce_str").getValue();
HttpEntity entity = response.getEntity();
String body = (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";;
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
/***
* @description 构建notify信息
* @param notifyRequest 推送请求结果
* @return: java.lang.String 应答信息
*/
public static String notifyMessage(NotifyRequest notifyRequest) {
String timestamp = notifyRequest.getTimestamp();
String nonce = notifyRequest.getNonce();
String body = notifyRequest.getBody();
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
public static void validateParametersNotify(NotifyRequest notifyRequest) {
//请求中必须存在的内容:签名字符串,随机字符串,时间戳
String signature = notifyRequest.getSignature();
if (signature==null){
throw new RuntimeException("签名字符串为空");
}
String nonce = notifyRequest.getNonce();
if (nonce==null){
throw new RuntimeException("随机字符串为空");
}
String timestamp = notifyRequest.getTimestamp();
if (timestamp==null){
throw new RuntimeException("时间戳为空");
}
//获得最后时间戳,判断应答时间戳到当前时间超过5分钟则认为应答失败
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期应答
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
throw new RuntimeException("应答超时");
}
} catch (DateTimeException | NumberFormatException e) {
throw new RuntimeException("应答时间处理异常");
}
}
}
@@ -0,0 +1,123 @@
package com.itheima.sfbx.utils;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/**
* @ClassName SymmetricCryptoUtil.java
* @Description TODO
*/
public class SecurityUtil {
/**
* 16字节
*/
private static final String ENCODE_KEY = "1234567812345678";
private static final String IV_KEY = "0000000000000000";
public static void main(String[] args) {
String password = "zdm321123.";
String encryptData = encryptFromStringAES(password, Mode.CBC, Padding.ZeroPadding);
System.out.println("AES加密:" + encryptData);
String decryptData = decryptFromStringAES(encryptData, Mode.CBC, Padding.ZeroPadding);
System.out.println("AES解密:" + decryptData);
RSA rsa = new RSA();
encryptData = encryptFromStringRSA(rsa,password);
System.out.println("RSA加密:"+encryptData);
System.out.println("================用于网络传输===========");
decryptData = decryptFromStringRSA(rsa,encryptData);
//解密内容生成字符串
System.out.println("RSA解密:" + decryptData);
}
public static String encryptFromStringAES(String data, Mode mode, Padding padding) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(IV_KEY.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
return aes.encryptBase64(data, StandardCharsets.UTF_8);
}
public static String encryptFromStringAES(String data, Mode mode, Padding padding,String securityKey) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(securityKey.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
return aes.encryptBase64(data, StandardCharsets.UTF_8);
}
public static String encryptFromStringRSA(RSA rsa,String password) {
//byte[]内容加密
byte[] encrypt = rsa.encrypt(StrUtil.bytes(password, CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
//加密后base64处理用于网络传输
return Base64.encodeBase64String(encrypt);
}
public static String encryptFromStringRSAPrivateKey(RSA rsa,String password) {
//byte[]内容加密
byte[] encrypt = rsa.encrypt(StrUtil.bytes(password, CharsetUtil.CHARSET_UTF_8), KeyType.PrivateKey);
//加密后base64处理用于网络传输
return Base64.encodeBase64String(encrypt);
}
public static String decryptFromStringAES(String data, Mode mode, Padding padding) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(IV_KEY.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
byte[] decryptDataBase64 = aes.decrypt(data);
return new String(decryptDataBase64, StandardCharsets.UTF_8);
}
public static String decryptFromStringAES(String data, Mode mode, Padding padding,String aesKey) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(aesKey.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
byte[] decryptDataBase64 = aes.decrypt(data);
return new String(decryptDataBase64, StandardCharsets.UTF_8);
}
public static String decryptFromStringRSA(RSA rsa, String encryptData) {
//对byte数组进行解密
byte[] decrypt = rsa.decrypt(Base64.decodeBase64(encryptData), KeyType.PrivateKey);
return new String(decrypt, StandardCharsets.UTF_8);
}
public static String decryptFromStringRSAPublicKey(RSA rsa, String encryptData) {
//对byte数组进行解密
byte[] decrypt = rsa.decrypt(Base64.decodeBase64(encryptData), KeyType.PublicKey);
return new String(decrypt, StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.sfbx.config.SecurityConfig
@@ -0,0 +1,104 @@
package com.itheima.sfbx;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itheima.sfbx.auth.BoleeEncryptor;
import com.itheima.sfbx.auth.PrivateKeySigner;
import com.itheima.sfbx.auth.PublicKeyVerifier;
import com.itheima.sfbx.constants.BoleeSecurityConstant;
import com.itheima.sfbx.dto.EncodeDTO;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName BoleeHttpClientTest.java
* @Description TODO
*/
public class BoleeHttpClientTest {
private static String privateKeyBase64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAILdGdmrbwdn0tkD5I1RYB/whGEUsAOYprsy/F0cCdRFcUfNT6Q4BcErwcJAmdXPDuSYSbPsImKA0EbLN+pGh/wwlrYtBueItE4zGV2GdMNhAU3P6CquIAm5H58UFeDmVEUnE81laylXjaiaZduXBVTiLgTPVcpSxus70ZJQQirBAgMBAAECgYAg0sBHHn7MxrfWAunyoDSSDkvF5eB4JnO7hIBUAlJc0cYmElMlh3+6AfWpeXaccED2CVSDMnk1Z8XV2+b8dhBpTo3IHG+IQwZvz89bfMcwhhpRT9eXpC5YkG5UHlh11Ftm7byAo7s0HG/JynquxWZDRifWnvrA2bYE2sk6oN2zuQJBAMxlxcDDcSIdsZbXH7zTFJ6c0WEYlsXndRJhT1OfmxzwNitN+8PicbyJh6GfDcM+PR+13rqGZ4x+yGyKSjsp9XMCQQCj5tRLINjRUcNalPrJnvvUMhlJ0yrxsKReX81L1lwDXjbFpcMsy9eZddgdLjGXRgH7K9tYmLGZ6hflRGTcbrH7AkEAt/HDFOYOU1iLsKbrDgCcJt4T5CC/11ykVCU0wZn6ewGGjlRBBhksqDLQ19ePCC1jzrzas9wvJhYXAu81PKdXFwJAUftq4u1aJlFcgtmUG/efBUPN7GRozZ3Kib4nxTBCtBiTEwfX+Xc4r3UHlYj+mykUYptMSyONamxyaWZtgOkJswJBAMLjyUR61F4UPjoyGGg5G4eRTM3fJ1w7ulHYYxlEy5u2UAC/jBtd07wv030lsqPSA7abTdK+6iNM2v0gA7KODKk=";
private static String publicKeyBase64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC3RnZq28HZ9LZA+SNUWAf8IRhFLADmKa7MvxdHAnURXFHzU+kOAXBK8HCQJnVzw7kmEmz7CJigNBGyzfqRof8MJa2LQbniLROMxldhnTDYQFNz+gqriAJuR+fFBXg5lRFJxPNZWspV42ommXblwVU4i4Ez1XKUsbrO9GSUEIqwQIDAQAB";
@Test
public void testTemplate() throws URISyntaxException, IOException {
URIBuilder urlBuilder = new URIBuilder()
.setScheme("http")
.setHost("localhost:8080")
.setPath("/insure");
RequestTemplate requestTemplate = RequestTemplate.builder()
.appId("10001")
.privateKey(privateKeyBase64)
.publicKey(publicKeyBase64)
.uriBuilder(urlBuilder)
.build();
Map<String, Object> params = new HashMap<>();
params.put("name","张三");
params.put("age","18");
params.put("sex","");
Map map = requestTemplate.doRequest(params, Map.class);
System.out.println(map);
}
@Test
public void BoleeHttpClientBuilderTest(){
CloseableHttpClient httpclient = BoleeHttpClientBuilder.create()
.withCredentials("10001", privateKeyBase64)
.withValidator(publicKeyBase64)
.build();
//=============基础配置===================
URIBuilder urlBuilder = new URIBuilder()
.setScheme("http")
.setHost("localhost:8080")
.setPath("/insure");
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(1000)
.setConnectTimeout(1000)
.build();
try {
URI url = urlBuilder.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
EncodeDTO encodeDTO = new EncodeDTO();
JSONObject body = JSONUtil.parseObj(new HashMap<>() {
{
put("age", 19);
}
});
encodeDTO.setBody(body.toString());
if(new BoleeEncryptor(privateKeyBase64).encode(encodeDTO)){
StringEntity stringEntity = new StringEntity(encodeDTO.getEncodeBody(), "utf-8");
httpPost.setEntity(stringEntity);
httpPost.addHeader(BoleeSecurityConstant.HEAD_NAME_BODY_KEY,encodeDTO.getHeadAESSecurityKey());
httpPost.addHeader("Content-Type","application/json;charset=utf-8");
CloseableHttpResponse apiRes = httpclient.execute(httpPost);
HttpEntity entity = apiRes.getEntity();
String content = EntityUtils.toString(entity);
System.out.println("接收到了返回信息:"+content);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}