柳峰微信公众平台开发教程企业号修改篇(AES验证)
Posted luozhuang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了柳峰微信公众平台开发教程企业号修改篇(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); } }
以上是关于柳峰微信公众平台开发教程企业号修改篇(AES验证)的主要内容,如果未能解决你的问题,请参考以下文章