最近在对接易宝支付,但是易宝支付的文档指定使用他们的SDK进行所有的操作。但一般的系统,相关参数为了安全考虑,都是自己从配置中心或者应用中心获取的。所以,我们可以继承他们的 AbstractClient 类,模仿他们的RequestClient3 类,自己去实现一下,然后自行配置加入日志组件,这样,可以更准确的定位问题和看到报文等信息。以下是,我自己实现的类。
关于如何使用:
还是使用他们的YeeRequest请求类,初始化类的时候,把AppKey和分配的私钥初始化进去,即可。
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.yeepay.g3.sdk.yop.client.*;
import com.yeepay.g3.sdk.yop.config.AppSdkConfig;
import com.yeepay.g3.sdk.yop.encrypt.CertTypeEnum;
import com.yeepay.g3.sdk.yop.encrypt.DigestAlgEnum;
import com.yeepay.g3.sdk.yop.encrypt.DigitalSignatureDTO;
import com.yeepay.g3.sdk.yop.exception.YopClientException;
import com.yeepay.g3.sdk.yop.http.Headers;
import com.yeepay.g3.sdk.yop.http.HttpUtils;
import com.yeepay.g3.sdk.yop.unmarshaller.JacksonJsonMarshaller;
import com.yeepay.g3.sdk.yop.utils.*;
import com.yeepay.g3.sdk.yop.utils.checksum.CRC64Utils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
public class YeePayRequestClient extends AbstractClient {
private static final Logger logger = LoggerFactory.getLogger(YeePayRequestClient.class);
private static CloseableHttpClient httpClient;
private static final Set<String> defaultHeadersToSign = Sets.newHashSet();
private static final Joiner headerJoiner = Joiner.on('\n');
private static final Joiner signedHeaderStringJoiner = Joiner.on(';');
private static final String EXPIRED_SECONDS = "1800";
static {
// TODO: 2018/11/8 以下两行为请求参数配置
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(InternalConfig.READ_TIMEOUT)
.setConnectTimeout(InternalConfig.CONNECT_TIMEOUT).build();
httpClient = HttpClientBuilder.create().setMaxConnTotal(InternalConfig.MAX_CONN_TOTAL)
.setMaxConnPerRoute(InternalConfig.MAX_CONN_PER_ROUTE)
.setSSLSocketFactory(InternalConfig.TRUST_ALL_CERTS ? getTrustedAllSSLConnectionSocketFactory() : null)
.setDefaultRequestConfig(requestConfig).evictExpiredConnections()
.evictIdleConnections(5000L, TimeUnit.MILLISECONDS).setRetryHandler(new YopHttpRequestRetryHandler()).build();
}
public static YopResponse postRsa(String apiUri, YopRequest request) throws IOException {
String contentUrl = richRequest(apiUri, request);
sign(apiUri, request);
RequestBuilder requestBuilder = RequestBuilder.post().setUri(contentUrl);
for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, String> entry : request.getParams().entries()) {
requestBuilder.addParameter(entry.getKey(), URLEncoder.encode(entry.getValue(), YopConstants.ENCODING));
}
HttpUriRequest httpPost = requestBuilder.build();
YopResponse response = fetchContentByApacheHttpClient(httpPost);
handleRsaResult(response, request.getAppSdkConfig());
return response;
}
private static void sign(String apiUri, YopRequest request) {
String appKey = request.getAppSdkConfig().getAppKey();
String timestamp = DateUtils.formatCompressedIso8601Timestamp(System.currentTimeMillis());
Map<String, String> headers = request.getHeaders();
if (!headers.containsKey(Headers.YOP_REQUEST_ID)) {
headers.put(Headers.YOP_REQUEST_ID, getUUID());
headers.put(Headers.YOP_SESSION_ID, SESSION_ID);
}
headers.put(Headers.YOP_DATE, timestamp);
if (request.hasFiles()) {
try {
request.addHeader(Headers.YOP_HASH_CRC64ECMA, CRC64Utils.calculateMultiPartFileCrc64ecma(request.getMultipartFiles()));
} catch (IOException ex) {
logger.error("IOException occurred when generate crc64ecma.", ex);
throw new YopClientException("IOException occurred when generate crc64ecma.", ex);
}
}
String authString = InternalConfig.PROTOCOL_VERSION + "/" + appKey + "/" + timestamp + "/" + EXPIRED_SECONDS;
Set<String> headersToSignSet = new HashSet<String>();
headersToSignSet.add(Headers.YOP_REQUEST_ID);
headersToSignSet.add(Headers.YOP_DATE);
headers.put(Headers.YOP_APP_KEY, appKey);
headersToSignSet.add(Headers.YOP_APP_KEY);
String canonicalURI = HttpUtils.getCanonicalURIPath(apiUri);
String canonicalQueryString = HttpUtils.getCanonicalQueryString(request.getParams(), true);
SortedMap<String, String> headersToSign = getHeadersToSign(headers, headersToSignSet);
String canonicalHeader = getCanonicalHeaders(headersToSign);
String signedHeaders = "";
if (headersToSignSet != null) {
signedHeaders = signedHeaderStringJoiner.join(headersToSign.keySet());
signedHeaders = signedHeaders.trim().toLowerCase();
}
String canonicalRequest = authString + "\n" + "POST" + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeader;
PrivateKey isvPrivateKey;
if (StringUtils.length(request.getSecretKey()) > 128) {
try {
isvPrivateKey = RSAKeyUtils.string2PrivateKey(request.getSecretKey());
} catch (NoSuchAlgorithmException e) {
throw Exceptions.unchecked(e);
} catch (InvalidKeySpecException e) {
throw Exceptions.unchecked(e);
}
} else {
isvPrivateKey = request.getAppSdkConfig().getDefaultIsvPrivateKey();
}
if (null == isvPrivateKey) {
throw new YopClientException("Can't init ISV private key!");
}
DigitalSignatureDTO digitalSignatureDTO = new DigitalSignatureDTO();
digitalSignatureDTO.setPlainText(canonicalRequest);
digitalSignatureDTO.setCertType(CertTypeEnum.RSA2048);
digitalSignatureDTO.setDigestAlg(DigestAlgEnum.SHA256);
digitalSignatureDTO = DigitalEnvelopeUtils.sign(digitalSignatureDTO, isvPrivateKey);
if (logger.isDebugEnabled()) {
logger.debug("canonicalRequest:" + canonicalRequest);
logger.debug("signature:" + digitalSignatureDTO.getSignature());
}
headers.put(Headers.AUTHORIZATION, "YOP-RSA2048-SHA256 " + InternalConfig.PROTOCOL_VERSION + "/" + appKey + "/" + timestamp + "/" + EXPIRED_SECONDS + "/" + signedHeaders + "/" + digitalSignatureDTO.getSignature());
}
/**
* 对业务结果签名进行校验
*/
public static boolean verifySignature(String result, String expectedSign, AppSdkConfig appSdkConfig) {
String trimmedBizResult = result.replaceAll("[ \t\n]", "");
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.trimToEmpty(trimmedBizResult));
DigitalSignatureDTO digitalSignatureDTO = new DigitalSignatureDTO();
digitalSignatureDTO.setCertType(CertTypeEnum.RSA2048);
digitalSignatureDTO.setSignature(expectedSign);
digitalSignatureDTO.setPlainText(sb.toString());
try {
DigitalEnvelopeUtils.verify(digitalSignatureDTO, appSdkConfig.getDefaultYopPublicKey());
} catch (Exception e) {
logger.error("error verify sign", e);
return false;
}
return true;
}
private static String getCanonicalHeaders(SortedMap<String, String> headers) {
if (headers.isEmpty()) {
return "";
}
List<String> headerStrings = Lists.newArrayList();
for (Map.Entry<String, String> entry : headers.entrySet()) {
String key = entry.getKey();
if (key == null) {
continue;
}
String value = entry.getValue();
if (value == null) {
value = "";
}
headerStrings.add(HttpUtils.normalize(key.trim().toLowerCase()) + ':' + HttpUtils.normalize(value.trim()));
}
Collections.sort(headerStrings);
return headerJoiner.join(headerStrings);
}
private static SortedMap<String, String> getHeadersToSign(Map<String, String> headers, Set<String> headersToSign) {
SortedMap<String, String> ret = Maps.newTreeMap();
if (headersToSign != null) {
Set<String> tempSet = Sets.newHashSet();
for (String header : headersToSign) {
tempSet.add(header.trim().toLowerCase());
}
headersToSign = tempSet;
}
for (Map.Entry<String, String> entry : headers.entrySet()) {
String key = entry.getKey();
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
if ((headersToSign == null && isDefaultHeaderToSign(key))
|| (headersToSign != null && headersToSign.contains(key.toLowerCase())
&& !Headers.AUTHORIZATION.equalsIgnoreCase(key))) {
ret.put(key, entry.getValue());
}
}
}
return ret;
}
private static void handleRsaResult(YopResponse response, AppSdkConfig appSdkConfig) {
String stringResult = response.getStringResult();
if (StringUtils.isNotBlank(stringResult)) {
response.setResult(JacksonJsonMarshaller.unmarshal(stringResult, Object.class));
}
String sign = response.getSign();
if (StringUtils.isNotBlank(sign)) {
response.setValidSign(verifySignature(stringResult, sign, appSdkConfig));
}
}
private static boolean isDefaultHeaderToSign(String header) {
header = header.trim().toLowerCase();
return header.startsWith(Headers.YOP_PREFIX) || defaultHeadersToSign.contains(header);
}
protected static YopResponse fetchContentByApacheHttpClient(HttpUriRequest request) throws IOException {
CloseableHttpResponse remoteResponse = httpClient.execute(request);
Object var2 = null;
YopResponse var7;
try {
int statusCode = remoteResponse.getStatusLine().getStatusCode();
if (statusCode >= 400) {
throw new YopClientException(Integer.toString(statusCode));
}
String content = EntityUtils.toString(remoteResponse.getEntity());
YopResponse response = (YopResponse)JacksonJsonMarshaller.unmarshal(content, YopResponse.class);
Header requestIdHeader = remoteResponse.getFirstHeader("x-yop-request-id");
if (null != requestIdHeader) {
response.setRequestId(requestIdHeader.getValue());
}
var7 = response;
} finally {
HttpClientUtils.closeQuietly(remoteResponse);
}
return var7;
}
private static SSLConnectionSocketFactory getTrustedAllSSLConnectionSocketFactory() {
logger.warn("[yop-sdk]已设置信任所有证书。仅供内测使用,请勿在生产环境配置。");
SSLConnectionSocketFactory sslConnectionSocketFactory = null;
try {
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial((KeyStore)null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
});
sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build());
} catch (Exception var2) {
logger.error("error when get trust-all-certs request factory,will return normal request factory instead", var2);
}
return sslConnectionSocketFactory;
}
}
0