使用微信JDK实现微信接口签名验证

知道91 | PHP | 2016-12-07 | 阅读:2557

首先确认微信的API只能在微信APP里面使用不能在其他浏览器中使用,所以你的网页都是针对微信网页的,如果不是微信浏览器打开的网页你就应该区别对待。

JSSDK使用步骤

步骤一:绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 微信的开发不能在本地进行,必须在当前配置的域名下,并且必须使用80端口。

备注:登录后可在“开发者中心”查看对应的接口权限。

步骤二:引入JS文件

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js

请注意,如果你的页面启用了https,务必引入 https://res.wx.qq.com/open/js/jweixin-1.0.0.js ,否则将无法在iOS9.0以上系统中成功使用JSSDK

如需使用摇一摇周边功能,请引入 jweixin-1.1.0.js

备注:支持使用 AMD/CMD 标准模块加载方法加载

步骤三:通过config接口注入权限验证配置(重点)

多数微信JDK开发者会卡在这一步。

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

wx.config({    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。    appId: '', // 必填,公众号的唯一标识    timestamp: , // 必填,生成签名的时间戳    nonceStr: '', // 必填,生成签名的随机串    signature: '',// 必填,签名,见附录1    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 });

appId 你容易在你的公众号设置中拿到

timestamp 是当前时间的时间戳,这个你很容易拿到

nonceStr 是一个任意字符串,作为你签名的混淆字符

关键是signature不好拿到。

根据微信JDK的官方文档,你这个签名必须在服务器端完成,而且必须请求微信服务器两次才能拿到。

下面主要介绍怎么取到这个signature。

微信签名的signature怎么通过JDK拿到

jsapi_ticket

生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket

  1. 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):../15/54ce45d8d30b6bf6758f68d2e95bc627.html

成功返回如下JSON:

{ "errcode":0, "errmsg":"ok", "ticket":"sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg", "expires_in":7200 }

获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。

签名算法

签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

即signature=sha1(string1)。

示例:

  • noncestr=Wm3WZYTPz0wzccnW

  • jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg

  • timestamp=1414587457

步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步骤2. 对string1进行sha1签名,得到signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事项

    • 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。

    • 签名用的url必须是调用JS接口页面的完整URL。

    • 出于安全考虑,开发者必须在服务器端实现签名的逻辑

    当你完成了上面的操作你就可以使用微信JDK提供的API了,如果这个通不过其他的你就不要想了。

    下面提供PHP作为后台开发语言下的方法。

    运用PHP对微信JDK API开发的示例

    一共包括两个文件(wx.php和cache.php),其中一个是缓存类,用来缓存access_token和jsapi_ticket。

    wx.php

    <!DOCTYPE html>
     <html>
     <head>
         <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
         <title>微信公众平台JDK API接口开发</title>
     </head>
      <body>
     <?php
     //引入缓存类
     include('cache.php');
     
     //实例化缓存类
     $cache = new Cache();
     
     //获取access_token的方法
     function wx_get_token() {
         global $cache;
     
         $token = $cache->retrieve('access_token');
         if (!$token) {
             //这个地方appid和secret改为公众号的appid和secret
             $res = file_get_contents('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=你的AppId&secret=你的secret');
             $res = json_decode($res, true);
             $token = $res['access_token'];
             $cache->store('access_token', $token, 7200);
         }
         return $token;
     }
     
     //获取jsapi_ticket的方法
     function wx_get_jsapi_ticket(){
         global $cache;
     
         $ticket = "";
         do{
             $ticket = $cache->retrieve('wx_ticket');
             if (!empty($ticket)) {
                 break;
             }
             $token = $cache->retrieve('access_token');
             if (empty($token)){
                 wx_get_token();
             }
             $token = $cache->retrieve('access_token');
             if (empty($token)) {
                 break;
             }
             $url2 = sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi",
                 $token);
             $res = file_get_contents($url2);
             $res = json_decode($res, true);
             $ticket = $res['ticket'];
             $cache->store('wx_ticket', $ticket, 7200);
         }while(0);
         return $ticket;
     }
     ?>
     
     <!--这里是PHP签名-->
     <?php 
       $timestamp = time();
       $wxnonceStr = '2016125162414';
       $wxticket = wx_get_jsapi_ticket();
       //把http://www.zhidao91.com/wx.php地址改为你们分享的地址,一定要全,如果是有参数,也要带上url上需要带上参数。
       //注意顺序一定要按照参数名ASCII 码从小到大排序
       $wxOri = 'jsapi_ticket='.$wxticket.'&noncestr='.$wxnonceStr.'&timestamp='.$timestamp.'&url=http://www.zhidao91.com/wx.php';
     
       //进行sha1签名,获取到微信JDK接口的signature
       $signature = sha1($wxOri);
     ?>
     <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
     <script>
         wx.config({
             debug: false,
             appId: '你的公众号appId', //这里是公众号appId
             timestamp:'<?php echo $timestamp; ?>',
             nonceStr: '<?php echo $wxnonceStr; ?>',
             signature: '<?php echo $signature; ?>',
             jsApiList: [
                 'onMenuShareTimeline',
                 'onMenuShareAppMessage'
               ]
           });
     </script>
     <script>
     // 微信JSSDK开发
     wx.ready(function () {
         // 获取“分享给朋友”按钮点击状态及自定义分享内容接口
         wx.onMenuShareAppMessage({
             title: '分享标题', // 分享标题
             desc: "分享描述", // 分享描述
             link:"http://www.zhidao91.com/wx.php",分享地址
             imgUrl: "http://www.zhidao91.com/wp-content/uploads/2015/08/how-to-write-a-blog.jpg", // 分享图标
             type: 'link', // 分享类型,music、video或link,不填默认为link
         });
     
         // 分享到朋友圈
         wx.onMenuShareTimeline({
           title: '分享标题', // 分享标题
           link: 'http://www.zhidao91.com/wx.php', // 分享地址
           imgUrl: 'http://www.zhidao91.com/wp-content/uploads/2015/08/how-to-write-a-blog.jpg', // 分享的图标
           fail: function (res) {
             //alert(JSON.stringify(res));
           }
         });
     });
     </script>
         </body>
     </html>

    这是使用PHP代码具体实现签名,并且使用微信分享到朋友圈分享给朋友的两个接口的使用方法,其中涉及到的cache.php如下。

    cache.php

    <?php
     
     /**
      * Simple Cache class
      * API Documentation: https://github.com/cosenary/Simple-PHP-Cache
      * 
      * @author Christian Metz
      * @since 22.12.2011
      * @copyright Christian Metz - MetzWeb Networks
      * @version 1.6
      * @license BSD http://www.opensource.org/licenses/bsd-license.php
      */
     
     class Cache {
     
       /**
        * The path to the cache file folder
        *
        * @var string
        */
       private $_cachepath = 'cache/';
     
       /**
        * The name of the default cache file
        *
        * @var string
        */
       private $_cachename = 'default';
     
       /**
        * The cache file extension
        *
        * @var string
        */
       private $_extension = '.cache';
     
       /**
        * Default constructor
        *
        * @param string|array [optional] $config
        * @return void
        */
       public function __construct($config = null) {
         if (true === isset($config)) {
           if (is_string($config)) {
             $this->setCache($config);
           } else if (is_array($config)) {
             $this->setCache($config['name']);
             $this->setCachePath($config['path']);
             $this->setExtension($config['extension']);
           }
         }
       }
     
       /**
        * Check whether data accociated with a key
        *
        * @param string $key
        * @return boolean
        */
       public function isCached($key) {
         if (false != $this->_loadCache()) {
           $cachedData = $this->_loadCache();
           return isset($cachedData[$key]['data']);
         }
       }
     
       /**
        * Store data in the cache
        *
        * @param string $key
        * @param mixed $data
        * @param integer [optional] $expiration
        * @return object
        */
       public function store($key, $data, $expiration = 0) {
         $storeData = array(
           'time'   => time(),
           'expire' => $expiration,
           'data'   => serialize($data)
         );
         $dataArray = $this->_loadCache();
         if (true === is_array($dataArray)) {
           $dataArray[$key] = $storeData;
         } else {
           $dataArray = array($key => $storeData);
         }
         $cacheData = json_encode($dataArray);
         file_put_contents($this->getCacheDir(), $cacheData);
         return $this;
       }
     
       /**
        * Retrieve cached data by its key
        * 
        * @param string $key
        * @param boolean [optional] $timestamp
        * @return string
        */
       public function retrieve($key, $timestamp = false) {
         $cachedData = $this->_loadCache();
         (false === $timestamp) ? $type = 'data' : $type = 'time';
         if (!isset($cachedData[$key][$type])) return null; 
         return unserialize($cachedData[$key][$type]);
       }
     
       /**
        * Retrieve all cached data
        * 
        * @param boolean [optional] $meta
        * @return array
        */
       public function retrieveAll($meta = false) {
         if ($meta === false) {
           $results = array();
           $cachedData = $this->_loadCache();
           if ($cachedData) {
             foreach ($cachedData as $k => $v) {
               $results[$k] = unserialize($v['data']);
             }
           }
           return $results;
         } else {
           return $this->_loadCache();
         }
       }
     
       /**
        * Erase cached entry by its key
        * 
        * @param string $key
        * @return object
        */
       public function erase($key) {
         $cacheData = $this->_loadCache();
         if (true === is_array($cacheData)) {
           if (true === isset($cacheData[$key])) {
             unset($cacheData[$key]);
             $cacheData = json_encode($cacheData);
             file_put_contents($this->getCacheDir(), $cacheData);
           } else {
             throw new Exception("Error: erase() - Key '{$key}' not found.");
           }
         }
         return $this;
       }
     
       /**
        * Erase all expired entries
        * 
        * @return integer
        */
       public function eraseExpired() {
         $cacheData = $this->_loadCache();
         if (true === is_array($cacheData)) {
           $counter = 0;
           foreach ($cacheData as $key => $entry) {
             if (true === $this->_checkExpired($entry['time'], $entry['expire'])) {
               unset($cacheData[$key]);
               $counter++;
             }
           }
           if ($counter > 0) {
             $cacheData = json_encode($cacheData);
             file_put_contents($this->getCacheDir(), $cacheData);
           }
           return $counter;
         }
       }
     
       /**
        * Erase all cached entries
        * 
        * @return object
        */
       public function eraseAll() {
         $cacheDir = $this->getCacheDir();
         if (true === file_exists($cacheDir)) {
           $cacheFile = fopen($cacheDir, 'w');
           fclose($cacheFile);
         }
         return $this;
       }
     
       /**
        * Load appointed cache
        * 
        * @return mixed
        */
       private function _loadCache() {
         if (true === file_exists($this->getCacheDir())) {
           $file = file_get_contents($this->getCacheDir());
           return json_decode($file, true);
         } else {
           return false;
         }
       }
     
       /**
        * Get the cache directory path
        * 
        * @return string
        */
       public function getCacheDir() {
         if (true === $this->_checkCacheDir()) {
           $filename = $this->getCache();
           $filename = preg_replace('/[^0-9a-z\.\_\-]/i', '', strtolower($filename));
           return $this->getCachePath() . $this->_getHash($filename) . $this->getExtension();
         }
       }
     
       /**
        * Get the filename hash
        * 
        * @return string
        */
       private function _getHash($filename) {
         return sha1($filename);
       }
     
       /**
        * Check whether a timestamp is still in the duration 
        * 
        * @param integer $timestamp
        * @param integer $expiration
        * @return boolean
        */
       private function _checkExpired($timestamp, $expiration) {
         $result = false;
         if ($expiration !== 0) {
           $timeDiff = time() - $timestamp;
           ($timeDiff > $expiration) ? $result = true : $result = false;
         }
         return $result;
       }
     
       /**
        * Check if a writable cache directory exists and if not create a new one
        * 
        * @return boolean
        */
       private function _checkCacheDir() {
         if (!is_dir($this->getCachePath()) && !mkdir($this->getCachePath(), 0775, true)) {
           throw new Exception('Unable to create cache directory ' . $this->getCachePath());
         } elseif (!is_readable($this->getCachePath()) || !is_writable($this->getCachePath())) {
           if (!chmod($this->getCachePath(), 0775)) {
             throw new Exception($this->getCachePath() . ' must be readable and writeable');
           }
         }
         return true;
       }
     
       /**
        * Cache path Setter
        * 
        * @param string $path
        * @return object
        */
       public function setCachePath($path) {
         $this->_cachepath = $path;
         return $this;
       }
     
       /**
        * Cache path Getter
        * 
        * @return string
        */
       public function getCachePath() {
         return $this->_cachepath;
       }
     
       /**
        * Cache name Setter
        * 
        * @param string $name
        * @return object
        */
       public function setCache($name) {
         $this->_cachename = $name;
         return $this;
       }
     
       /**
        * Cache name Getter
        * 
        * @return void
        */
       public function getCache() {
         return $this->_cachename;
       }
     
       /**
        * Cache file extension Setter
        * 
        * @param string $ext
        * @return object
        */
       public function setExtension($ext) {
         $this->_extension = $ext;
         return $this;
       }
     
       /**
        * Cache file extension Getter
        * 
        * @return string
        */
       public function getExtension() {
         return $this->_extension;
       }
     
     }

    这是一个PHP的缓存工具类,作为缓存的工具。

    微信JDK接口签名开发总结

    上面的示例是PHP的,如果是其他后台语言只要转换成相应的逻辑就可以实现,注意两点,要使用微信的接口必须在绑定的域名下测试;签名必须先向微信请求到access_token,然后用access_token再去请求jsapi_ticket,最后用jsapi_ticket和相关的参数按照ASCII码从小到大进行sha1排序,最后拿到signature,然后用signature和相关参数去通过config接口(wx.config)注入权限验证配置。