View Javadoc
1   package com.foxinmy.weixin4j.http.weixin;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.security.*;
7   import java.security.cert.CertificateFactory;
8   import java.security.interfaces.RSAPrivateKey;
9   import java.security.spec.InvalidKeySpecException;
10  import java.security.spec.PKCS8EncodedKeySpec;
11  import java.util.Arrays;
12  
13  import javax.net.ssl.KeyManagerFactory;
14  import javax.net.ssl.SSLContext;
15  import java.security.cert.CertificateException;
16  import java.security.cert.X509Certificate;
17  import java.util.regex.Matcher;
18  import java.util.regex.Pattern;
19  import javax.xml.bind.DatatypeConverter;
20  
21  import com.foxinmy.weixin4j.exception.WeixinException;
22  import com.foxinmy.weixin4j.http.HttpClient;
23  import com.foxinmy.weixin4j.http.HttpClientException;
24  import com.foxinmy.weixin4j.http.HttpMethod;
25  import com.foxinmy.weixin4j.http.HttpParams;
26  import com.foxinmy.weixin4j.http.HttpRequest;
27  import com.foxinmy.weixin4j.http.HttpResponse;
28  import com.foxinmy.weixin4j.http.MimeType;
29  import com.foxinmy.weixin4j.http.URLParameter;
30  import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
31  import com.foxinmy.weixin4j.http.apache.mime.HttpMultipartMode;
32  import com.foxinmy.weixin4j.http.apache.mime.MultipartEntityBuilder;
33  import com.foxinmy.weixin4j.http.entity.FormUrlEntity;
34  import com.foxinmy.weixin4j.http.entity.HttpEntity;
35  import com.foxinmy.weixin4j.http.entity.StringEntity;
36  import com.foxinmy.weixin4j.http.factory.HttpClientFactory;
37  import com.foxinmy.weixin4j.http.message.XmlMessageConverter;
38  import com.foxinmy.weixin4j.logging.InternalLogLevel;
39  import com.foxinmy.weixin4j.logging.InternalLogger;
40  import com.foxinmy.weixin4j.logging.InternalLoggerFactory;
41  import com.foxinmy.weixin4j.util.Consts;
42  import com.foxinmy.weixin4j.util.StringUtil;
43  import org.bouncycastle.jce.provider.BouncyCastleProvider;
44  import static java.util.regex.Pattern.CASE_INSENSITIVE;
45  
46  /**
47   * 负责微信请求的执行
48   *
49   * @className WeixinRequestExecutor
50   * @author jinyu(foxinmy@gmail.com)
51   * @date 2015年8月15日
52   * @since JDK 1.6
53   * @see
54   */
55  public class WeixinRequestExecutor {
56  
57  	protected final InternalLogger logger = InternalLoggerFactory
58  			.getInstance(getClass());
59  
60  	private static final String SUCCESS_CODE = ",0,success,";
61  
62  	private final HttpClient httpClient;
63  
64  	public WeixinRequestExecutor() {
65  		this.httpClient = HttpClientFactory.getInstance();
66  	}
67  
68  	public WeixinRequestExecutor(HttpParams params) {
69  		this.httpClient = HttpClientFactory.getInstance(params);
70  	}
71  
72  	private static final Pattern CERT_PATTERN = Pattern.compile(
73  			"-+BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+" + // Header
74  					"([a-z0-9+/=\\r\\n]+)" +                    // Base64 text
75  					"-+END\\s+.*CERTIFICATE[^-]*-+",            // Footer
76  			CASE_INSENSITIVE);
77  
78  	private static final Pattern KEY_PATTERN = Pattern.compile(
79  			"-+BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+" + // Header
80  					"([a-z0-9+/=\\r\\n]+)" +                       // Base64 text
81  					"-+END\\s+.*PRIVATE\\s+KEY[^-]*-+",            // Footer
82  			CASE_INSENSITIVE);
83  
84  	/**
85  	 * Post方法执行微信请求
86  	 * 
87  	 * @param url
88  	 *            请求URL
89  	 * @param body
90  	 *            参数内容
91  	 * @return 微信响应
92  	 * @throws WeixinException
93  	 */
94  	public WeixinResponse post(String url, String body) throws WeixinException {
95  		HttpEntity entity = new StringEntity(body);
96  		HttpRequest request = new HttpRequest(HttpMethod.POST, url);
97  		request.setEntity(entity);
98  		return doRequest(request);
99  	}
100 
101 	/**
102 	 * Post方法执行微信请求,用于文件上传
103 	 * 
104 	 * @param url
105 	 *            请求URL
106 	 * @param bodyParts
107 	 *            文件内容
108 	 * @return 微信响应
109 	 * @throws WeixinException
110 	 */
111 	public WeixinResponse post(String url, FormBodyPart... bodyParts)
112 			throws WeixinException {
113 		MultipartEntityBuilder builder = MultipartEntityBuilder.create();
114 		for (FormBodyPart bodyPart : bodyParts) {
115 			builder.addPart(bodyPart);
116 		}
117 		HttpRequest request = new HttpRequest(HttpMethod.POST, url);
118 		request.setEntity(builder.setMode(HttpMultipartMode.RFC6532)
119 				.buildEntity());
120 		return doRequest(request);
121 	}
122 
123 	/**
124 	 * Get方法执行微信请求
125 	 * 
126 	 * @param url
127 	 *            请求URL,如:https://api.weixin.qq.com/cgi-bin/token
128 	 * @param parameters
129 	 *            url上的参数,如:new URLParameter("appid",xxxxx)
130 	 * @return 微信响应
131 	 * @throws WeixinException
132 	 */
133 	public WeixinResponse get(String url, URLParameter... parameters)
134 			throws WeixinException {
135 		// always contain the question mark
136 		StringBuilder buf = new StringBuilder(url);
137 		if (parameters != null && parameters.length > 0) {
138 			buf.append("&").append(
139 					FormUrlEntity.formatParameters(Arrays.asList(parameters)));
140 		}
141 		HttpRequest request = new HttpRequest(HttpMethod.GET, buf.toString());
142 		return doRequest(request);
143 	}
144 
145 	/**
146 	 * 执行微信请求
147 	 * 
148 	 * @param request
149 	 *            微信请求
150 	 * @return 微信响应
151 	 * @throws WeixinException
152 	 */
153 	public WeixinResponse doRequest(HttpRequest request) throws WeixinException {
154 		try {
155 			if (logger.isEnabled(InternalLogLevel.DEBUG)) {
156 				logger.debug("weixin request >> "
157 						+ request.getMethod()
158 						+ " "
159 						+ request.getURI().toString()
160 						+ (request.getEntity() instanceof StringEntity ? " >> "
161 								+ ((StringEntity) request.getEntity())
162 										.getContentString() : ""));
163 			}
164 			HttpResponse httpResponse = httpClient.execute(request);
165 			WeixinResponse response = new WeixinResponse(httpResponse);
166 			handleResponse(response);
167 			return response;
168 		} catch (HttpClientException e) {
169 			throw new WeixinException(e);
170 		}
171 	}
172 
173 	/**
174 	 * 响应内容是否为流
175 	 * 
176 	 * @param response
177 	 *            微信响应
178 	 * @return true/false
179 	 */
180 	private boolean hasStreamMimeType(WeixinResponse response) {
181 		MimeType responseMimeType = MimeType.valueOf(response.getHeaders()
182 				.getContentType());
183 		for (MimeType streamMimeType : MimeType.STREAM_MIMETYPES) {
184 			if (streamMimeType.includes(responseMimeType)) {
185 				return true;
186 			}
187 		}
188 		return false;
189 	}
190 
191 	/**
192 	 * handle the weixin response
193 	 * 
194 	 * @param response
195 	 *            微信请求响应
196 	 * @throws WeixinException
197 	 */
198 	protected void handleResponse(WeixinResponse response)
199 			throws WeixinException {
200 		boolean hasStreamMimeType = hasStreamMimeType(response);
201 		if (logger.isEnabled(InternalLogLevel.DEBUG)) {
202 			logger.debug("weixin response << "
203 					+ response.getProtocol()
204 					+ response.getStatus()
205 					+ " << "
206 					+ (hasStreamMimeType ? response.getHeaders()
207 							.getContentType() : response.getAsString()));
208 		}
209 		if (hasStreamMimeType) {
210 			return;
211 		}
212 		ApiResult result = response.getAsResult();
213 		if (!SUCCESS_CODE.contains(String.format(",%s,", result.getReturnCode()
214 				.toLowerCase()))) {
215 			throw new WeixinException(result.getReturnCode(),
216 					result.getReturnMsg());
217 		}
218 		if (XmlMessageConverter.GLOBAL.canConvert(XmlResult.class, response)) {
219 			try {
220 				XmlResult xmlResult = XmlMessageConverter.GLOBAL.convert(
221 						XmlResult.class, response);
222 				// 微信最新的刷脸支付API中已没有返回resultCode,需做非空判断,否则抛异常
223 				if(StringUtil.isNotBlank(xmlResult.getResultCode())) {
224 					if (!SUCCESS_CODE.contains(String.format(",%s,", xmlResult
225 							.getResultCode().toLowerCase()))) {
226 						throw new WeixinException(xmlResult.getErrCode(),
227 								xmlResult.getErrCodeDes());
228 					}
229 				}
230 			} catch (IOException e) {
231 				;
232 			}
233 		}
234 	}
235 
236 	public HttpClient getExecuteClient() {
237 		return httpClient;
238 	}
239 
240 	/**
241 	 * 创建 SSL微信请求对象
242 	 * 
243 	 * @param password
244 	 *            加载密钥
245 	 * @param inputStream
246 	 *            密钥内容
247 	 * @return 微信请求
248 	 * @throws WeixinException
249 	 */
250 	public WeixinRequestExecutor createSSLRequestExecutor(String password,
251 			InputStream inputStream) throws WeixinException {
252 		try {
253 			KeyStore keyStore = KeyStore.getInstance(Consts.PKCS12);
254 			keyStore.load(inputStream, password.toCharArray());
255 			KeyManagerFactory kmf = KeyManagerFactory
256 					.getInstance(Consts.SunX509);
257 			kmf.init(keyStore, password.toCharArray());
258 			SSLContext sslContext = SSLContext.getInstance("TLS");
259 			sslContext.init(kmf.getKeyManagers(), null,
260 					new java.security.SecureRandom());
261 			return createSSLRequestExecutor(sslContext);
262 		} catch (Exception e) {
263 			if (inputStream != null) {
264 				try {
265 					inputStream.close();
266 				} catch (IOException ignore) {
267 				}
268 			}
269 			throw new WeixinException("Key load error", e);
270 		}
271 	}
272 
273 	public WeixinRequestExecutor createSSLRequestExecutor(SSLContext sslContext) {
274 		if (sslContext == null) {
275 			throw new IllegalArgumentException("sslContext must not be empty");
276 		}
277 		HttpParams params = new HttpParams();
278 		params.setSSLContext(sslContext);
279 		return new WeixinRequestExecutor(params);
280 	}
281 
282 	/**
283 	 * 使用PEM格式证书创建SSL微信请求对象
284 	 *
285 	 * @param pemCertificate
286 	 * 			PEM格式证书内容
287 	 * @param pemPrivateKey
288 	 * 			PEM格式证书私钥
289 	 * @return
290 	 */
291 	public WeixinRequestExecutor createSSLRequestExecutor(String password, String pemCertificate, String pemPrivateKey) throws WeixinException{
292 		Security.addProvider(new BouncyCastleProvider());
293 
294 		try {
295 			byte[] certBytes = parseDERFromPEM(pemCertificate);
296 			byte[] keyBytes = parseDERFromPEM(pemPrivateKey);
297 
298 			char[] passwordChars = password.toCharArray();
299 			X509Certificate cert = generateCertificateFromDER(certBytes);
300 			RSAPrivateKey key  = generatePrivateKeyFromDER(keyBytes);
301 
302 			KeyStore keystore = KeyStore.getInstance("JKS");
303 			keystore.load(null);
304 			keystore.setCertificateEntry("cert-alias", cert);
305 			keystore.setKeyEntry("key-alias", key, passwordChars, new X509Certificate[] {cert});
306 
307 			KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
308 			kmf.init(keystore, passwordChars);
309 
310 			SSLContext context = SSLContext.getInstance("TLS");
311 			context.init(kmf.getKeyManagers(), null, new java.security.SecureRandom());
312 
313 			return createSSLRequestExecutor(context);
314 		} catch (Exception e) {
315 			throw new WeixinException("Certificate load error", e);
316 		}
317 
318 	}
319 
320 	private static byte[] parseDERFromPEM(String data) throws KeyStoreException {
321 		Matcher matcher = CERT_PATTERN.matcher(data);
322 		String content = "";
323 		if(!matcher.find()){
324 			matcher = KEY_PATTERN.matcher(data);
325 			if(!matcher.find()){
326 				throw new KeyStoreException("found no private key or certificate from content:"+ data);
327 			}
328 		}
329 		content = matcher.group(1);
330 		return DatatypeConverter.parseBase64Binary(content);
331 	}
332 
333 	private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
334 		PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
335 
336 		KeyFactory factory = KeyFactory.getInstance("RSA");
337 
338 		return (RSAPrivateKey)factory.generatePrivate(spec);
339 	}
340 
341 	protected static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
342 		CertificateFactory factory = CertificateFactory.getInstance("X.509");
343 
344 		return (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(certBytes));
345 	}
346 }