最近开始搞搞开放平台,先是TaoBaoSDK,挺好用的基本每碰到什么问题,就是各种没权限。。。
后来搞微信的,第一步卡了我一天。。。年纪大了,反应迟钝啊。。。
首先按照公众号平台里把信息配置好,这里我就不截图了
服务端刚开始调用的是Demo里WXBizMsgCrypt.verifyUrl(msgSignature,timeStamp,nonce,echoStr)
不要问我为什么,因为懒。。。
就是验不通过,经过核对参数、设置编码等等最后发现这个方法不是用来响应服务端验证的,而且Demo里似乎也没有现成的方法。。。。
于是动手开始改了(同学们要相信自己啊):
签名方法:考虑到Demo里那个方法是四个参数的,所以写了一个兼容多个参数的
1/** 2 * 签名数据 3 * @param args 签名数据列表 arg[0]必须为appId 4 * @return 签名结果 5 */ 6public String sign(String... args){ 7 /*** 不用看 begin,就是把第一个参数由appId换成token,因为我这个多账号,否则写死 ****/ 8 String appId = args[0]; 9 AppConf conf = getAppConf(appId); 10 args[0]=conf.getToken(); 11 /*** 不用看 end *****/ 12 13 // 字典序排序,如果有null的这里会报错 14 Arrays.sort(args); 15 // 字符串拼接 16 StringBuffer buffer = new StringBuffer(); 17 for(String str:args){ 18 if(str!=null){ 19 buffer.append(str); 20 } 21 } 22 try{ 23 // SHA1签名生成 24 MessageDigest md = MessageDigest.getInstance("SHA-1"); 25 md.update(buffer.toString().getBytes()); 26 byte[] digest = md.digest(); 27 28 StringBuffer hexstr = new StringBuffer(); 29 String shaHex = ""; 30 for (int i = 0; i < digest.length; i++) { 31 shaHex = Integer.toHexString(digest[i] & 0xFF); 32 if (shaHex.length() < 2) { 33 hexstr.append(0); 34 } 35 hexstr.append(shaHex); 36 } 37 return hexstr.toString(); 38 }catch(Exception ex){ 39 throw new BaseRuntimeException(WXServerError.sign(appId,args)); 40 } 41 42}
调用的地方:
- 签名返回报文,4个参数(不知道是不是这个,反正签了):
String sign =wxMsgCryptService.sign(info.getAppId(),timestamp,nonce,encrypt);
- 响应服务端验证,3个参数(这个是正主):
1if(signature!=null){ 2 String sign = wxMsgCryptService.sign(appId,timestamp,nonce); 3 4 if(sign.equals(signature)){ 5 if(body==null){ 6 return echostr; 7 } 8 }else{ 9 throw new BaseRuntimeException(WXServerError.verify(appId,timestamp,nonce,signature)); 10 } 11}
返回部分:返回是我很费解的地方,可以是字符串、可以是xml、没有异常报文不是200的一律失败,不过好像也没什么不对。
1// 返回的直接是Http Body了 2@ResponseBody 3/** 我习惯了用扩展名来区分响应,微信那边配的是server/{appId}.xml 4 * 这样返回的就是xml了,那天支持json了换json吧 5 */ 6@RequestMapping("server/{appId}.*") 7public Object service( 8 @PathVariable("appId") String appId, 9 @RequestParam(value="signature",required=false)String signature, 10 @RequestParam(value="timestamp",required=false)String timestamp, 11 @RequestParam(value="nonce",required=false)String nonce, 12 @RequestParam(value="echostr",required=false)String echostr, 13 @RequestBody(required=false) String body){ 14 15 logger.info("accept -> signature:{},timestamp:{},nonce:{},echostr:{}",new Object[]{ 16 signature,timestamp,nonce,echostr 17 }); 18 // 配置的时候有三个选项:明文的时候就不验签了,估计不会有人这么配置吧 19 // 接受明文消息时,不进行验签 20 if(signature!=null){ 21 String sign = wxMsgCryptService.sign(appId,timestamp,nonce); 22 23 if(sign.equals(signature)){ 24 if(body==null){ 25 // 响应验证到这里就结束了,返回echostr,微信那边就验证通过了 26 return echostr; 27 } 28 }else{ 29 throw new BaseRuntimeException(WXServerError.verify(appId,timestamp,nonce,signature)); 30 } 31 } 32 33 logger.info("receive->{}",body); 34 35 36 // 以下接受到消息的处理,属于增值包 😜 37 38 MsgCryptInfo info = new MsgCryptInfo(); 39 info.setAppId(appId); 40 info.setNonce(nonce); 41 info.setSignature(signature); 42 info.setTimeStamp(timestamp); 43 // 里面判断要不要解密消息,不要直接返回了 44 WXMessage message = wxMessageService.getMessage(info,body); 45 // 去找对应的消息处理器生成返回消息 46 message = wxMessageService.reply(message); 47 // 无返回消息时 48 if(message==null){ 49 // 官方说返回"success"字串也是成功,我是不会让它走到这个分支的,邪恶啊 50 return DEFAULT_REPLY; 51 } 52 // 接受明文消息时,不进行验签 53 if(signature!=null){ 54 // 消息加密部分,官方的Demo抄下来就好了 55 message = wxMessageService.encryptMessage(info, message); 56 } 57 58 return message; 59}
PS:当时我用verifyURL跑不过验证的时候,我内心时崩溃的,在想难道未认证的订阅号连这个都不让干了。所以我终究还是低估了微信对于个人开发者的友好