您的位置:首页 > 移动开发 > 微信开发

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

2016-06-21 15:40 681 查看
本文目标:学习一种比较安全的服务器间互相验证身份的方式。 

 

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

 

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代码  


package message;  

  

import java.security.*;  

import java.util.Arrays;  

  

/*** 

 * 微信消息接口认证token摘要类 

 *  

 * 这个摘要类实现为单例,校验一个签名是否合法的例子如下 

 * <pre> 

 * WeixinMessageDigest wxDigest = WeixinMessageDigest.getInstance(); 

 * boolean bValid = wxDigest.validate(signature, timestamp, nonce); 

 * </pre> 

 *  

 *  

 * @author liguocai 

 */  

public final class WeixinMessageDigest {  

      

    /** 

     * 单例持有类 

     * @author liguocai 

     * 

     */  

    private static class SingletonHolder{  

        static final WeixinMessageDigest INSTANCE = new WeixinMessageDigest();  

    }  

      

    /** 

     * 获取单例 

     * @return 

     */  

    public static WeixinMessageDigest getInstance() {  

        return SingletonHolder.INSTANCE;  

    }  

      

    private MessageDigest digest;  

      

    private WeixinMessageDigest() {  

        try {  

            digest = MessageDigest.getInstance("SHA-1");  

        } catch(Exception e) {  

            throw new InternalError("init MessageDigest error:" + e.getMessage());  

        }  

    }  

  

      

  

    /** 

     * 将字节数组转换成16进制字符串 

     * @param b 

     * @return 

     */  

    private static String byte2hex(byte[] b) {  

        StringBuilder sbDes = new StringBuilder();  

        String tmp = null;  

        for (int i = 0; i < b.length; i++) {  

            tmp = (Integer.toHexString(b[i] & 0xFF));  

            if (tmp.length() == 1) {  

                sbDes.append("0");  

            }  

            sbDes.append(tmp);  

        }  

        return sbDes.toString();  

    }  

      

    private String encrypt(String strSrc) {  

        String strDes = null;  

        byte[] bt = strSrc.getBytes();  

        digest.update(bt);  

        strDes = byte2hex(digest.digest());  

        return strDes;  

    }  

  

    /** 

     * 校验请求的签名是否合法 

     *  

     * 加密/校验流程: 

     * 1. 将token、timestamp、nonce三个参数进行字典序排序 

     * 2. 将三个参数字符串拼接成一个字符串进行sha1加密 

     * 3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信 

     * @param signature 

     * @param timestamp 

     * @param nonce 

     * @return 

     */  

    public boolean validate(String signature, String timestamp, String nonce){  

        //1. 将token、timestamp、nonce三个参数进行字典序排序  

        String token = getToken();  

        String[] arrTmp = { token, timestamp, nonce };  

        Arrays.sort(arrTmp);  

        StringBuffer sb = new StringBuffer();  

        //2.将三个参数字符串拼接成一个字符串进行sha1加密  

        for (int i = 0; i < arrTmp.length; i++) {  

            sb.append(arrTmp[i]);  

        }  

        String expectedSignature = encrypt(sb.toString());  

        //3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信  

        if(expectedSignature.equals(signature)){  

            return true;  

        }  

        return false;  

    }  

      

    private String getToken(){  

        return "111111";  

    }  

  

    public static void main(String[] args) {  

          

        String signature="f86944503c10e7caefe35d6bc19a67e6e8d0e564";//加密需要验证的签名  

        String timestamp="1371608072";//时间戳  

        String nonce="1372170854";//随机数  

          

        WeixinMessageDigest wxDigest = WeixinMessageDigest.getInstance();  

        boolean bValid = wxDigest.validate(signature, timestamp, nonce);          

        if (bValid) {  

            System.out.println("token 验证成功!");  

        }else {  

            System.out.println("token 验证失败!");  

        }  

    }  

  

}  

 

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: