微信支付相关-Spring RestTemplate和javax SSLContext
来源:互联网 发布:热血屠龙灵兽进阶数据 编辑:程序博客网 时间:2024/06/05 16:59
环境: java8+Spring
起因: 4月写微信支付的后台中间接口时,申请退款请求需要带上商户证书。微信官方给的java demo用的是apache的HttpClient,但因为实际server用的是Spring…所以就考虑怎么在Spring的RestTemplate里引入商户凭证,结果发现牵扯出来很多东西…这里整理了一些。
思路:
RestTemplate部分:
Spring用的不久,稍微仔细点地看了下代码、才发现RestTemplate诚如其名就是封装好的模板。它众多的xxForObject、xxForEntity方法,内部流程其实很简明:/* RestTemplate#doExecute */// ClientHttpRequestFactory#createRequest,生成一个具体请求实例ClientHttpRequest request = createRequest(url, method);if (requestCallback != null) { // 请求headers和body处理,用到了HttpMessageConverter requestCallback.doWithRequest(request); }//ClientHttpRequest#execute 执行请求response = request.execute(); // response error处理handleResponse(url, method, response);
于是只看ClientHttpRequestFactory,查找下它的实现类:
简单检查了下import、只有基于HttpComponents、Netty和OkHttp的涉及到了ssl。几个request factory都可以通过constuctor注入相应client实例,所以就回到了HttpComponents/Netty/OkHttp怎么加ssl……然后矛头都指向了一个class:SSLContext。证书相关:
因为之前读书少在看微信demo时发现通篇的双向认证、公钥私钥、KeyStore、PKCS12……几乎都是陌生词汇。网络安全相关到目前才了解皮毛……还好只是写个退款接口、搞起几个相关的概念就写完了:首先必须先了解https签名认证流程。SO上看到推荐Wikipedia Public-key cryptography里面的Postal analogies(邮局比喻)、确实写的好。其他就自行百度google了。这里记录的只有一个点就是证书和公钥的区别:按Wikipedia的Public key certificate第一句就是“数字证书是用于证明公钥所有者身份的电子文档”、按SE的这个问题“X.509证书至少包含1)公钥和2)公钥所有者信息”,一开始没搞清还是很影响理解的……
然后直接回来看SSLContext,简单搜下例程知道它是先getInstance然后init, getInstance没有深入看,总之先按demo用
TLSv1
协议;init参数有3个,均可为null、null时用系统默认:public final void init(KeyManager[] var1, TrustManager[] var2, SecureRandom var3) throws KeyManagementException
KeyManager
管理本地使用者的密钥证书信息,微信商户凭证就是由此引入;TrustManager
管理受信任的服务方的信息。这些信息可以每次向权威证书认证机构查询、也可以自己看准了直接导入(比如12306让干的)。微信demo关于rootca.pem的说明文档提到:“某些环境和工具已经内置了若干权威机构的根证书,无需引用该证书也可以正常进行验证,这里提供给您在未内置所必须根证书的环境中载入使用“,微信或腾讯必然在某家CA认证过,Java的话有默认的CA列表(cacerts文件,jdk目录下,用.\bin\keytool.exe -list -keystore .\jre\lib\security\cacerts
查看)。所以这个TrustManager可以直接设成null用系统默认的(就是demo里写的那样)。SecureRandom
应该是https双向认证成功后、对称加密时用的随机数生成器,这个也可以用默认不用考虑。微信给的商户证书是文件,要读取成程序运行时的数据、所以就用到了FileInputStream和KeyStore。
看javadoc,KeyStore和KeyStoreSpi作用是统一封装存放了不同算法的证书密钥,
比如读前面的cacerts是这样:
读微信的apiclient_cert.p12文件时是这样:
具体值的含义和各种KeyStore的区别确实一下子搞不清、因为暂时也用不到就先放着了……
最后通过KeyManagerFactory的init方法,将需要的商户凭证抽取出来:
微信商户证书文件apiclient_cert.p12的格式为PKCS12,按(还是)Wikipedia说的”It is commonly used to bundle a private key with its X.509 certificate”,上图debug信息、和demo给的“导出的apiclient_cert.pem(证书文件)和apiclient_key.pem(私钥文件)”验证了这一说法。不过demo文档还提到“服务器验证客户端的时候通过客户端证书和签名(既:apiclient_cert.p12 或者 apiclient_cert.pem和apiclient_key.pem)”,并不是很确定为什么这样说。
综上,一个非常简单的test main:
语言: java
包依赖: 如图
说明:3个RestTemplate的factory配置,调试学习用
代码:
import io.netty.handler.ssl.SslContext;import io.netty.handler.ssl.SslContextBuilder;import okhttp3.OkHttpClient;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.ssl.SSLContexts;import org.springframework.beans.factory.DisposableBean;import org.springframework.http.ResponseEntity;import org.springframework.http.client.ClientHttpRequestFactory;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.http.client.Netty4ClientHttpRequestFactory;import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;import org.springframework.http.converter.StringHttpMessageConverter;import org.springframework.web.client.RestTemplate;import javax.net.ssl.*;import java.io.FileInputStream;import java.nio.charset.StandardCharsets;import java.security.KeyStore;import java.security.SecureRandom;public class RestTemplateExample { private static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund"; public static void main(String[] args) throws Exception { final String certFile = args[0]; // cert file location final String passwd = args[1]; // mch_id KeyStore keyStore = loadFrom("PKCS12", certFile, passwd); // httpComponent ClientHttpRequestFactory factory = createHttpComponentFactory(keyStore, passwd); testGet(factory); // okhttp factory = createOkHttp3Factory(keyStore, passwd); testGet(factory); // netty factory = createNettyFactory(keyStore, passwd); testGet(factory); ((DisposableBean) factory).destroy(); System.out.println("end"); } private static void testGet(ClientHttpRequestFactory factory) { RestTemplate restTemplate = new RestTemplate(factory); System.out.println("using " + restTemplate.getRequestFactory().getClass()); restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); ResponseEntity<String> getRes = restTemplate.getForEntity(REFUND_URL, String.class); System.out.println(getRes.getBody()); } private static KeyStore loadFrom(String type, String fileName, String passwd) throws Exception { KeyStore keyStore = KeyStore.getInstance(type); try (FileInputStream fileIn = new FileInputStream(fileName)) { keyStore.load(fileIn, passwd.toCharArray()); } System.out.println("keystore entries: " + keyStore.size()); return keyStore; } private static ClientHttpRequestFactory createOkHttp3Factory(KeyStore keyStore, String passwd) throws Exception { KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, passwd.toCharArray()); SSLContext context = SSLContext.getInstance("TLSV1"); context.init(keyManagerFactory.getKeyManagers(), null, null); OkHttpClient okHttpClient = new OkHttpClient.Builder() .sslSocketFactory(context.getSocketFactory(), getDefaultX509TrustManager()) .build(); return new OkHttp3ClientHttpRequestFactory(okHttpClient); } /** * @see OkHttpClient.Builder#sslSocketFactory(SSLSocketFactory) * @see OkHttpClient.Builder#sslSocketFactory(SSLSocketFactory, X509TrustManager) * @see sun.security.ssl.SSLContextImpl#engineInit(KeyManager[], TrustManager[], SecureRandom) */ private static X509TrustManager getDefaultX509TrustManager() throws Exception { TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); factory.init((KeyStore) null); return (X509TrustManager) factory.getTrustManagers()[0]; } /** * @see <a href="https://hc.apache.org/httpcomponents-client-ga/httpclient/examples/org/apache/http/examples/client/ClientCustomSSL.java"> * HttpClient custom ssl example</a> */ private static ClientHttpRequestFactory createHttpComponentFactory(KeyStore keyStore, String passwd) throws Exception { SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, passwd.toCharArray()).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf).build(); return new HttpComponentsClientHttpRequestFactory(httpclient); } private static ClientHttpRequestFactory createNettyFactory(KeyStore keyStore, String passwd) throws Exception { SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, passwd.toCharArray()); SslContext sslContext = sslContextBuilder.keyManager(keyManagerFactory).build(); Netty4ClientHttpRequestFactory factory = new Netty4ClientHttpRequestFactory(); factory.setSslContext(sslContext); return factory; }}
上面写不下的注释:
1. OkHttp部分,按它javadoc的意思,它需要一个X509TrustManager来处理cert chain,虽然SSLSocketFactory的实现类里就包着一个、但因为没有public get方法、要拿只能靠反射;为了不用反射使源码变得难看,就只好请开发者在client端调用时传一个进来;即使这样也还是很难看、且自行导入的CA列表也可能不安全、所以javadoc里也不建议这么做……
2. HttpClient包下不少ssl相关的class都被deprecate了,参考的apache官方示例(就是微信例程用法…)稍微改了下。
3. Netty factory会按url重用BootStrap和新建Channel连接,但EventLoopGroup线程池可以通用;实际用Spring的话框架会自动搜索和关闭。
- 微信支付相关-Spring RestTemplate和javax SSLContext
- 微信支付相关
- 微信支付和微信认证的相关介绍
- 微信支付宝扫码支付相关接口
- iOS微信支付相关~~
- 微信支付相关问题
- 微信支付和支付宝支付
- 支付宝支付和微信支付
- 微信支付和支付宝支付
- java后端配合app前端的支付宝和微信支付(逻辑相关代码)
- 关于支付(支付宝和微信)
- 支付宝和微信支付封装
- iOS支付宝和微信支付
- 微信和支付宝支付实战
- 微信支付和连连支付
- android微信和支付宝支付
- 微信和支付宝支付开发
- Android 支付宝和微信支付
- 开启前端开发
- Mysql你必须知道的查询语句
- Allowed memory size of 149946368 bytes exhausted (tried to allocate 32640 bytes)
- MyEclipse2016stable1.0安装/crack教程
- 值得推荐的C/C++框架和库
- 微信支付相关-Spring RestTemplate和javax SSLContext
- 算法--选择排序
- java WebSocketClient重连机制
- Socket编程问题
- quartz定时任务
- 排序算法C++ && Python实现---直接插入排序
- 取消会议日历
- Java web过滤器验证登录(避免未经登录进入主页)
- 机器学习中特征选择概述