柳峰微信公众平台开发教程企业号修改篇(AES验证)
本文针对《微信公众平台应用开发:方法、技巧与案例》 一书中示例和代码不适用于微信企业号的情况进行修改。
修改原因:
企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全
修改方法:
按照微信加密库进行加密验证,具体加密库下载地点请参考开发人员文档,这里不再叙述
注意事项:
异常Java.security.InvalidKeyException:illegal Key Size
需要去Oracle官方网站下载JCE无限制权限策略文件,分JDK6 JDK7 JDK8,不会的同学请上网找教程。
关键示例代码:(注意不是全部完整代码,这涉及到log之类云云。。)
- package com.luozhuang;
- import com.luozhuang.CommonClass;
- import com.luozhuang.MyLog;
- import com.luozhuang.util.service.QCoreService;
- import com.qq.weixin.mp.aes.AesException;
- import com.qq.weixin.mp.aes.WXBizMsgCrypt;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.PrintWriter;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.commons.io.IOUtils;
- public class CoreServletliufeng extends HttpServlet{
- private static final long serialVersionUID = 4440739483644821986L;
- String sToken = "luozhuang"; //这个Token是随机生成,但是必须跟企业号上的相同
- String sCorpID = "luozhuang"; //这里是你企业号的CorpID</span>
- String sEncodingAESKey ="luozhuang"; //这个EncodingAESKey是随机生成,但是必须跟企业号上的相同</span>
- boolean islog = true;
- /**
- * 确认请求来自微信服务器
- * 这个方法针对企业版微信
- * 注意,企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全
- * 如果使用 柳峰 等普通微信开发的教程需要进行修改,支持加密
- * 企业版微信有加密 AES
- * 需要导入微信加密库
- * http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8A%A0%E8%A7%A3%E5%AF%86%E5%BA%93%E4%B8%8B%E8%BD%BD%E4%B8%8E%E8%BF%94%E5%9B%9E%E7%A0%81
- * WXBizMsgCrypt 建议认真阅读说明
- * 步骤
- * 1、首要要有一个ICP备案的域名,一定要有ICP备案,后面需要;
- 2、EncodeAESKey 需要设置时候那个;
- 3、替换JCE包,重启服务器
- 4、JDK版本要大于等于1.6
- 5、回调模式和主动调用模式在消息发送上也有很大不同:
- A:回调模式下,被动发送的消息需要时xml格式并进行加密,加密规则是首先进行AES加密,然后进行base64加密。
- B:主动发送消息,格式为json格式,不需要加密,但需要token
- 6、回调模式接受到真正的消息内容之后,注意回复,空消息即可,否则微信会认为消息接受失败,会再次发送同一消息
- * @throws IOException
- */
- public void doGet(HttpServletRequest request,
- HttpServletResponse response) throws IOException {
- // 微信加密签名
- String sVerifyMsgSig = request.getParameter("msg_signature");
- // 时间戳
- String sVerifyTimeStamp = request.getParameter("timestamp");
- // 随机数
- String sVerifyNonce = request.getParameter("nonce");
- // 随机字符串
- String sVerifyEchoStr = request.getParameter("echostr");
- String sEchoStr; //需要返回的明文
- PrintWriter out = response.getWriter();
- WXBizMsgCrypt wxcpt;
- try {
- wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
- sEchoStr =
- wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce,
- sVerifyEchoStr);
- // 验证URL成功,将sEchoStr返回
- out.print(sEchoStr);
- } catch (AesException e1) {
- e1.printStackTrace();
- }
- }
- /**
- * 处理微信服务器发来的消息
- * 微信企业号接收消息(使用SpringMVC)
- * 注意,企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全
- * 如果使用 柳峰 等普通微信开发的教程需要进行修改,支持加密
- */
- public void doPost(HttpServletRequest request,
- HttpServletResponse response) throws ServletException,
- IOException {
- // 将请求、响应的编码均设置为UTF-8(防止中文乱码)
- request.setCharacterEncoding("UTF-8");
- response.setCharacterEncoding("UTF-8");
- // 微信加密签名
- String msg_signature = request.getParameter("msg_signature");
- // 时间戳
- String timestamp = request.getParameter("timestamp");
- // 随机数
- String nonce = request.getParameter("nonce");
- //从请求中读取整个post数据
- InputStream inputStream = request.getInputStream();
- String postData = IOUtils.toString(inputStream, "UTF-8");
- System.out.println(postData);
- String msg = "";
- WXBizMsgCrypt wxcpt = null;
- try {
- wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
- //解密消息
- msg = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, postData);
- } catch (AesException e) {
- println(e);
- if (e.OrginexceptionMessage != null) {
- println(e.OrginexceptionMessage);
- }
- println("msg_signature:" + msg_signature + "timestamp:" +
- timestamp + "nonce:" + nonce + "postData:" + postData);
- return;
- }
- // 调用核心业务类接收消息、处理消息
- String respMessage;
- try {
- respMessage = QCoreService.processRequest(msg);
- } catch (Exception e) {
- println(e);
- return;
- }
- String encryptMsg = "";
- try {
- //加密回复消息
- encryptMsg = wxcpt.EncryptMsg(respMessage, timestamp, nonce);
- } catch (AesException e) {
- println(e);
- return;
- }
- // 响应消息
- PrintWriter out = response.getWriter();
- out.print(encryptMsg);
- out.close();
- }
- void println(String Message) {
- if (islog == true) {
- MyLog.writelogfile(CommonClass.GetCurrentDatText() + Message);
- } else {
- System.out.printf(Message);
- }
- }
- void println(Exception e) {
- if (islog == true) {
- MyLog.writelogfile(CommonClass.GetCurrentDatText() +
- e.getMessage());
- } else {
- System.out.printf(e.getMessage());
- }
- }
- }
为了保证统一这里建立一个QCoreService 作为企业号专用类以区别原来的CoreService 。
- package com.luozhuang.util.service;
- import com.liufeng.util.message.resp.Article;
- import com.liufeng.util.message.resp.NewsMessage;
- import com.liufeng.util.message.resp.TextMessage;
- import com.liufeng.util.util.MessageUtil;
- import java.util.ArrayList;
- import java.util.Date;
- import java.util.List;
- import java.util.Map;
- /**
- * 核心服务类,适合企业号
- *
- */
- public class QCoreService {
- public QCoreService() {
- super();
- }
- /**
- * 处理微信发来的请求
- *
- * @param request
- * @return xml
- */
- public static String processRequest(String msg) throws Exception {
- // xml格式的消息数据
- String respXml = null;
- // 调用parseXml方法解析请求消息
- Map<String, String> requestMap = MessageUtil.parseXml(msg);
- // 发送方帐号
- String fromUserName = requestMap.get("FromUserName");
- // 开发者微信号
- String toUserName = requestMap.get("ToUserName");
- // 消息类型
- String msgType = requestMap.get("MsgType");
- // 默认返回的文本消息内容
- String respContent = "未知的消息类型!";
- TextMessage textMessage = new TextMessage();
- textMessage.setToUserName(fromUserName);
- textMessage.setFromUserName(toUserName);
- textMessage.setCreateTime(new Date().getTime());
- textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
- // 事件推送
- if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
- // 事件类型
- String eventType = requestMap.get("Event");
- // 订阅
- if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
- textMessage.setContent("您好,欢迎关注!");
- // 将消息对象转换成xml
- respXml = MessageUtil.messageToXml(textMessage);
- }
- // 取消订阅
- else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
- // TODO 取消订阅后用户不会再收到公众账号发送的消息,因此不需要回复
- }
- // 自定义菜单点击事件 微信那边有时候不分大小写
- else if (eventType.equalsIgnoreCase(MessageUtil.EVENT_TYPE_CLICK)) {
- respContent = "您菜单事件!";
- // 事件KEY值,与创建菜单时的key值对应
- String eventKey = requestMap.get("EventKey");
- // 根据key值判断用户点击的按钮
- }
- // 扫描带参数二维码
- else if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) {
- // TODO 处理扫描带参数二维码事件
- respContent = "您扫描带参数二维码事件!";
- }
- // 上报地理位置
- else if (eventType.equals(MessageUtil.EVENT_TYPE_LOCATION)) {
- // TODO 处理上报地理位置事件
- respContent = "您上报地理位置事件!";
- }
- }
- // 当用户发消息时
- // 文本消息
- if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
- String content = requestMap.get("Content").trim();
- if (content.startsWith("登录")) {
- textMessage.setContent(" 登录!");
- respXml = MessageUtil.messageToXml(textMessage);
- } else {
- respContent = "您发送的是文本消息!";
- }
- }
- // 图片消息
- else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
- respContent = "您发送的是图片消息!";
- }
- // 语音消息
- else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {
- respContent = "您发送的是语音消息!";
- }
- // 视频消息
- else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {
- respContent = "您发送的是视频消息!";
- }
- // 地理位置消息
- else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {
- respContent = "您发送的是地理位置消息!";
- }
- // 链接消息
- else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {
- respContent = "您发送的是链接消息!";
- }
- textMessage.setContent(respContent);
- // 将文本消息对象转换成xml
- if(respXml==null)
- {
- respXml = MessageUtil.messageToXml(textMessage);
- }
- return respXml;
- }
- }
由于原liufeng的类不支持XML格式,所以进行增加修改
- /**
- * 消息处理工具类
- *
- * @author liufeng
- * @date 2013-09-15
- */
- public class MessageUtil {
- // 请求消息类型:文本
- public static final String REQ_MESSAGE_TYPE_TEXT = "text";
- // 请求消息类型:图片
- public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
- // 请求消息类型:语音
- public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
- // 请求消息类型:视频
- public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
- // 请求消息类型:地理位置
- public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
- // 请求消息类型:链接
- public static final String REQ_MESSAGE_TYPE_LINK = "link";
- // 请求消息类型:事件推送
- public static final String REQ_MESSAGE_TYPE_EVENT = "event";
- // 事件类型:subscribe(订阅)
- public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
- // 事件类型:unsubscribe(取消订阅)
- public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
- // 事件类型:scan(用户已关注时的扫描带参数二维码)
- public static final String EVENT_TYPE_SCAN = "scan";
- // 事件类型:LOCATION(上报地理位置)
- public static final String EVENT_TYPE_LOCATION = "LOCATION";
- // 事件类型:CLICK(自定义菜单)
- public static final String EVENT_TYPE_CLICK = "CLICK";
- // 响应消息类型:文本
- public static final String RESP_MESSAGE_TYPE_TEXT = "text";
- // 响应消息类型:图片
- public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
- // 响应消息类型:语音
- public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
- // 响应消息类型:视频
- public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
- // 响应消息类型:音乐
- public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
- // 响应消息类型:图文
- public static final String RESP_MESSAGE_TYPE_NEWS = "news";
- /**
- * 解析微信发来的请求(XML)
- *
- * @param request
- * @return
- * @throws Exception
- */
- public static Map<String, String> parseXml(String msg)
- throws Exception {
- // 将解析结果存储在HashMap中
- Map<String, String> map = new HashMap<String, String>();
- // 从request中取得输入流
- InputStream inputStream = new ByteArrayInputStream(msg.getBytes("UTF-8"));
- // 读取输入流
- SAXReader reader = new SAXReader();
- Document document = reader.read(inputStream);
- // 得到xml根元素
- Element root = document.getRootElement();
- // 得到根元素的所有子节点
- List<Element> elementList = root.elements();
- // 遍历所有子节点
- for (Element e : elementList)
- map.put(e.getName(), e.getText());
- // 释放资源
- inputStream.close();
- inputStream = null;
- return map;
- }
- /**
- * 解析微信发来的请求(XML)
- *
- * @param request
- * @return Map<String, String>
- * @throws Exception
- */
- @SuppressWarnings("unchecked")
- public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
- // 将解析结果存储在HashMap中
- Map<String, String> map = new HashMap<String, String>();
- // 从request中取得输入流
- InputStream inputStream = request.getInputStream();
- // 读取输入流
- SAXReader reader = new SAXReader();
- Document document = reader.read(inputStream);
- // 得到xml根元素
- Element root = document.getRootElement();
- // 得到根元素的所有子节点
- List<Element> elementList = root.elements();
- // 遍历所有子节点
- for (Element e : elementList)
- map.put(e.getName(), e.getText());
- // 释放资源
- inputStream.close();
- inputStream = null;
- return map;
- }
- /**
- * 扩展xstream使其支持CDATA
- */
- private static XStream xstream = new XStream(new XppDriver() {
- public HierarchicalStreamWriter createWriter(Writer out) {
- return new PrettyPrintWriter(out) {
- // 对所有xml节点的转换都增加CDATA标记
- boolean cdata = true;
- @SuppressWarnings("unchecked")
- public void startNode(String name, Class clazz) {
- super.startNode(name, clazz);
- }
- protected void writeText(QuickWriter writer, String text) {
- if (cdata) {
- writer.write("<![CDATA[");
- writer.write(text);
- writer.write("]]>");
- } else {
- writer.write(text);
- }
- }
- };
- }
- });
- /**
- * 文本消息对象转换成xml
- *
- * @param textMessage 文本消息对象
- * @return xml
- */
- public static String messageToXml(TextMessage textMessage) {
- xstream.alias("xml", textMessage.getClass());
- return xstream.toXML(textMessage);
- }
- /**
- * 图片消息对象转换成xml
- *
- * @param imageMessage 图片消息对象
- * @return xml
- */
- public static String messageToXml(ImageMessage imageMessage) {
- xstream.alias("xml", imageMessage.getClass());
- return xstream.toXML(imageMessage);
- }
- /**
- * 语音消息对象转换成xml
- *
- * @param voiceMessage 语音消息对象
- * @return xml
- */
- public static String messageToXml(VoiceMessage voiceMessage) {
- xstream.alias("xml", voiceMessage.getClass());
- return xstream.toXML(voiceMessage);
- }
- /**
- * 视频消息对象转换成xml
- *
- * @param videoMessage 视频消息对象
- * @return xml
- */
- public static String messageToXml(VideoMessage videoMessage) {
- xstream.alias("xml", videoMessage.getClass());
- return xstream.toXML(videoMessage);
- }
- /**
- * 音乐消息对象转换成xml
- *
- * @param musicMessage 音乐消息对象
- * @return xml
- */
- public static String messageToXml(MusicMessage musicMessage) {
- xstream.alias("xml", musicMessage.getClass());
- return xstream.toXML(musicMessage);
- }
- /**
- * 图文消息对象转换成xml
- *
- * @param newsMessage 图文消息对象
- * @return xml
- */
- public static String messageToXml(NewsMessage newsMessage) {
- xstream.alias("xml", newsMessage.getClass());
- xstream.alias("item", new Article().getClass());
- return xstream.toXML(newsMessage);
- }
- }