微信小程序入门10-微信公众号token验证失败
为了更好的运营公众号,微信官方支持用户自定义实现公众号功能,这里第一步就是配置服务器回调域名,如下图:
如果是SpringBoot项目,我们会写一个如下的Controller类
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Controller
@Slf4j
public class WxGzhEventController1 {private final static String gzhToken = "公众号那边自定义的令牌(Token)";/**** 微信服务器触发get请求用于检测签名* @return*/@SneakyThrows@GetMapping(value = "/callback/handleWxCheckSignature")@ResponseBodypublic String handleWxCheckSignature(HttpServletRequest request, HttpServletResponse response) {//微信加密签名String signature = request.getParameter("signature");//时间戳String timestamp = request.getParameter("timestamp");//随机数String nonce = request.getParameter("nonce");//随机字符串String echostr = request.getParameter("echostr");//接入验证if (WXBizMsgCrypt.checkSignature(signature, timestamp, nonce, gzhToken)) {log.info("微信公众号校验完成echostr:[{}]", echostr);return echostr ;}throw new RuntimeException("解析签名发生异常");}
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.util.crypto.PKCS7Encoder;
import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.Arrays;/*** 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).* <ol> * <li>第三方回复加密消息给公众平台</li> * <li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>* </ol>* 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案* <ol>* <li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: ** http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>* <li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>* <li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>* <li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>** </ol>*/
@Slf4j
public class WXBizMsgCrypt {static Charset CHARSET = Charset.forName("utf-8");Base64 base64 = new Base64();byte[] aesKey;String token;String appId;/*** 构造函数** @param token 公众平台上,开发者设置的token* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey* @param appId 公众平台appid* @throws WxAesException 执行失败,请查看该异常的错误码和具体的错误信息*/public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throws WxAesException {if (encodingAesKey.length() != 43) {throw new WxAesException(WxAesException.IllegalAesKey);}this.token = token;this.appId = appId;aesKey = Base64.decodeBase64(encodingAesKey + "=");}// 还原4个字节的网络字节序int recoverNetworkBytesOrder(byte[] orderBytes) {int sourceNumber = 0;for (int i = 0; i < 4; i++) {sourceNumber <<= 8;sourceNumber |= orderBytes[i] & 0xff;}return sourceNumber;}/*** 对密文进行解密.** @param text 需要解密的密文* @return 解密得到的明文* @throws WxAesException aes解密失败*/String decrypt(String text) throws WxAesException {byte[] original;try {// 设置解密模式为AES的CBC模式Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);// 使用BASE64对密文进行解码byte[] encrypted = Base64.decodeBase64(text);// 解密original = cipher.doFinal(encrypted);} catch (Exception e) {e.printStackTrace();throw new WxAesException(WxAesException.DecryptAESError);}String xmlContent, from_appid;try {// 去除补位字符byte[] bytes = PKCS7Encoder.decode(original);// 分离16位随机字符串,网络字节序和AppIdbyte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);int xmlLength = recoverNetworkBytesOrder(networkOrder);xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);from_appid =new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);} catch (Exception e) {e.printStackTrace();throw new WxAesException(WxAesException.IllegalBuffer);}// appid不相同的情况if (!from_appid.equals(appId)) {throw new WxAesException(WxAesException.ValidateSignatureError);}return xmlContent;}/*** * 检验消息的真实性,并且获取解密后的明文.* <ol>* <li>利用收到的密文生成安全签名,进行签名验证</li>* <li>若验证通过,则提取xml中的加密消息</li>* <li>对消息进行解密</li>* </ol>** @param msgSignature 签名串,对应URL参数的msg_signature* @param timeStamp 时间戳,对应URL参数的timestamp* @param nonce 随机串,对应URL参数的nonce* @param postData 密文,对应POST请求的数据* @return 解密后的原文* @throws WxAesException 执行失败,请查看该异常的错误码和具体的错误信息*/public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData)throws WxAesException {// 密钥,公众账号的app secret// 提取密文Object[] encrypt = extract(postData);// 验证安全签名String signature = getSHA1(token, timeStamp, nonce, encrypt[1].toString());// 和URL中的签名比较是否相等// System.out.println("第三方收到URL中的签名:" + msg_sign);// System.out.println("第三方校验签名:" + signature);if (!signature.equals(msgSignature)) {throw new WxAesException(WxAesException.ValidateSignatureError);}// 解密String result = decrypt(encrypt[1].toString());return result;}/*** 提取出xml数据包中的加密消息** @param xmltext 待提取的xml字符串* @return 提取出的加密消息字符串* @throws WxAesException*/public static Object[] extract(String xmltext) throws WxAesException {Object[] result = new Object[3];try {DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);dbf.setXIncludeAware(false);dbf.setExpandEntityReferences(false);DocumentBuilder db = dbf.newDocumentBuilder();StringReader sr = new StringReader(xmltext);InputSource is = new InputSource(sr);Document document = db.parse(is);Element root = document.getDocumentElement();NodeList nodelist1 = root.getElementsByTagName("Encrypt");NodeList nodelist2 = root.getElementsByTagName("ToUserName");result[0] = 0;result[1] = nodelist1.item(0).getTextContent();//注意这里,获取ticket中的xml里面没有ToUserName这个元素,官网原示例代码在这里会报空//空指针,所以需要处理一下if (nodelist2 != null) {if (nodelist2.item(0) != null) {result[2] = nodelist2.item(0).getTextContent();}}return result;} catch (Exception e) {e.printStackTrace();throw new WxAesException(WxAesException.ParseXmlError);}}/*** 用SHA1算法生成安全签名** @param token 票据* @param timestamp 时间戳* @param nonce 随机字符串* @param encrypt 密文* @return 安全签名* @throws WxAesException*/public static String getSHA1(String token, String timestamp, String nonce, String encrypt)throws WxAesException {try {String[] array = new String[]{token, timestamp, nonce, encrypt};StringBuffer sb = new StringBuffer();// 字符串排序Arrays.sort(array);for (int i = 0; i < 4; i++) {sb.append(array[i]);}String str = sb.toString();// SHA1签名生成MessageDigest md = MessageDigest.getInstance("SHA-1");md.update(str.getBytes());byte[] digest = md.digest();StringBuffer hexstr = new StringBuffer();String shaHex = "";for (int i = 0; i < digest.length; i++) {shaHex = Integer.toHexString(digest[i] & 0xFF);if (shaHex.length() < 2) {hexstr.append(0);}hexstr.append(shaHex);}return hexstr.toString();} catch (Exception e) {throw new WxAesException(WxAesException.ComputeSignatureError);}}/*** 校验签名** @param signature 签名* @param timestamp 时间戳* @param nonce 随机数* @return 布尔值*/public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {String checkText = null;if (null != signature) {//对ToKen,timestamp,nonce 按字典排序String[] paramArr = new String[]{token, timestamp, nonce};Arrays.sort(paramArr);//将排序后的结果拼成一个字符串String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);try {MessageDigest md = MessageDigest.getInstance("SHA-1");//对接后的字符串进行sha1加密byte[] digest = md.digest(content.toString().getBytes());checkText = byteToStr(digest);} catch (Exception e) {log.error("解码发生异常", e);}}//将加密后的字符串与signature进行对比return checkText != null ? checkText.equals(signature.toUpperCase()) : false;}/*** 将字节数组转化我16进制字符串* @param byteArrays 字符数组* @return 字符串*/private static String byteToStr(byte[] byteArrays){String str = "";for (int i = 0; i < byteArrays.length; i++) {str += byteToHexStr(byteArrays[i]);}return str;}/*** 将字节转化为十六进制字符串* @param myByte 字节* @return 字符串*/private static String byteToHexStr(byte myByte) {char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};char[] tampArr = new char[2];tampArr[0] = Digit[(myByte >>> 4) & 0X0F];tampArr[1] = Digit[myByte & 0X0F];String str = new String(tampArr);return str;}
}
@SuppressWarnings("serial")
public class WxAesException extends Exception {public final static int OK = 0;public final static int ValidateSignatureError = -40001;public final static int ParseXmlError = -40002;public final static int ComputeSignatureError = -40003;public final static int IllegalAesKey = -40004;public final static int ValidateCorpidError = -40005;public final static int EncryptAESError = -40006;public final static int DecryptAESError = -40007;public final static int IllegalBuffer = -40008;//public final static int EncodeBase64Error = -40009;//public final static int DecodeBase64Error = -40010;//public final static int GenReturnXmlError = -40011;private int code;private static String getMessage(int code) {switch (code) {case ValidateSignatureError:return "签名验证错误";case ParseXmlError:return "xml解析失败";case ComputeSignatureError:return "sha加密生成签名失败";case IllegalAesKey:return "SymmetricKey非法";case ValidateCorpidError:return "corpid校验失败";case EncryptAESError:return "aes加密失败";case DecryptAESError:return "aes解密失败";case IllegalBuffer:return "解密后得到的buffer非法";
// case EncodeBase64Error:
// return "base64加密错误";
// case DecodeBase64Error:
// return "base64解密错误";
// case GenReturnXmlError:
// return "xml生成失败";default:return null; // cannot be}}public int getCode() {return code;}WxAesException(int code) {super(getMessage(code));this.code = code;}}
正常情况下,这样写是没有问题的。但是很多时候我们都会使用FastJSON,还会加一个FastJsonHttpMessageConverter用来格式化返回的json数据。
加FastJsonHttpMessageConverter本意上是好的,但是这玩意会在我们返回的字符串上加上双引号,但是又不能不加。
网上解决这个问题,一般都是加一个StringHttpMessageConverter,但是我发现加上之后双引号确实没了,但是不知道为啥还是不行。
所以最好的方式是不要直接返回String类型,直接使用response.getWriter().print(echostr); 这样返回的值才不会被我们的工具类处理。
if (WXBizMsgCrypt.checkSignature(signature, timestamp, nonce, gzhToken)) {log.info("微信公众号校验完成echostr:[{}]", echostr);try {response.getWriter().print(echostr);} catch (IOException e) {log.error("输出返回值异常", e);}return;
}
微信小程序入门10-微信公众号token验证失败相关推荐
- 微信小程序入门7-微信公众号设置IP白名单
一.为什么要配置IP白名单 此处IP为服务器对公网IP: 在IP白名单内的IP地址作为来源,获取access_token接口才可调用成功. 而想要调用公众号相关API,就必须获取access_toke ...
- 微信小程序生态13-微信公众号自定义菜单、个性化菜单配置
文章导航 微信小程序生态1-初识小程序 微信小程序生态2-创建一个微信小程序 微信小程序生态3-微信小程序登录流程设计 微信小程序生态4-扫普通二维码进入小程序.打开短链接进入小程序 微信小程序生态5 ...
- 微信小程序获取并展示公众号推文
微信小程序获取并展示公众号推文 获取公众号access_token(开发环境) IP白名单设置 获取access_token 小程序前端展示 素材导入云数据库 云函数读取展示 注:笔者是小程序菜鸟,代 ...
- nodejs+express解决微信公众号token验证失败
nodejs+express解决微信公众号token验证失败 问题描述 下面是服务端代码 问题原因 解决方案 重新运行服务器问题解决 结语 问题描述 这里是测试号的状态,如果是公众号的话上边应该显示t ...
- php 公众号 验证失败,微信公众号Token验证失败的几种情况及解决办法
微信公众号Token验证失败的几种情况及解决办法 1. 思路看验证的文件,编码是否是:utf-8 无bom头的 2. thinkphp框架,要关闭调试模式,开启调试模式容易有错误代码出现,导致验证失败 ...
- php开发微信公众号token验证失败,Thinkphp5 微信公众号token验证不成功的原因
微信是中国最流行的社交软件之一,随着微信生态系统的建立,很多人也致力于公众号 相关的文章">微信公众号和微信小程序的开发,那么Thinkphp5 微信公众号token验证不成功的原因及 ...
- java获取openid_JAVA获取微信小程序openid和获取公众号openid,以及通过openid获取用户信息...
https://www.cnblogs.com/lxwt/p/10154540.html?tdsourcetag=s_pcqq_aiomsg 一,首先说明下这个微信的openid 为了识别用户,每个用 ...
- 微信小程序 official-account组件 关注公众号
昨天微信小程序发布了 official-account 相关组件,从意思上来讲可以通过小程序直接点击标签关注对应的公众号今天一上班就试了一下. 文档不长,但是对于我这种看文档能力较差的人还是有点吃力的 ...
- 微信小程序内跳转公众号
可使用微信小程序官方提供的official-account 组件来完成,官方文档 Tips: 1. 使用组件前,需前往小程序后台,在"设置"->"关注公众号&quo ...
最新文章
- mfc 静态框接收tab焦点_目标检测中焦点损失的入门指南
- 框架:Spring事务的隔离级别
- MQTT之mosquitto_passwd命令使用
- linux:shell:tree
- 2008 Round1AA Minimum Scalar Product
- mysql客户端报错1366_mysql一些异常
- SQL Server中TEXT类型操作
- IIS Express局域网访问配置
- update语句修改报错问题处理
- cmake--生成--动态库
- 数据结构(java语言描述)递归实现——汉诺塔问题
- git 暂存文件操作 stash
- 小程序wxParse
- java中this和this()区别
- 怎么实现word自动分级标题
- html视频播放卡顿,网页看视频卡怎么解决
- 安卓手机怎样安装apk应用
- Python语言程序设计基础_实验四_函数(二)_答案_通识教育必修课程_上海师范大学
- 【Python 数据科学】Numpy和Pandas基础
- 国家级版权区块链系统 DCI标准联盟链发布 迅雷区块链提供技术支持