企业微信三方开发(一):回调验证及重要参数获取

您所在的位置:网站首页 微信url地址是什么意思啊怎么设置 企业微信三方开发(一):回调验证及重要参数获取

企业微信三方开发(一):回调验证及重要参数获取

2024-07-11 20:36| 来源: 网络整理| 查看: 265

Git 地址:https://gitee.com/tantantantan/cwp.git ——————————————————————

其他链接

初识微信开发

企业微信三方开发:注册企业微信服务商

企业微信三方开发(一):回调验证及重要参数获取

企业微信三方开发(二):获取access_token

企业微信三方开发(三):网页授权登录

企业微信三方开发(四):发送消息

企业微信三方开发(五):扫码登录

目录 前言技术栈及工具一、后台配置应用回调配置通用开发参数回调配置 二、构建spring-boot项目新建项目项目目录结构导入加解密包 写验证类1、回调验证2、获取suite_ticket及auth_code 总结

前言 我们在新建好一个网页应用后,需要填一些基础配置。其中最重要的就是一些回调配置,回调路径指向我们自己的服务器,需要正确接收响应微信服务器的请求。微信接口开发无非就是通过一些带token的http请求来实现相关功能,而获取这些token往往需要先获取一些重要参数,这里有两个参数需要先获取:suite_ticket 和 auth_code对微信开发不熟,或者没读我之前文章的同学一定要搞清一个概念:在三方应用开发中,应用开发方统称三方服务商,对应id我会称 【服务商corpId】;应用使用方称作 企业,对应id叫 【企业corpId】。请不要搞混了 技术栈及工具 开发框架:spring-boot开发工具: idea 一、后台配置 应用回调配置

首先我们进入新建的应用: 在这里插入图片描述 其中最主要的配置就是这个回调配置: 在这里插入图片描述

共有四个配置项,分别是:

两个回调URLToken(密钥,不可泄漏)EncodingAESKey(加密消息内容的码)

这里的URL就是我们服务器的响应路径,微信服务器会根据这里的URL向我们服务器发送请求,我们服务器验证方面需要做两件事:

分辨出是否为企业微信来源分辨出推送消息的内容是否被篡改

在这里插入图片描述

填写好回调URL,并选择自动生成Token和EncodingAESKey

简单一句话概括就是,在某些时点(具体什么时点或访问哪个回调请看下面 “写VerifyController类” 内容),微信服务器会向我们服务器发送验证请求,我们服务器需要通过Token、EncodingAESKey和服务商CorpID计算Signature与GET请求参数中的Signature作对比,如果一样就验证通过,返回"success"。

具体的加密计算方法不再赘述,自己阅读文档。企业微信为我们集成好了加解密的包,我们拿之即用就行: https://work.weixin.qq.com/api/doc/90000/90138/90307

通用开发参数回调配置

还有一个重要参数需要设置,就是 通用开发参数 里的 系统事件接收URL。 在这里插入图片描述

当 企业 安装应用时,微信服务器会向这个 系统事件接收URL 发送验证请求,我们需要做的响应跟上面的应用回调验证基本一样。 所以我将两者URL、Token、EncodingAESKey设为一样,通过同一个接口来处理这两种请求。

由于后台还未搭建,这些设置都保存不了,接下来我们先搭建后台。

二、构建spring-boot项目

Spring Boot的出现简直就是像我这样的后端渣渣的福音,我们可以很快速的构建一个服务系统。所以我很愉快的选择了它!

还有很重要的一点,本系列文重在打通企业微信的一些功能,以及验证一些逻辑。代码尽量简洁。所以我暂时不会遵循实战项目的设计逻辑,也缺少一些排错处理。请读者知悉!

新建项目

新建一个项目:cwp(company wechat project之意) 并新建一些必要的包和类,具体目录结构如下: 在这里插入图片描述

项目目录结构 新建controller目录:这里暂时不考虑打通数据库,接收请求和数据处理全在controller完成。新建confg目录:存放Constant.java——公用参数。新建 data.properties :通过controller获得的数据存在此文件中。新建 util 目录:存放 PropertiesUtil.java ——此类作用是动态向data.properties写入内容。存放 WxUtil.java——此类存放xml和map互转等方法。 导入加解密包

还记的之前说过的企业微信给的加密计算方法实例吗? 微信准备了xml和json两种解密方式。这里选择Java的xml方式。将验证包中的类全部考入 wechataes包 中: 在这里插入图片描述 同时pom.xml中需要引入commons.codec包

commons-codec commons-codec 1.15 写验证类

此类有两个方法:

doGetValid : 接收验证请求,用于验证通用开发参数系统事件接收URL、数据回调URL、指令回调URL。当企业微信后台录入回调URL点击保存时,微信服务器会立即发送一条GET请求到对应URL,该函数就对URL的signature进行验证。doPostValid: 用于获取 suite_ticket ,以及安装应用时传递过来的 auth_code ,还有用户从企业微信打开应用时也会调用此函数。解密包的第三个参数需做区分:当刷新ticket和安装应用时传递 【SuitID】 ;当打开应用时传递 【CorpID】

再来捋一下,数据回调和指令回调除了保存配置时被访问,还有哪些情况会被微信服务器访问:

数据回调URL: 当每次从企业微信打开应用时。【doPostValid 中的解密参数传递CorpID】指令回调URL: 微信服务器推送suite_ticket以及安装应用时推送auth_code时。【doPostValid 中的解密参数传递SuitID】 1、回调验证

doGetValid方法如下:

package com.tan.cwp.controller; import com.tan.cwp.config.Constant; import com.tan.cwp.wechataes.WXBizMsgCrypt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; @RestController @RequestMapping("/verify") public class VerifyController { Logger logger = LoggerFactory.getLogger(VerifyController.class); /* * 验证通用开发参数及应用回调 */ @RequestMapping(value = "callback_verify.do" ,method = RequestMethod.GET) public void doGetValid(HttpServletRequest request, HttpServletResponse response) throws Exception { // 微信加密签名 String msg_signature = request.getParameter("msg_signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); // 随机字符串 // 如果是刷新,需返回原echostr String echostr = request.getParameter("echostr"); WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Constant.TOKEN, Constant.EncodingAESKey, Constant.CorpID); String sEchoStr=""; //需要返回的明文 PrintWriter out; try { sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr); logger.info("verifyurl echostr: " + sEchoStr); // 验证URL成功,将sEchoStr返回 out = response.getWriter(); out.print(sEchoStr); } catch (Exception e) { //验证URL失败,错误原因请查看异常 e.printStackTrace(); } } }

在后台录入好接口URL保存时,微信后台即会向该接口发送GET请求,并携带四个参数:msg_signature、timestamp、nonce、echostr。

通过 request.getParameter(xxx)获取到他们。

还需要从企业微信后台获取三个固定参数用于计算singnature:TOKEN、EncodingAESKey、CorpID

我把固定参数都放在 配置类Constant 中:

package com.tan.cwp.config; public class Constant { // 服务商相关 /** * 服务商CorpID */ public static final String CorpID = "xxxx"; /** * 服务商身份的调用凭证 */ public static final String ProviderSecret = "xxxx"; // 应用相关 /** * 应用的唯一身份标识 */ public static final String SuiteID = "xxxx"; /** * 应用的调用身份密钥 */ public static final String SuiteSecret = "xxxx"; // 回调相关 /** * 回调/通用开发参数Token, 两者解密算法一样,所以为方便设为一样 */ public static final String TOKEN = "xxxx"; /** * 回调/通用开发参数EncodingAESKey, 两者解密算法一样,所以为方便设为一样 */ public static final String EncodingAESKey = "xxxx"; }

有了这些重要参数就可以通过企业微信准备的包进行验证了。运行项目,并点击申请验证: 在这里插入图片描述 都成功返回 echostr,并显示已验证:

在这里插入图片描述 在这里插入图片描述 通用参数也同样验证成功: 在这里插入图片描述

2、获取suite_ticket及auth_code suite_ticket: 是用于获取 第三方应用凭证(suite_access_token) 的重要参数,由企业微信后台每过十分钟推送一次给 “指令回调URL”auth_code: 是用于获取企业临时授权码的重要参数,由企业安装应用成功时返回

两者获取方式完全相同,微信服务器会通过POST请求向我们服务器传递:msg_signature,timestamp,nonce,echostr四个参数 ,同样通过 request.getParameter(xxx)获取。

同时还会传递一个加密过的xml格式的请求体,通过输入流方式获取该参数,并通过企业微信的验证包进行解密。

具体解密过程查看 doPostValid 方法:

package com.tan.cwp.controller; import com.tan.cwp.config.Constant; import com.tan.cwp.util.PropertiesUtil; import com.tan.cwp.util.WxUtil; import com.tan.cwp.wechataes.WXBizMsgCrypt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Iterator; import java.util.Map; @RestController @RequestMapping("/verify") public class VerifyController { Logger logger = LoggerFactory.getLogger(VerifyController.class); /** * 验证通用开发参数 */ @RequestMapping(value = "callback_verify.do" ,method = RequestMethod.GET) public void doGetValid(HttpServletRequest request, HttpServletResponse response) throws Exception { // 微信加密签名 String msg_signature = request.getParameter("msg_signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); // 随机字符串 // 如果是刷新,需返回原echostr String echostr = request.getParameter("echostr"); WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Constant.TOKEN, Constant.EncodingAESKey, Constant.CorpID); String sEchoStr=""; //需要返回的明文 PrintWriter out; try { sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr); // 验证URL成功,将sEchoStr返回 out = response.getWriter(); out.print(sEchoStr); } catch (Exception e) { //验证URL失败,错误原因请查看异常 e.printStackTrace(); } } /** * 刷新 ticket */ @RequestMapping(value = "callback_verify.do" ,method = RequestMethod.POST) public String doPostValid(HttpServletRequest request) throws Exception { // 微信加密签名 String msg_signature = request.getParameter("msg_signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); String type = request.getParameter("type"); String id = ""; // 访问应用和企业回调传不同的ID if(type.equals("data")){ id = Constant.CorpID; } else { id = Constant.SuiteID; } WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Constant.TOKEN, Constant.EncodingAESKey, id); String postData=""; // 密文,对应POST请求的数据 //1.获取加密的请求消息:使用输入流获得加密请求消息postData ServletInputStream in = request.getInputStream(); BufferedReader reader =new BufferedReader(new InputStreamReader(in)); String tempStr=""; //作为输出字符串的临时串,用于判断是否读取完毕 while(null!=(tempStr=reader.readLine())){ postData+=tempStr; } String suiteXml=wxcpt.DecryptMsg( msg_signature, timestamp, nonce, postData); logger.info("suiteXml: " + suiteXml); Map suiteMap = WxUtil.transferXmlToMap(suiteXml); if(suiteMap.get("SuiteTicket") != null) { PropertiesUtil.setProperty("suite_ticket", (String) suiteMap.get("SuiteTicket")); } else if(suiteMap.get("AuthCode") != null){ PropertiesUtil.setProperty("auth_code", (String) suiteMap.get("AuthCode")); } String success = "success"; return success; } }

注意这段: 在这里插入图片描述 我们在数据回调URL加个参数用于判断是用corpid还是suiteid: 在这里插入图片描述 最终解密过后都会获取到一个字符串类型的xml,我们用 WxUtil类 中的 parseXml方法 将string格式转为map格式:

package com.tan.cwp.util; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; public class WxUtil { /** * 将 Map 转化为 XML * * @param map * @return */ public static String transferMapToXml(SortedMap map) { StringBuffer sb = new StringBuffer(); sb.append(""); for (String key : map.keySet()) { sb.append("") .append(map.get(key)) .append(""); } return sb.append("").toString(); } /** * 将 XML 转化为 map * * @param strxml * @return * @throws IOException */ public static Map transferXmlToMap(String strxml) throws IOException { strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); if (null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder = new SAXBuilder(); Document doc = null; try { doc = builder.build(in); } catch (JDOMException e) { throw new IOException(e.getMessage()); // 统一转化为 IO 异常输出 } // 解析 DOM Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } // 辅助 transferXmlToMap 方法递归提取子节点数据 private static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append(""); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append(""); } } return sb.toString(); } }

里面用到了dom4J,需要在pom.xml中引入:

org.jdom jdom2 2.0.6

由于并不急着打通数据库,这里通过 PropertiesUtil类 动态的读取和写入获取到的参数到 data.properties中:

package com.tan.cwp.util; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.Properties; public class PropertiesUtil { private static Logger logger = LoggerFactory.getLogger(PropertiesUtil.class); private static Properties props; private static String fileName = "data.properties"; private static String path ="D:/Nobug/WeChat/backend/cwp/src/main/resources/data.properties"; static { props = new Properties(); try { props.load(new InputStreamReader(PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName),"UTF-8")); } catch (IOException e) { logger.error("配置文件读取异常",e); } } // 读取参数 public static String getProperty(String key){ String value = props.getProperty(key.trim()); if(StringUtils.isBlank(value)){ return null; } return value.trim(); } // 写入参数 public static String setProperty(String key, String value) throws IOException { props.setProperty(key, value); FileOutputStream file = new FileOutputStream(path); props.store(file,"refresh"); return null; } }

pom.xml需要引入commons-lang3

org.apache.commons commons-lang3 3.5

记住要先在data.properties中放好suite_ticket和auth_code: 在这里插入图片描述 正确获取到参数后,还需要返回success给微信服务器(注意要将"success"字符串复制给一个变量返回,这是一个坑!)

再次运行项目,我们先获取suite_ticket: 在这里插入图片描述 写入成功: 写入成功 注意!!以下安装测试需先通过授权才能安装。授权方法参考下一章[企业微信三方开发(二)的授权配置一节](https://blog.csdn.net/YNEWA/article/details/106885604),建议把下一章全部调通再回过头来测试安装应用!

我们再试下安装测试: 在这里插入图片描述 在这里插入图片描述

通过企业微信扫码安装后,显示安装成功: 在这里插入图片描述 AuthCode也写入: 在这里插入图片描述

总结

在第三方应用开发提供的接口中,主要围绕三种类型的access_token进行开发:

provider_access_token 服务商的tokensuite_access_token 第三方应用的tokenaccess_token 授权企业的token

通过今天的配置和获取到SuiteTicket和AuthCode后,接下来要做的就是通过一系列HTTP请求来愉快获取这些token!

2020/10/7重要更正: 在这里插入图片描述 今天在测试数据回调时发现数据回调后台提示corpid验证失败。原来逻辑是实例化WXBizMsgCrypt类时传递的corpid为服务商的id。通过测试发现要用安装企业的id才行,赶紧回看文档果然有变化。等于说企业微信服务器在访问数据回调url时都会携带上访问的企业id,后台在处理时将corpid取出用作参数就行了! 在这里插入图片描述 首先将数据回调URL加上 &corpid=$CORPID$ ,微信服务器会自动将 $CORPID$ 的内容替换为该企业的corpid,我们后台直接获取该参数就行了。

在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3