微信公众平台开发教程企业号修改篇(AES验证)

柳峰微信公众平台开发教程企业号修改篇(AES验证)


  本文针对《微信公众平台应用开发:方法、技巧与案例》 一书中示例和代码不适用于微信企业号的情况进行修改。

 

 修改原因:

  企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全

  修改方法:

  按照微信加密库进行加密验证,具体加密库下载地点请参考开发人员文档,这里不再叙述

  

  注意事项:

  异常Java.security.InvalidKeyException:illegal Key Size

  需要去Oracle官方网站下载JCE无限制权限策略文件,分JDK6 JDK7 JDK8,不会的同学请上网找教程。


  关键示例代码:(注意不是全部完整代码,这涉及到log之类云云。。)

  

  1. package com.luozhuang;  
  2.   
  3. import com.luozhuang.CommonClass;  
  4. import com.luozhuang.MyLog;  
  5.   
  6. import com.luozhuang.util.service.QCoreService;  
  7.   
  8. import com.qq.weixin.mp.aes.AesException;  
  9. import com.qq.weixin.mp.aes.WXBizMsgCrypt;  
  10.   
  11. import java.io.IOException;  
  12. import java.io.InputStream;  
  13. import java.io.PrintWriter;  
  14.   
  15. import javax.servlet.ServletException;  
  16. import javax.servlet.http.HttpServlet;  
  17. import javax.servlet.http.HttpServletRequest;  
  18. import javax.servlet.http.HttpServletResponse;  
  19.   
  20. import org.apache.commons.io.IOUtils;  
  21.   
  22. public class CoreServletliufeng extends HttpServlet{  
  23.     private static final long serialVersionUID = 4440739483644821986L;  
  24.   
  25.     String sToken = "luozhuang"//这个Token是随机生成,但是必须跟企业号上的相同  
  26.     String sCorpID = "luozhuang"//这里是你企业号的CorpID</span>  
  27.   
  28.     String sEncodingAESKey ="luozhuang"//这个EncodingAESKey是随机生成,但是必须跟企业号上的相同</span>  
  29.   
  30.     boolean islog = true;  
  31.   
  32.     /** 
  33.      * 确认请求来自微信服务器 
  34.      * 这个方法针对企业版微信 
  35.      * 注意,企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全 
  36.      * 如果使用 柳峰 等普通微信开发的教程需要进行修改,支持加密 
  37.      * 企业版微信有加密 AES 
  38.      * 需要导入微信加密库 
  39.      * 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 
  40.      * WXBizMsgCrypt 建议认真阅读说明 
  41.      * 步骤 
  42.      *    1、首要要有一个ICP备案的域名,一定要有ICP备案,后面需要; 
  43.  
  44.         2、EncodeAESKey 需要设置时候那个; 
  45.  
  46.         3、替换JCE包,重启服务器 
  47.  
  48.         4、JDK版本要大于等于1.6 
  49.  
  50.         5、回调模式和主动调用模式在消息发送上也有很大不同: 
  51.  
  52.               A:回调模式下,被动发送的消息需要时xml格式并进行加密,加密规则是首先进行AES加密,然后进行base64加密。 
  53.  
  54.               B:主动发送消息,格式为json格式,不需要加密,但需要token 
  55.  
  56.         6、回调模式接受到真正的消息内容之后,注意回复,空消息即可,否则微信会认为消息接受失败,会再次发送同一消息 
  57.      * @throws IOException 
  58.      */  
  59.     public void doGet(HttpServletRequest request,  
  60.                       HttpServletResponse response) throws IOException {  
  61.   
  62.         // 微信加密签名  
  63.         String sVerifyMsgSig = request.getParameter("msg_signature");  
  64.         // 时间戳  
  65.         String sVerifyTimeStamp = request.getParameter("timestamp");  
  66.         // 随机数  
  67.         String sVerifyNonce = request.getParameter("nonce");  
  68.         // 随机字符串  
  69.         String sVerifyEchoStr = request.getParameter("echostr");  
  70.         String sEchoStr; //需要返回的明文  
  71.         PrintWriter out = response.getWriter();  
  72.         WXBizMsgCrypt wxcpt;  
  73.         try {  
  74.             wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);  
  75.             sEchoStr =  
  76.                     wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce,  
  77.                                     sVerifyEchoStr);  
  78.             // 验证URL成功,将sEchoStr返回  
  79.             out.print(sEchoStr);  
  80.         } catch (AesException e1) {  
  81.             e1.printStackTrace();  
  82.         }  
  83.     }  
  84.   
  85.   
  86.     /** 
  87.      * 处理微信服务器发来的消息 
  88.      * 微信企业号接收消息(使用SpringMVC) 
  89.      * 注意,企业在接收消息,以及发送被动响应消息时,消息体都以AES方式加密,以保证传输的安全 
  90.      * 如果使用 柳峰 等普通微信开发的教程需要进行修改,支持加密 
  91.      */  
  92.     public void doPost(HttpServletRequest request,  
  93.                        HttpServletResponse response) throws ServletException,  
  94.                                                             IOException {  
  95.         // 将请求、响应的编码均设置为UTF-8(防止中文乱码)  
  96.         request.setCharacterEncoding("UTF-8");  
  97.         response.setCharacterEncoding("UTF-8");  
  98.   
  99.         // 微信加密签名  
  100.         String msg_signature = request.getParameter("msg_signature");  
  101.         // 时间戳  
  102.         String timestamp = request.getParameter("timestamp");  
  103.         // 随机数  
  104.         String nonce = request.getParameter("nonce");  
  105.   
  106.         //从请求中读取整个post数据  
  107.         InputStream inputStream = request.getInputStream();  
  108.         String postData = IOUtils.toString(inputStream, "UTF-8");  
  109.         System.out.println(postData);  
  110.   
  111.         String msg = "";  
  112.         WXBizMsgCrypt wxcpt = null;  
  113.         try {  
  114.             wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);  
  115.             //解密消息  
  116.             msg = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, postData);  
  117.         } catch (AesException e) {  
  118.             println(e);  
  119.             if (e.OrginexceptionMessage != null) {  
  120.                 println(e.OrginexceptionMessage);  
  121.             }  
  122.             println("msg_signature:" + msg_signature + "timestamp:" +  
  123.                     timestamp + "nonce:" + nonce + "postData:" + postData);  
  124.             return;  
  125.         }  
  126.         
  127.         // 调用核心业务类接收消息、处理消息  
  128.         String respMessage;  
  129.         try {  
  130.             respMessage = QCoreService.processRequest(msg);  
  131.         } catch (Exception e) {  
  132.             println(e);  
  133.             return;  
  134.         }  
  135.   
  136.         String encryptMsg = "";  
  137.         try {  
  138.             //加密回复消息  
  139.             encryptMsg = wxcpt.EncryptMsg(respMessage, timestamp, nonce);  
  140.         } catch (AesException e) {  
  141.             println(e);  
  142.             return;  
  143.         }  
  144.   
  145.         // 响应消息  
  146.         PrintWriter out = response.getWriter();  
  147.         out.print(encryptMsg);  
  148.         out.close();  
  149.     }  
  150.   
  151.     void println(String Message) {  
  152.         if (islog == true) {  
  153.             MyLog.writelogfile(CommonClass.GetCurrentDatText() + Message);  
  154.         } else {  
  155.             System.out.printf(Message);  
  156.         }  
  157.     }  
  158.   
  159.     void println(Exception e) {  
  160.         if (islog == true) {  
  161.             MyLog.writelogfile(CommonClass.GetCurrentDatText() +  
  162.                                e.getMessage());  
  163.         } else {  
  164.             System.out.printf(e.getMessage());  
  165.         }  
  166.     }  
  167. }  

为了保证统一这里建立一个QCoreService 作为企业号专用类以区别原来的CoreService 。

  1. package com.luozhuang.util.service;  
  2.   
  3. import com.liufeng.util.message.resp.Article;  
  4. import com.liufeng.util.message.resp.NewsMessage;  
  5. import com.liufeng.util.message.resp.TextMessage;  
  6. import com.liufeng.util.util.MessageUtil;  
  7.   
  8. import java.util.ArrayList;  
  9. import java.util.Date;  
  10. import java.util.List;  
  11. import java.util.Map;  
  12.   
  13.   
  14. /** 
  15.  * 核心服务类,适合企业号 
  16.  * 
  17.  */  
  18. public class QCoreService {  
  19.     public QCoreService() {  
  20.         super();  
  21.     }  
  22.   
  23.     /** 
  24.      * 处理微信发来的请求 
  25.      * 
  26.      * @param request 
  27.      * @return xml 
  28.      */  
  29.   
  30.   
  31.     public static String processRequest(String msg) throws Exception {  
  32.         // xml格式的消息数据  
  33.         String respXml = null;  
  34.   
  35.         // 调用parseXml方法解析请求消息  
  36.         Map<String, String> requestMap = MessageUtil.parseXml(msg);  
  37.         // 发送方帐号  
  38.         String fromUserName = requestMap.get("FromUserName");  
  39.         // 开发者微信号  
  40.         String toUserName = requestMap.get("ToUserName");  
  41.         // 消息类型  
  42.         String msgType = requestMap.get("MsgType");  
  43.         // 默认返回的文本消息内容  
  44.         String respContent = "未知的消息类型!";  
  45.         TextMessage textMessage = new TextMessage();  
  46.         textMessage.setToUserName(fromUserName);  
  47.         textMessage.setFromUserName(toUserName);  
  48.         textMessage.setCreateTime(new Date().getTime());  
  49.         textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);  
  50.         // 事件推送  
  51.   
  52.   
  53.         if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {  
  54.             // 事件类型  
  55.             String eventType = requestMap.get("Event");  
  56.             // 订阅  
  57.             if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {  
  58.                 textMessage.setContent("您好,欢迎关注!");  
  59.                 // 将消息对象转换成xml  
  60.                 respXml = MessageUtil.messageToXml(textMessage);  
  61.             }  
  62.             // 取消订阅  
  63.             else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {  
  64.                 // TODO 取消订阅后用户不会再收到公众账号发送的消息,因此不需要回复  
  65.             }  
  66.             // 自定义菜单点击事件 微信那边有时候不分大小写  
  67.             else if (eventType.equalsIgnoreCase(MessageUtil.EVENT_TYPE_CLICK)) {  
  68.                 respContent = "您菜单事件!";  
  69.                 // 事件KEY值,与创建菜单时的key值对应  
  70.                 String eventKey = requestMap.get("EventKey");  
  71.                 // 根据key值判断用户点击的按钮  
  72.                
  73.             }  
  74.   
  75.             // 扫描带参数二维码  
  76.             else if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) {  
  77.                 // TODO 处理扫描带参数二维码事件  
  78.                 respContent = "您扫描带参数二维码事件!";  
  79.             }  
  80.             // 上报地理位置  
  81.             else if (eventType.equals(MessageUtil.EVENT_TYPE_LOCATION)) {  
  82.                 // TODO 处理上报地理位置事件  
  83.                 respContent = "您上报地理位置事件!";  
  84.             }  
  85.   
  86.   
  87.         }  
  88.   
  89.         // 当用户发消息时  
  90.         // 文本消息  
  91.         if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {  
  92.             String content = requestMap.get("Content").trim();  
  93.             if (content.startsWith("登录")) {  
  94.                 textMessage.setContent(" 登录!");  
  95.                 respXml = MessageUtil.messageToXml(textMessage);  
  96.             } else {  
  97.                 respContent = "您发送的是文本消息!";  
  98.             }  
  99.         }  
  100.         // 图片消息  
  101.         else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {  
  102.             respContent = "您发送的是图片消息!";  
  103.         }  
  104.         // 语音消息  
  105.         else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {  
  106.             respContent = "您发送的是语音消息!";  
  107.         }  
  108.         // 视频消息  
  109.         else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {  
  110.             respContent = "您发送的是视频消息!";  
  111.         }  
  112.         // 地理位置消息  
  113.         else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {  
  114.             respContent = "您发送的是地理位置消息!";  
  115.         }  
  116.         // 链接消息  
  117.         else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {  
  118.             respContent = "您发送的是链接消息!";  
  119.         }  
  120.   
  121.         textMessage.setContent(respContent);  
  122.         // 将文本消息对象转换成xml  
  123.         if(respXml==null)  
  124.         {  
  125.         respXml = MessageUtil.messageToXml(textMessage);  
  126.         }  
  127.         return respXml;  
  128.     }  
  129. }  

由于原liufeng的类不支持XML格式,所以进行增加修改
  1. /** 
  2.  * 消息处理工具类 
  3.  * 
  4.  * @author liufeng 
  5.  * @date 2013-09-15 
  6.  */  
  7. public class MessageUtil {  
  8.     // 请求消息类型:文本  
  9.     public static final String REQ_MESSAGE_TYPE_TEXT = "text";  
  10.     // 请求消息类型:图片  
  11.     public static final String REQ_MESSAGE_TYPE_IMAGE = "image";  
  12.     // 请求消息类型:语音  
  13.     public static final String REQ_MESSAGE_TYPE_VOICE = "voice";  
  14.     // 请求消息类型:视频  
  15.     public static final String REQ_MESSAGE_TYPE_VIDEO = "video";  
  16.     // 请求消息类型:地理位置  
  17.     public static final String REQ_MESSAGE_TYPE_LOCATION = "location";  
  18.     // 请求消息类型:链接  
  19.     public static final String REQ_MESSAGE_TYPE_LINK = "link";  
  20.   
  21.     // 请求消息类型:事件推送  
  22.     public static final String REQ_MESSAGE_TYPE_EVENT = "event";  
  23.   
  24.     // 事件类型:subscribe(订阅)  
  25.     public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";  
  26.     // 事件类型:unsubscribe(取消订阅)  
  27.     public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";  
  28.     // 事件类型:scan(用户已关注时的扫描带参数二维码)  
  29.     public static final String EVENT_TYPE_SCAN = "scan";  
  30.     // 事件类型:LOCATION(上报地理位置)  
  31.     public static final String EVENT_TYPE_LOCATION = "LOCATION";  
  32.     // 事件类型:CLICK(自定义菜单)  
  33.     public static final String EVENT_TYPE_CLICK = "CLICK";  
  34.   
  35.     // 响应消息类型:文本  
  36.     public static final String RESP_MESSAGE_TYPE_TEXT = "text";  
  37.     // 响应消息类型:图片  
  38.     public static final String RESP_MESSAGE_TYPE_IMAGE = "image";  
  39.     // 响应消息类型:语音  
  40.     public static final String RESP_MESSAGE_TYPE_VOICE = "voice";  
  41.     // 响应消息类型:视频  
  42.     public static final String RESP_MESSAGE_TYPE_VIDEO = "video";  
  43.     // 响应消息类型:音乐  
  44.     public static final String RESP_MESSAGE_TYPE_MUSIC = "music";  
  45.     // 响应消息类型:图文  
  46.     public static final String RESP_MESSAGE_TYPE_NEWS = "news";  
  47.   
  48.     /** 
  49.              * 解析微信发来的请求(XML) 
  50.              *  
  51.              * @param request 
  52.              * @return 
  53.              * @throws Exception 
  54.              */  
  55.             public static Map<String, String> parseXml(String msg)  
  56.                             throws Exception {  
  57.                     // 将解析结果存储在HashMap中  
  58.                     Map<String, String> map = new HashMap<String, String>();  
  59.   
  60.                     // 从request中取得输入流  
  61.                     InputStream inputStream = new ByteArrayInputStream(msg.getBytes("UTF-8"));  
  62.                       
  63.                     // 读取输入流  
  64.                     SAXReader reader = new SAXReader();  
  65.                     Document document = reader.read(inputStream);  
  66.                     // 得到xml根元素  
  67.                     Element root = document.getRootElement();  
  68.                     // 得到根元素的所有子节点  
  69.                     List<Element> elementList = root.elements();  
  70.   
  71.                     // 遍历所有子节点  
  72.                     for (Element e : elementList)  
  73.                             map.put(e.getName(), e.getText());  
  74.   
  75.                     // 释放资源  
  76.                     inputStream.close();  
  77.                     inputStream = null;  
  78.   
  79.                     return map;  
  80.             }  
  81.     /** 
  82.      * 解析微信发来的请求(XML) 
  83.      *  
  84.      * @param request 
  85.      * @return Map<String, String> 
  86.      * @throws Exception 
  87.      */  
  88.     @SuppressWarnings("unchecked")  
  89.     public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {  
  90.         // 将解析结果存储在HashMap中  
  91.         Map<String, String> map = new HashMap<String, String>();  
  92.   
  93.         // 从request中取得输入流  
  94.         InputStream inputStream = request.getInputStream();  
  95.         // 读取输入流  
  96.         SAXReader reader = new SAXReader();  
  97.         Document document = reader.read(inputStream);  
  98.         // 得到xml根元素  
  99.         Element root = document.getRootElement();  
  100.         // 得到根元素的所有子节点  
  101.         List<Element> elementList = root.elements();  
  102.   
  103.         // 遍历所有子节点  
  104.         for (Element e : elementList)  
  105.             map.put(e.getName(), e.getText());  
  106.   
  107.         // 释放资源  
  108.         inputStream.close();  
  109.         inputStream = null;  
  110.   
  111.         return map;  
  112.     }  
  113.   
  114.     /** 
  115.      * 扩展xstream使其支持CDATA 
  116.      */  
  117.     private static XStream xstream = new XStream(new XppDriver() {  
  118.         public HierarchicalStreamWriter createWriter(Writer out) {  
  119.             return new PrettyPrintWriter(out) {  
  120.                 // 对所有xml节点的转换都增加CDATA标记  
  121.                 boolean cdata = true;  
  122.   
  123.                 @SuppressWarnings("unchecked")  
  124.                 public void startNode(String name, Class clazz) {  
  125.                     super.startNode(name, clazz);  
  126.                 }  
  127.   
  128.                 protected void writeText(QuickWriter writer, String text) {  
  129.                     if (cdata) {  
  130.                         writer.write("<![CDATA[");  
  131.                         writer.write(text);  
  132.                         writer.write("]]>");  
  133.                     } else {  
  134.                         writer.write(text);  
  135.                     }  
  136.                 }  
  137.             };  
  138.         }  
  139.     });  
  140.   
  141.     /** 
  142.      * 文本消息对象转换成xml 
  143.      *  
  144.      * @param textMessage 文本消息对象 
  145.      * @return xml 
  146.      */  
  147.     public static String messageToXml(TextMessage textMessage) {  
  148.         xstream.alias("xml", textMessage.getClass());  
  149.         return xstream.toXML(textMessage);  
  150.     }  
  151.   
  152.     /** 
  153.      * 图片消息对象转换成xml 
  154.      *  
  155.      * @param imageMessage 图片消息对象 
  156.      * @return xml 
  157.      */  
  158.     public static String messageToXml(ImageMessage imageMessage) {  
  159.         xstream.alias("xml", imageMessage.getClass());  
  160.         return xstream.toXML(imageMessage);  
  161.     }  
  162.   
  163.     /** 
  164.      * 语音消息对象转换成xml 
  165.      *  
  166.      * @param voiceMessage 语音消息对象 
  167.      * @return xml 
  168.      */  
  169.     public static String messageToXml(VoiceMessage voiceMessage) {  
  170.         xstream.alias("xml", voiceMessage.getClass());  
  171.         return xstream.toXML(voiceMessage);  
  172.     }  
  173.   
  174.     /** 
  175.      * 视频消息对象转换成xml 
  176.      *  
  177.      * @param videoMessage 视频消息对象 
  178.      * @return xml 
  179.      */  
  180.     public static String messageToXml(VideoMessage videoMessage) {  
  181.         xstream.alias("xml", videoMessage.getClass());  
  182.         return xstream.toXML(videoMessage);  
  183.     }  
  184.   
  185.     /** 
  186.      * 音乐消息对象转换成xml 
  187.      *  
  188.      * @param musicMessage 音乐消息对象 
  189.      * @return xml 
  190.      */  
  191.     public static String messageToXml(MusicMessage musicMessage) {  
  192.         xstream.alias("xml", musicMessage.getClass());  
  193.         return xstream.toXML(musicMessage);  
  194.     }  
  195.   
  196.     /** 
  197.      * 图文消息对象转换成xml 
  198.      *  
  199.      * @param newsMessage 图文消息对象 
  200.      * @return xml 
  201.      */  
  202.     public static String messageToXml(NewsMessage newsMessage) {  
  203.         xstream.alias("xml", newsMessage.getClass());  
  204.         xstream.alias("item"new Article().getClass());  
  205.         return xstream.toXML(newsMessage);  
  206.     }  
  207. }  
 
 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值