手机扫描二维码后关注公众号,发送验证码给公众号,公众号返回验证码,然后输入到网页判断
一、业务需求
手机扫描二维码后关注公众号,发送验证码给公众号,公众号返回验证码,然后输入到网页判断验证码是否正确后通过登录。
二、初步接入微信公众号
接入微信公众平台开发,开发者需要按照如下步骤完成:
1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑
2.1 填写服务器配置
登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。
同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码。
2.2 验证服务器地址有效性
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
| 参数 | 描述 | | --- | --- | | signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 | | timestamp | 时间戳 | | nonce | 随机数 | | echostr | 随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
2.2.1 验证消息的确来自微信服务器 - 代码
token值要与配置服务器时的token一致
我们采取明文模式,不需要encrypt密文参数
signature与sha1加密后的token、timestamp、nonce 进行对比,成功则返回echostr,成为开发者。
private static final String token = "ShiShuoMing";
@GetMapping("callback")
public String callback(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
log.info("get验签请求参数:signature:{},timestamp:{},nonce:{},echostr:{}",
signature, timestamp, nonce, echostr);
String shaStr = SHA1.getSHA1(token, timestamp, nonce, "");
if (signature.equals(shaStr)) {
return echostr;
}
return "unknown";
}
2.2.2 SHA1 加密
@Log4j2
public class SHA1 {
/**
* 用SHA1算法生成安全签名
*
* @param token 票据
* @param timestamp 时间戳
* @param nonce 随机字符串
* @param encrypt 密文
* @return 安全签名
*/
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) {
try {
String[] array = new String[]{token, timestamp, nonce, encrypt}
StringBuffer sb = new StringBuffer()
// 字符串排序
Arrays.sort(array)
for (int i = 0
sb.append(array[i])
}
String str = sb.toString()
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1")
md.update(str.getBytes())
byte[] digest = md.digest()
StringBuffer hexStr = new StringBuffer()
String shaHex = ""
for (int i = 0
shaHex = Integer.toHexString(digest[i] & 0xFF)
if (shaHex.length() < 2) {
hexStr.append(0)
}
hexStr.append(shaHex)
}
return hexStr.toString()
} catch (Exception e) {
log.error("sha加密生成签名失败:", e)
return null
}
}
}
2.2.3 内网穿透
在第一步配置服务器时,除了签名token还需要填写URL,当微信服务器会向URL发送get请求,当验签通过后,我们才可以成为开发者,拥有权限(向用户发送消息等等)。
由于开发环境不方便调试,所以就需要用到natapp内网穿透技术了,把本地ip映射到外网域名地址。
使用natapp进行内网穿透,配置如下:
配置完成后,会得到authtoken, 之后在natapp.exe的文件目录下,cmd输入start natapp -authtoken=xxxx
启动,就可以把本地url地址映射到外网。
以测试号为例,此时URL地址就为:
能够成功填写接口配置信息就代表我们已经成为了开发者(提交信息时,微信服务器会发送请求验签),用户向公众号发送消息时。
2.3 接收普通消息(接收消息、关注、取关等) -- 初探
公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上(还是callback接口)。
请注意:
- 关于重试的消息排重,推荐使用msgid排重。
- 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。详情请见“发送消息-被动回复消息”。
- 如果开发者需要对用户消息在5秒内立即做出回应,即使用“发送消息-被动回复消息”接口向用户被动回复消息时,可以在
公众平台官网的开发者中心处设置消息加密。开启加密后,用户发来的消息和开发者回复的消息都会被加密(但开发者通过客服接口等API调用形式向用户发送消息,则不受影响)。关于消息加解密的详细说明,请见“发送消息-被动回复消息加解密说明”。 各消息类型的推送XML数据包结构如下:
文本消息格式
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
<MsgDataId>xxxx</MsgDataId>
<Idx>xxxx</Idx>
</xml>
| 参数 | 描述 | | --- | --- | | ToUserName | 开发者微信号 | | FromUserName | 发送方账号(一个OpenID) | | CreateTime | 消息创建时间 (整型) | | MsgType | 消息类型,文本为text | | Content | 文本消息内容 | | MsgId | 消息id,64位整型 | | MsgDataId | 消息的数据ID(消息如果来自文章时才有) | | Idx | 多图文时第几篇文章,从1开始(消息如果来自文章时才有) |
参数解读:
- @requestBody: 请求的消息体会放到requestBody里
- @msg_signature: 开启加密模式时,post请求还会发送meg_signature消息用于验签,明文时不会发送
@PostMapping(value = "callback", produces = "application/xml;charset=UTF-8")
public String callback(
@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam(value = "meg_signature", required = false) String msgSignature) {
log.info("接收到微信的请求:request:{}, signature:{},timestamp:{},nonce:{}", requestBody, signature, timestamp, nonce);
return "unknown";
}
2.5 回复消息 -- 初探
我们希望当用户关注后,为用户发送验证码。同样,也应该以XML形式发送,我们要得到两个参数:
ToUserName:发给谁
FromUserName:来自哪(公众号)
回复文本消息格式
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
| 参数 | 是否必须 | 描述 | | --- | --- | --- | | ToUserName | 是 | 接收方账号(收到的OpenID) | | FromUserName | 是 | 开发者微信号 | | CreateTime | 是 | 消息创建时间 (整型) | | MsgType | 是 | 消息类型,文本为text | | Content | 是 | 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) |```less @PostMapping(value = "callback", produces = "application/xml;charset=UTF-8") public String callback( @RequestBody String requestBody, @RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam(value = "meg_signature", required = false) String msgSignature) { log.info("接收到微信的请求:request:{}, signature:{},timestamp:{},nonce:{}", requestBody, signature, timestamp, nonce);
Map<String, String> msgMap = MessageUtil.parseXml(requestBody);
String toUserName = msgMap.get("ToUserName");
String fromUserName = msgMap.get("FromUserName");
String msg = "<xml>n" +
" <ToUserName><![CDATA[" + fromUserName + "]]></ToUserName>n" +
" <FromUserName><![CDATA[" + toUserName + "]]></FromUserName>n" +
" <CreateTime>12345678</CreateTime>n" +
" <MsgType><![CDATA[text]]></MsgType>n" +
" <Content><![CDATA[你好,欢迎来到坤坤Club]]></Content>n" +
"</xml>";
return msg;
}
```