微信公众平台的TOKEN安全验证

来源:互联网 发布:广电网络和移动哪个好 编辑:程序博客网 时间:2024/05/07 19:36

本文目标:学习一种比较安全的服务器间互相验证身份的方式。 

 

问题:开发微信公众平台接口,开发者的服务器为了确保请求是否来自微信服务器,应该如何去做?

 

1)  在微信管理页面上填写URL和TOKEN,开发者服务器上也记录同样的TOKEN。

 

2)  微信服务器发送HTTP请求,附带上参数(注意TOKEN是不会被传输的)

 

参数 描述signature微信加密签名timestamp时间戳nonce随机数echostr随机字符串

 其中signature值通过如下摘要运算得出:

1. 将token、timestamp、nonce三个参数进行字典序排序2. 将三个参数字符串拼接成一个字符串进行sha1加密(这个加密是不可逆的),并将结果的byte[]转换为16进制字符串

 

3)  开发者服务器接收到signature,timestamp,nonce,echostr参数,跟服务器做同样的摘要运算,得到预期的一个signatrue,然后对比微信服务器发送过来的signature参数,如果相同,证明双方的TOKEN是一致的,开发者服务器确实接收到了来自微信服务器的请求,开发者服务器最后返回echostr,以告诉微信服务器接入成功。具体的开发者服务器校验逻辑代码如下显示。

 

 

Java代码  收藏代码
  1. package message;  
  2.   
  3. import java.security.*;  
  4. import java.util.Arrays;  
  5.   
  6. /*** 
  7.  * 微信消息接口认证token摘要类 
  8.  *  
  9.  * 这个摘要类实现为单例,校验一个签名是否合法的例子如下 
  10.  * <pre> 
  11.  * WeixinMessageDigest wxDigest = WeixinMessageDigest.getInstance(); 
  12.  * boolean bValid = wxDigest.validate(signature, timestamp, nonce); 
  13.  * </pre> 
  14.  *  
  15.  *  
  16.  * @author liguocai 
  17.  */  
  18. public final class WeixinMessageDigest {  
  19.       
  20.     /** 
  21.      * 单例持有类 
  22.      * @author liguocai 
  23.      * 
  24.      */  
  25.     private static class SingletonHolder{  
  26.         static final WeixinMessageDigest INSTANCE = new WeixinMessageDigest();  
  27.     }  
  28.       
  29.     /** 
  30.      * 获取单例 
  31.      * @return 
  32.      */  
  33.     public static WeixinMessageDigest getInstance() {  
  34.         return SingletonHolder.INSTANCE;  
  35.     }  
  36.       
  37.     private MessageDigest digest;  
  38.       
  39.     private WeixinMessageDigest() {  
  40.         try {  
  41.             digest = MessageDigest.getInstance("SHA-1");  
  42.         } catch(Exception e) {  
  43.             throw new InternalError("init MessageDigest error:" + e.getMessage());  
  44.         }  
  45.     }  
  46.   
  47.       
  48.   
  49.     /** 
  50.      * 将字节数组转换成16进制字符串 
  51.      * @param b 
  52.      * @return 
  53.      */  
  54.     private static String byte2hex(byte[] b) {  
  55.         StringBuilder sbDes = new StringBuilder();  
  56.         String tmp = null;  
  57.         for (int i = 0; i < b.length; i++) {  
  58.             tmp = (Integer.toHexString(b[i] & 0xFF));  
  59.             if (tmp.length() == 1) {  
  60.                 sbDes.append("0");  
  61.             }  
  62.             sbDes.append(tmp);  
  63.         }  
  64.         return sbDes.toString();  
  65.     }  
  66.       
  67.     private String encrypt(String strSrc) {  
  68.         String strDes = null;  
  69.         byte[] bt = strSrc.getBytes();  
  70.         digest.update(bt);  
  71.         strDes = byte2hex(digest.digest());  
  72.         return strDes;  
  73.     }  
  74.   
  75.     /** 
  76.      * 校验请求的签名是否合法 
  77.      *  
  78.      * 加密/校验流程: 
  79.      * 1. 将token、timestamp、nonce三个参数进行字典序排序 
  80.      * 2. 将三个参数字符串拼接成一个字符串进行sha1加密 
  81.      * 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 
  82.      * @param signature 
  83.      * @param timestamp 
  84.      * @param nonce 
  85.      * @return 
  86.      */  
  87.     public boolean validate(String signature, String timestamp, String nonce){  
  88.         //1. 将token、timestamp、nonce三个参数进行字典序排序  
  89.         String token = getToken();  
  90.         String[] arrTmp = { token, timestamp, nonce };  
  91.         Arrays.sort(arrTmp);  
  92.         StringBuffer sb = new StringBuffer();  
  93.         //2.将三个参数字符串拼接成一个字符串进行sha1加密  
  94.         for (int i = 0; i < arrTmp.length; i++) {  
  95.             sb.append(arrTmp[i]);  
  96.         }  
  97.         String expectedSignature = encrypt(sb.toString());  
  98.         //3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信  
  99.         if(expectedSignature.equals(signature)){  
  100.             return true;  
  101.         }  
  102.         return false;  
  103.     }  
  104.       
  105.     private String getToken(){  
  106.         return "111111";  
  107.     }  
  108.   
  109.     public static void main(String[] args) {  
  110.           
  111.         String signature="f86944503c10e7caefe35d6bc19a67e6e8d0e564";//加密需要验证的签名  
  112.         String timestamp="1371608072";//时间戳  
  113.         String nonce="1372170854";//随机数  
  114.           
  115.         WeixinMessageDigest wxDigest = WeixinMessageDigest.getInstance();  
  116.         boolean bValid = wxDigest.validate(signature, timestamp, nonce);          
  117.         if (bValid) {  
  118.             System.out.println("token 验证成功!");  
  119.         }else {  
  120.             System.out.println("token 验证失败!");  
  121.         }  
  122.     }  
  123.   
  124. }  

 

4) 这个摘要对比的技术,同样适用于单点登录、服务期间互相调用的身份验证,前提是每台服务器都持有相同的TOKEN。此外,有些细节可以优化,例如通过timestamp对签名做超时的处理,超时的签名默认不通过;请求的参数可以加上IP, USERID等额外信息;返回的echostr可以再次与TOKEN做摘要,可以使微信服务器确保接受来自开发者服务器的响应,但是微信服务器没有这么做,也许它本身已经做了足够安全控制。

 

微信消息接口文档:

http://mp.weixin.qq.com/wiki/index.php?title=%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3%E6%8C%87%E5%8D%97

0 0
原创粉丝点击