com.gitlab.summercattle.addons.wechat.officialaccounts.service.OfficialAccountsReceiveService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cattle-addons-wechat-starter Show documentation
Show all versions of cattle-addons-wechat-starter Show documentation
Cattle Framework Addons WeChat Component
The newest version!
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gitlab.summercattle.addons.wechat.officialaccounts.service;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.Set;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gitlab.summercattle.addons.wechat.memory.RcvMessageIdMemoryService;
import com.gitlab.summercattle.addons.wechat.officialaccounts.configure.OfficialAccountsApplication;
import com.gitlab.summercattle.addons.wechat.officialaccounts.configure.OfficialAccountsConfigProperties;
import com.gitlab.summercattle.addons.wechat.officialaccounts.event.AbstractReceiveEvent;
import com.gitlab.summercattle.addons.wechat.officialaccounts.event.RcvEvent;
import com.gitlab.summercattle.addons.wechat.officialaccounts.event.ReceiveEventType;
import com.gitlab.summercattle.addons.wechat.officialaccounts.message.receive.AbstractReceiveMessage;
import com.gitlab.summercattle.addons.wechat.officialaccounts.message.receive.RcvMessage;
import com.gitlab.summercattle.addons.wechat.officialaccounts.message.receive.ReceiveMessageType;
import com.gitlab.summercattle.addons.wechat.officialaccounts.message.send.AbstractSendMessage;
import com.gitlab.summercattle.addons.wechat.officialaccounts.security.ByteGroup;
import com.gitlab.summercattle.addons.wechat.officialaccounts.security.Pkcs7Encoder;
import com.gitlab.summercattle.commons.aop.utils.ClassUtils;
import com.gitlab.summercattle.commons.exception.CommonException;
import com.gitlab.summercattle.commons.exception.ExceptionWrapUtils;
import com.gitlab.summercattle.commons.utils.auxiliary.Dom4jUtils;
import com.gitlab.summercattle.commons.utils.reflect.ReflectUtils;
/**
* 公众号接收服务
* @author orange
*
*/
@Service
public class OfficialAccountsReceiveService {
private static final Logger logger = LoggerFactory.getLogger(OfficialAccountsReceiveService.class);
private static final String ENCRYPT_TYPE_AES = "aes";
private static final int NETWORK_BYTES_ORDER_LENGTH = 4;
@Autowired
private OfficialAccountsConfigProperties officialAccountsConfigProperties;
@Autowired
private RcvMessageIdMemoryService rcvMessageIdMemoryService;
public boolean validateSignature(OfficialAccountsApplication officialAccountsApplication, String signature, String timestamp, String nonce)
throws CommonException {
return validateSignatureData(signature, officialAccountsApplication.getToken(), timestamp, nonce);
}
private boolean validateSignatureData(String signature, String token, String... datas) throws CommonException {
if (StringUtils.isBlank(signature)) {
throw new CommonException("需验证的签名为空");
}
if (StringUtils.isBlank(token)) {
throw new CommonException("令牌为空");
}
if (datas == null || datas.length == 0) {
throw new CommonException("数据为空");
}
String lSignature = signature(token, datas);
return lSignature.equals(signature.toLowerCase());
}
private String signature(String token, String... datas) {
String[] arrays = new String[datas.length + 1];
arrays[0] = token;
for (int i = 0; i < datas.length; i++) {
arrays[i + 1] = datas[i];
}
Arrays.sort(arrays);
StringBuffer sb = new StringBuffer();
for (int i = 0; i < arrays.length; i++) {
sb.append(arrays[i]);
}
return Hex.encodeHexString(DigestUtils.sha1(sb.toString()), true);
}
public String receive(OfficialAccountsApplication officialAccountsApplication, String encryptType, String openid, String msgSignature,
String timestamp, String nonce, String xml) throws CommonException {
Element receiveElement;
if (StringUtils.isNotBlank(encryptType)) {
if (encryptType.equals(ENCRYPT_TYPE_AES)) {
try {
Element encryptElement = Dom4jUtils.getDocument(xml).getRootElement();
String toUserName = encryptElement.elementText("ToUserName");
String encrypt = encryptElement.elementText("Encrypt");
if (validateSignatureData(msgSignature, officialAccountsApplication.getToken(), timestamp, nonce, encrypt)) {
byte[] key = Base64.getDecoder().decode(officialAccountsApplication.getMessageAesKey() + "=");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(key, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
byte[] datas = cipher.doFinal(Base64.getDecoder().decode(encrypt));
byte[] bytes = Pkcs7Encoder.decode(datas);
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int xmlLength = recoverNetworkBytesOrder(networkOrder);
String fromApiId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), "UTF-8");
String xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), "UTF-8");
if (!officialAccountsApplication.getAppId().equals(fromApiId)) {
throw new CommonException("微信公众服务号Post消息有异常");
}
receiveElement = Dom4jUtils.getDocument(xmlContent).getRootElement();
String lToUserName = receiveElement.elementText("ToUserName");
if (!StringUtils.equals(toUserName, lToUserName)) {
throw new CommonException("微信公众服务号Post消息有异常");
}
}
else {
throw new CommonException("消息体的签名验证无效");
}
}
catch (InvalidKeyException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException
| InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException e) {
throw ExceptionWrapUtils.wrap(e);
}
}
else {
throw new CommonException("加密类型'" + encryptType + "'无效");
}
}
else {
receiveElement = Dom4jUtils.getDocument(xml).getRootElement();
}
AbstractSendMessage abstractSendMessage = processReceive(receiveElement, openid);
if (abstractSendMessage != null) {
return processSend(abstractSendMessage, officialAccountsApplication, encryptType);
}
return null;
}
private String processSend(AbstractSendMessage abstractSendMessage, OfficialAccountsApplication officialAccountsApplication, String encryptType)
throws CommonException {
String result = abstractSendMessage.toStringData();
logger.debug("回复微信公众服务号消息:" + result);
if (StringUtils.isNotBlank(encryptType)) {
if (encryptType.equals(ENCRYPT_TYPE_AES)) {
byte[] key = Base64.getDecoder().decode(officialAccountsApplication.getMessageAesKey() + "=");
String randomString = com.gitlab.summercattle.commons.utils.auxiliary.StringUtils.getRandomString(16);
ByteGroup byteCollector = new ByteGroup();
byteCollector.addBytes(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(randomString));
byte[] textBytes = org.apache.commons.codec.binary.StringUtils.getBytesUtf8(result);
byteCollector.addBytes(networkBytesOrder(textBytes.length));
byteCollector.addBytes(textBytes);
byteCollector.addBytes(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(officialAccountsApplication.getAppId()));
byte[] padBytes = Pkcs7Encoder.encode(byteCollector.size());
byteCollector.addBytes(padBytes);
byte[] unencrypted = byteCollector.toBytes();
try {
// 设置加密模式为AES的CBC模式
Cipher lCipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec lkeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec liv = new IvParameterSpec(key, 0, 16);
lCipher.init(Cipher.ENCRYPT_MODE, lkeySpec, liv);
// 加密
byte[] encrypted = lCipher.doFinal(unencrypted);
// 使用BASE64对加密后的字符串进行编码
String base64Encrypted = Base64.getEncoder().encodeToString(encrypted);
long returnTime = (new Date()).getTime() / 1000;
String signature = signature(officialAccountsApplication.getToken(), String.valueOf(returnTime), randomString, base64Encrypted);
return generate(base64Encrypted, signature, String.valueOf(returnTime), randomString);
}
catch (IllegalBlockSizeException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException
| NoSuchAlgorithmException | NoSuchPaddingException e) {
throw ExceptionWrapUtils.wrap(e);
}
}
else {
throw new CommonException("加密类型\"" + encryptType + "\"无效");
}
}
return result;
}
private AbstractSendMessage processReceive(Element receiveElement, String openid) throws CommonException {
AbstractSendMessage abstractSendMessage = null;
String fromUserName = receiveElement.elementText("FromUserName");
if (!openid.equals(fromUserName)) {
throw new CommonException("微信公众服务号Post消息有异常");
}
String toUserName = receiveElement.elementText("ToUserName");
String msgType = receiveElement.elementText("MsgType");
String strCreateTime = receiveElement.elementText("CreateTime");
Date createTime = new Date(NumberUtils.toLong(strCreateTime) * 1000);
ReceiveMessageType rcvMessageType = ReceiveMessageType.parse(msgType);
if (rcvMessageType != null) {
int receiveMessageTimeout = officialAccountsConfigProperties.getReceiveMessageTimeout();
if (rcvMessageType == ReceiveMessageType.Event) {
if (rcvMessageIdMemoryService.setByUserName(fromUserName, createTime, receiveMessageTimeout)) {
String lEventType = receiveElement.elementText("Event");
ReceiveEventType rcvEventType = ReceiveEventType.parse(lEventType);
if (rcvEventType != null) {
Set> rcvEventClasses = ClassUtils.getSubTypesOf(AbstractReceiveEvent.class);
AbstractReceiveEvent event = null;
for (Class< ? extends AbstractReceiveEvent> lEvent : rcvEventClasses) {
RcvEvent rcvEvent = ReflectUtils.getAnnotation(lEvent, RcvEvent.class);
if (null != rcvEvent && rcvEvent.value() == rcvEventType) {
event = ClassUtils.instance(lEvent);
break;
}
}
if (event != null) {
event.parse(fromUserName, toUserName, createTime, receiveElement);
abstractSendMessage = event.receive();
}
}
else {
logger.debug("微信公众服务号Post未处理的消息:" + Dom4jUtils.asXmlWithoutPretty(receiveElement, "UTF-8"));
}
}
}
else {
String msgId = receiveElement.elementText("MsgId");
if (rcvMessageIdMemoryService.setByMsgId(msgId, createTime, receiveMessageTimeout)) {
Set> rcvMessageClasses = ClassUtils.getSubTypesOf(AbstractReceiveMessage.class);
AbstractReceiveMessage message = null;
for (Class< ? extends AbstractReceiveMessage> abstractReceiveMessage : rcvMessageClasses) {
RcvMessage rcvMessage = ReflectUtils.getAnnotation(abstractReceiveMessage, RcvMessage.class);
if (null != rcvMessage && rcvMessage.value() == rcvMessageType) {
message = ClassUtils.instance(abstractReceiveMessage);
break;
}
}
if (message != null) {
message.parse(fromUserName, toUserName, createTime, receiveElement);
abstractSendMessage = message.receive();
}
}
}
}
else {
logger.debug("微信公众服务号Post未处理的消息:" + Dom4jUtils.asXmlWithoutPretty(receiveElement, "UTF-8"));
}
return abstractSendMessage;
}
private int recoverNetworkBytesOrder(byte[] orderBytes) {
int sourceNumber = 0;
for (int i = 0; i < NETWORK_BYTES_ORDER_LENGTH; i++) {
sourceNumber <<= 8;
sourceNumber |= orderBytes[i] & 0xff;
}
return sourceNumber;
}
private byte[] networkBytesOrder(int sourceNumber) {
byte[] orderBytes = new byte[NETWORK_BYTES_ORDER_LENGTH];
orderBytes[NETWORK_BYTES_ORDER_LENGTH - 1] = (byte) (sourceNumber & 0xFF);
orderBytes[NETWORK_BYTES_ORDER_LENGTH - 2] = (byte) (sourceNumber >> 8 & 0xFF);
orderBytes[NETWORK_BYTES_ORDER_LENGTH - 3] = (byte) (sourceNumber >> 16 & 0xFF);
orderBytes[NETWORK_BYTES_ORDER_LENGTH - 4] = (byte) (sourceNumber >> 24 & 0xFF);
return orderBytes;
}
private String generate(String encrypt, String signature, String timestamp, String nonce) throws CommonException {
Document document = DocumentHelper.createDocument();
Element rootElement = document.addElement("xml");
rootElement.addElement("Encrypt").add(DocumentHelper.createCDATA(encrypt));
rootElement.addElement("MsgSignature").add(DocumentHelper.createCDATA(signature));
rootElement.addElement("TimeStamp").setText(timestamp);
rootElement.addElement("Nonce").add(DocumentHelper.createCDATA(nonce));
return Dom4jUtils.asXmlWithoutPretty(rootElement, "UTF-8");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy