这里开发语言使用的是java,推荐使用认证过的微信公众号或者是微信公众平台测试账号。测试账号登录申请请访问:http://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
一、开发环境的搭建
这里提供两种开发环境:
一是开发测试环境:
- 使用本地加算计作为服务器,安装好开发环境
- 通过内网穿透代理到外网上去,内网穿透可以使用花生壳等内网穿透工具。这里教大家一种免费的内网穿透工具:https://www.ngrok.cc
二是正式的运行部署环境:
- 购买云服务器,搭建好运行环境
- 备案审核通过的域名
- nginx服务器
二、查看服务器配置信息的介绍
登录微信公众平台官网后,进入到公众平台后台管理页面。
选择 公众号基本设置-》基本配置 ,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey。
微信公众号配置信息:
微信公众部分接口权限需要认证之后才能开通,开发阶段建议实现用微信公众平台测试账号。
测试账号配置信息:
可以看到需要的参数:
- URL:是开发者用来接收微信消息和事件 的接口URL。(必须以http://开头,目前支持80端口)
- Token:可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。注意必须为英文或数字,长度为3-32字符。
- EncodingAESKey:由开发者手动填写或随机生成,将用作消息体加解密密钥。(消息加密密钥由43位字符组成,可随机修改,字符范围为A-Z,a-z,0-9。)
同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式: - 明文模式:不使用消息体加解密功能,安全系数较低
- 兼容模式:明文、密文将共存,方便开发者调试和维护
- 安全模式:消息包为纯密文,需要开发者加密和解密,安全系数高
三、服务器开发
查看微信公众号开发文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
在入门指引查看验证消息需要的参数,开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp
参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
下面就可以进行开发了:
首先创建一个web工程,博主使用的是SpringBoot框架,大家随意。
springboot项目启动类
package cc.feefox.wechat;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
控制类
package cc.feefox.wechat.main;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* @Package: cc.feefox.wechat.main
* @author: cc
* @date: 2018年8月18日 下午12:17:55
*/
public class BaseController {
protected static final String _CSRF_RANDOM_TOKEN = "_CSRF_RANDOM_TOKEN";
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
}
package cc.feefox.wechat.main;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import cc.feefox.wechat.common.util.SignatureUtil;
/**
* 微信消息入口
*
* @Package: cc.feefox.wechat.main
* @author: cc
* @date: 2018年8月18日 下午12:18:39
*/
@RestController
@RequestMapping("/wechat")
public class WeChatController extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(WeChatController.class);
/**
* 校验信息是否是从微信服务器发出,处理消息
*
* @param request
* @param out
* @throws IOException
*/
@RequestMapping(value = "/handler", method = { RequestMethod.GET, RequestMethod.POST })
public void processPost() throws Exception {
this.getRequest().setCharacterEncoding("UTF-8");
this.getResponse().setCharacterEncoding("UTF-8");
logger.info("开始校验信息是否是从微信服务器发出");
// 签名
String signature = this.getRequest().getParameter("signature");
// 时间戳
String timestamp = this.getRequest().getParameter("timestamp");
// 随机数
String nonce = this.getRequest().getParameter("nonce");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (SignatureUtil.checkSignature(signature, timestamp, nonce)) {
// 随机字符串
String echostr = this.getRequest().getParameter("echostr");
logger.debug("接入成功,echostr {}", echostr);
this.getResponse().getWriter().write(echostr);
}
}
}
签名验证
package cc.feefox.wechat.common.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import cc.feefox.wechat.common.constant.WechatConstant;
/**
* 校验签名工具类
* @Package: cc.feefox.wechat.common.util
* @author: cc
* @date: 2018年8月18日 下午12:17:19
*/
public class SignatureUtil {
// 此处的token即为微信接口配置填写的签名
private static final String TOKEN = "weixin";
/**
* 验证签名
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
// 1.将token、timestamp、nonce三个参数进行字典序排序
String[] arr = new String[] { TOKEN, timestamp, nonce };
Arrays.sort(arr);
// 2. 将三个参数字符串拼接成一个字符串进行sha1加密
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
MessageDigest md = null;
String tmpStr = null;
try {
md = MessageDigest.getInstance("SHA-1");
// 将三个参数字符串拼接成一个字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = null;
// 3.将sha1加密后的字符串可与signature对比,标识该请求来源于微信
Boolean asd = tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
return asd;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
return new String(tempArr);
}
}
三、接口配置
URL: http://域名:80/wechat/handler 或者是https://域名:443/wechat/handler
TOKEN:weixin 与SignatureUtil类中定义的TOKEN常量一致。
启动服务点击提交
如有错漏请指出,欢迎加群 581817132