柳峰微信公众平台开发教程企业号修改篇(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);
	}
}
时间: 2024-08-07 08:21:43

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

[051] 微信公众平台开发教程第22篇-如何保证access_token长期有效

为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等,开发者在调用这些接口时,都需要传入一个相同的参数access_token,它是公众账号的全局唯一票据,它是接口访问凭证. access_token的有效期是7200秒(两小时),在有效期内,可以一直使用,只有当access_token过期时,才需要再次调用接口获取access_token.在理想情况下,一个7x24小时运行的系统,每天只需

[053] 微信公众平台开发教程第23篇-SAE不支持XStream框架的解决方案

问题描述 最近几天(2014年8月20日之后),突然有不少网友反应,柳峰博客中的微信公众平台开发代码在SAE上运行会报错,或者是能正常部署,但向公众号发消息没反应.以前也有一些初学者质疑过我博客中的代码是否能正常运行,最后都被我一一证明是由于他们的不理解和粗心导致,但这一次短短几天就有很多人反应同样的问题,这就引起了我的足够重视.对于这种"同样的代码以前可以正常运行,现在却不能运行"的问题,我猜测可能是程序运行环境发生了某种变化,应该是SAE近期做了什么更新导致的. 问题分析 如果Ja

微信公众平台开发教程第1篇-新手解惑

1.订阅号与服务号的主要区别是什么?订阅号每天能群发一条消息,没有自定义菜单及高级接口权限(目前 个人.企业订阅号关联腾讯微博认证之后才有自定义菜单):服务号有自定义菜单微信认证之后有高级接口权限,但每月只能群发一条消息. 2.到底该申请订阅号还是服务号?申请哪种类型的公众账号,主要取决于账号的用途.服务号主要面向企业和组织,旨在为用户提供服务:订阅号主要面向媒体和个人,旨在为用户提供信息和资讯. 3.订阅号是否支持编程开发?不管是订阅号,还是服务号,在高级功能中都有编辑模式和开发模式,订阅号也

[051] 微信公众平台开发教程第22篇-怎样保证access_token长期有效

为了使第三方开发人员能够为用户提供很多其它更有价值的个性化服务,微信公众平台开放了很多接口,包含自己定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等,开发人员在调用这些接口时.都须要传入一个同样的參数access_token.它是公众账号的全局唯一票据.它是接口訪问凭证. access_token的有效期是7200秒(两小时),在有效期内.能够一直使用.仅仅有当access_token过期时,才须要再次调用接口获取access_token.在理想情况下,一个7x24小时执行的系

微信公众平台开发教程第23篇-SAE不支持XStream框架的解决方案

问题分析 如 果Java Web项目中使用了日志工具log4j或者slf4j,并且设置了将日志输出到控制台(console),那么在项目部署到SAE之后,可以在SAE网站 的“日志中心”看到应用的相关日志.查看HTTP服务error级别的日志,能够看到如下图所示的错误日志: 为了方便查看和讲解,我对上述错误日志进行了格式化处理,结果如下: [java] view plaincopy 101.226.62.83 [27/Aug/2014:17:23:10 +0800] JAVA_SAE_Fatal

微信公众平台开发教程新手解惑40则

[编者按]由CSDN和<程序员>杂志联合主办的 2014年微信开发者大会 将于8月23日在北京举行,邀请了来自于一线的微信开发商技术负责人或资深工程师从企业应用开发高级篇.智能客服与LBS.微信支付.微信上的HTML5社交应用.微信小店开发等角度为与会者带来实战分享( 议程 ).目前报名处于优惠票价阶段,通过申请加入CSDN CTO俱乐部即可享受8折购票价格(票款中均含午餐),在8月1日前完成付款的同学还将免费获赠微信开发图书一本(两选一,活动现场发放).  值得一提的是,CSDN优秀博主.畅

Senparc.Weixin.MP SDK 微信公众平台开发教程(十五):消息加密

原文:Senparc.Weixin.MP SDK 微信公众平台开发教程(十五):消息加密 前不久,微信的企业号使用了强制的消息加密方式,随后公众号也加入了可选的消息加密选项.目前企业号和公众号的加密方式是一致的(格式会有少许差别). 加密设置 进入公众号后台的“开发者中心”,我们可以看到Url对接的设置: 点击[修改设置],可以进入到修改页面: 加密的方式一共有3种: 明文模式,即原始的消息格式 兼容模式,明文.密文将共存,正式发布的产品不建议使用(因为仍然包含了明文,达不到加密的效果) 安全模

微信公众平台开发教程目录

1.微信公众平台开发教程(一) 微信公众账号注册流程 2.微信公众平台开发教程(二) 基本原理及消息接口 3.微信公众账号开发教程(三) 基础框架搭建 4.微信公众平台开发教程(四) 实例入门:机器人(附源码) 5.微信公众平台开发教程(五)自定义菜单 6.微信公众平台开发教程(六)获取个性二维码 7.微信公众平台开发教程(七)安全策略 8.微信公众平台开发教程(八)Session处理 9.微信公众平台开发教程(九)微信公众平台通用开发框架 10.微信公众平台开发教程(十) 订阅号与服务号的区别

微信公众平台开发教程(八)Session处理

微信公众平台开发教程(八)Session处理 在微信窗口,输入的信息有限,我们需要将一些信息分多次请求. 比如:在进行用户绑定时,我们需要输入用户的相关信息,比如:用户名.密码,或者姓名.电话号码,服务端验证通过,即可将系统用户与微信用户绑定. 然后,此微信账户就有一定的功能权限了,可以查积分,消费记录等.服务号:招商银行信用卡,就有很多功能. 微信客户端无法缓存信息,而且输入信息有限,需要进行多次请求,在服务端保存当前会话状态.这就需要Session. 本文以用户认证,绑定账号为例,来说明具体