用spring搭建微信公众号开发者模式下服务器处理用户消息的加密传输构架(java)

    要搭建加密传输的微信公众号消息传输,首先要在开发这平台下载一下微信加密的相关jar包,并做一些准备。准备的步骤如下:


1.打开开发者文档,找到消息加减密--->接入指引,如下图所示:



2.在页面底部找到实例代码,并下载解压,下载的地方如下图所示:


3.在解压的地方找到java版本,并做如下准备:

(1)将commons-codec-1.9.jar导入到项目中。



(2)将dist下的aes-jre1.6.jar导入到项目中。


(3)打开readme.txt,按照他的指示操作。红色对勾的地方很重要。


要下载的东西就是下图标记的地方


这样准备工作就完成了,可以进入正式的代码编写了。


代码部分的解释基本都放在注释中,首先开用来核心控制的controller。内容如下:

package org.calonlan.soulpower.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.calonlan.soulpower.service.WeiInfoService;
import org.calonlan.soulpower.util.AesUtils;
import org.calonlan.soulpower.util.MessageUtil;
import org.calonlan.soulpower.util.SignUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * @author Administrator
 *         这个controller是用来处理消息的核心controller,在这里有两个请求地址都为/sign的请求地址,一个是用get方式,用来
 *         做服务器和微信服务器之间的认证;一个是post方式,用来在服务器和微信服务器之间传递消息。
 */
@Controller
@RequestMapping("/core")
public class CoreController {
	@Resource
	private WeiInfoService weiInfoService;/*
										 * 这个service大家可以不用管,
										 * 因为在我的项目中我是把所有的微信相关的配置信息都保存在一个weiInfo的类中了
										 * ,这里只会用到token。
										 */

	
	/**
	 * @param request
	 * @param response
	 * @throws IOException
	 * @throws ServletException
	 * 用来和微信服务器之间相互认证
	 */
	@RequestMapping(method = RequestMethod.GET, value = "/sign")
	public void goGet(HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {
		String signature = request.getParameter("signature");// 获得signature
		String timestamp = request.getParameter("timestamp");// 获得timestamp
		String nonce = request.getParameter("nonce");// 获得nonce
		String echostr = request.getParameter("echostr");// 获得echostr
		/*
		 * 上面的四个参数都是微信服务器发送过来的,其中signature、timestamp、nonce是要参与服务器的验证的,
		 * 而echostr是在我们通过验证后返回给服务器告诉服务器我们就是要通讯 的那个远程服务器
		 */
		PrintWriter out = response.getWriter();
		if (SignUtil.checkSignature(signature, timestamp, nonce,
				weiInfoService.get())) {//在SignUtil中使用checkSignature来进行验证,代码附在后面。
			out.print(echostr);//验证通过后就把echostr返回给微信服务器。
		}
		out.close();
		out = null;
	}

	
	
	@RequestMapping(method = RequestMethod.POST, value = "/sign")
	/**
	 * 
	 * 用来和微信服务器通信
	 * 
	 * */
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");

		/** 进行消息分发 */
		/* 首先定义一个空的回复消息 */
		String respMessage = "";

		try {
			/* 从请求中获得xml格式的信息。并转化为map类型 */
			Map<String, String> requestMapSecret = MessageUtil
					.parseXml(request);//从request中获得获得xml,并解析,代码附在后面
			/* 获得解密后的消息正文 */
			String mingwenXML = AesUtils.descMessage(//用微信官方给的jar包中的AesUtils来对消息进行解密
					requestMapSecret.get("Encrypt"),/* 加密的消息体 */
					request.getParameter("msg_signature"),/* 请求中的消息签名 */
					request.getParameter("timestamp"),/* 时间戳 */
					request.getParameter("nonce"));/* 无序数列 */
			/* 将明文再次进行xml解析 */
			Map<String, String> requestMap = MessageUtil
					.parseXml(new StringReader(mingwenXML));//将明文的xml再次解析后放入map中,
			/** 获得用户发来的消息类型,并做相应的处理 */
			String messageType = requestMap.get("MsgType");
			System.out.println(messageType);
			
			/*处理不同格式的消息类型开始-------------------------------------------------------*/
			// 用户发来的是文本消息
			if (messageType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
				System.out.println(requestMap.get("Content"));
			}
			// 用户发来的是图片消息
			else if (messageType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {

			}
			// 用户发来地理位置信息
			else if (messageType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {

			}
			// 用户发来链接消息
			else if (messageType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {

			}
			// 用户发来音频消息
			else if (messageType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {

			}
			/** 事件推送的处理 */
			else if (messageType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
				// 事件类型
				String eventType = requestMap.get("Event");
				// 订阅
				if (eventType.equals(MessageUtil.REQ_MESSAGE_TYPE_SUBSCRIBE)) {

				}
				// 取消订阅
				else if (eventType
						.equals(MessageUtil.REQ_MESSAGE_TYPE_UNSUBSCRIBE)) {
				}
				// 点击按钮事件
				else if (eventType.equals(MessageUtil.REQ_MESSAGE_TYPE_CLICK)) {

				}
			}
			/*处理不同格式的消息类型介绍-------------------------------------------------------*/
			/*对于不同类型的消息的处理网上有很多高手已经发表了很多文章了,我自己也会总结一下,不过不是在这里---------*/
			// 给返回的消息加密
			AesUtils.aescMessage(respMessage,
					request.getParameter("timestamp"),
					request.getParameter("nonce"));
		} catch (Exception e) {
			e.printStackTrace();
		}

		/*返回消息给微信服务器,然后微信服务器把消息转发给用户···额,貌似我们聊什么,微信服务器都是可以截获的,*/
		PrintWriter out = response.getWriter();
		out.print(respMessage);
		out.close();
	}
}

SignUtil的checkSignature代码如下:

	public static boolean checkSignature(String signature, String timestamp,
			String nonce, WeiInfo info) {
		/*
		 * 微信服务器发送过来的signature是通过某些处理然后进行SHA1加密的,我们来用它发过来的信息自己生成一个signature,
		 * 然后两者之间进行比对,一致的话我们就是伙伴,不一致就拒绝它
		 */
		String[] arr = new String[] { info.getToken(), timestamp, nonce };
		/* token是我们自己填写的,具体填写位置见下图;timestamp、nonce是微信服务器发送过来的,这里我们把他们都放到数组中。*/
		Arrays.sort(arr);//对数组中的数据进行字典排序。。。。要不然加密出来的东西是没用的
		StringBuilder content = new StringBuilder();
		for (int i = 0; i < arr.length; i++) {
			content.append(arr[i]);
		}//把字典排序好的数组读取成字符串。
		MessageDigest md = null;
		String tmpStr = null;

		try {/*进行sha1加密*/
			md = MessageDigest.getInstance("SHA-1");
			byte[] digest = md.digest(content.toString().getBytes());
			tmpStr = byteToStr(digest);//将加密后的byte转化为16进制字符串,这就是我们自己构造的signature
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}

		content = null;

		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;//进行对比,相同返回true,不同返回false
	}

	private static String byteToStr(byte[] byteArray) {
		String strDigest = "";
		for (int i = 0; i < byteArray.length; i++) {
			strDigest += byteToHexStr(byteArray[i]);//分别把没一个byte位转换成一个16进制字符,代码见下面
		}
		return strDigest;
	}

	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];
		String s = new String(tempArr);
		return s;
	}

AesUtils的具体代码

package org.calonlan.soulpower.util;

import org.calonlan.soulpower.dao.impl.WeiInfoDaoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AesUtils {

	private static ApplicationContext applicationContext;

	/*
	 * 这里我直接使用的spring来读取weiInfo对象中的加解密密钥的内容,大家可以不用这样,直接把密钥放在这里就可以了,
	 * 也可以放在properties文件中读取更方便
	 */

	public static String descMessage(String encrypt, String msgSignature,
			String timestamp, String nonce) throws AesException {
		applicationContext = new ClassPathXmlApplicationContext(
				"config/application-context.xml");
		WeiInfoDaoImpl daoImpl = (WeiInfoDaoImpl) applicationContext
				.getBean("weiInfoDao");
		String token = daoImpl.get().getToken();//获得token
		String encodingAesKey = daoImpl.get().getEncodingAesKey();//获得加解密密钥
		String appId = daoImpl.get().getAppId();//获得appid,这个在开发者模式下就能看到,填写你自己的。
		String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
		String fromXML = String.format(format, encrypt);
		WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
		String result = pc.decryptMsg(msgSignature, timestamp, nonce, fromXML);
		return result;
	}

	
	/*加密和解密类似,就不做注释了*/
	public static String aescMessage(String replyMsg, String timestamp,
			String nonce) throws AesException {
		applicationContext = new ClassPathXmlApplicationContext(
				"config/application-context.xml");
		WeiInfoDaoImpl daoImpl = (WeiInfoDaoImpl) applicationContext
				.getBean("weiInfoDao");
		String token = daoImpl.get().getToken();
		String encodingAesKey = daoImpl.get().getEncodingAesKey();
		String appId = daoImpl.get().getAppId();
		WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
		String mingwen = pc.encryptMsg(replyMsg, timestamp, nonce);
		return mingwen;
	}
}

MessageUtil中的parseXml代码,有两个,一个是用来直接解析request中的xml,一个是用来解析字符串类型的xml


解析request中的xml代码

	public static Map<String, String> parseXml(HttpServletRequest request)
			throws Exception {
		Map<String, String> map = new HashMap<String, String>();

		InputStream inputStream = request.getInputStream();
		SAXReader reader = new SAXReader();//我用的是SAXReader
		Document document = reader.read(inputStream);

		Element root = document.getRootElement();

		@SuppressWarnings("unchecked")
		List<Element> elementList = root.elements();

		for (Element e : elementList) {
			map.put(e.getName(), e.getText());
		}

		inputStream.close();
		inputStream = null;

		return map;
	}


解析字符串中的xml代码

	public static Map<String, String> parseXml(StringReader readers) {
		Map<String, String> map = new HashMap<String, String>();

		SAXReader reader = new SAXReader();
		Document document = null;
		try {
			InputSource inputSource = new InputSource(readers);
			document = reader.read(inputSource);
		} catch (DocumentException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		Element root = document.getRootElement();

		@SuppressWarnings("unchecked")
		List<Element> elementList = root.elements();

		for (Element e : elementList) {
			map.put(e.getName(), e.getText());
		}
		return map;
	}


到这里,构建就完成了,可以用我们的服务器来和微信服务器进行密文的消息传递了。
posted @ 2015-12-03 10:46  qz程程  阅读(697)  评论(0编辑  收藏  举报