`

微信平台的token安全验证(转)

 
阅读更多


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



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



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
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics