当前位置:首页 > 教程 > PHP > 使用微信JDK实现微信接口签名验证

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

首先确认微信的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
  2. 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

成功返回如下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
  • url=http://mp.weixin.qq.com?params=value

步骤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

注意事项

  1. 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
  2. 签名用的url必须是调用JS接口页面的完整URL。
  3. 出于安全考虑,开发者必须在服务器端实现签名的逻辑

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

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

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

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

wx.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<!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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
<?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)注入权限验证配置。

  • << ABP开发指南系列教程(2) – 多层架构体系
  • oracle数据库相关操作注意事项 >>
  • 作者:
    除非注明,本文原创:知道91,欢迎转载!转载请以链接形式注明本文地址,谢谢。
    原文链接:http://www.zhidao91.com/weixin-jdk-signature/

    相关文章 近期热评 最新文章

    • 使用微信JDK实现微信接口签名验证
      要使用微信的接口必须在绑定的域名下测试;签名必须先向微信请求到access_token,然后用access_token再去请求jsapi_ticket,最后用jsapi_ticket和相关的参数按照ASCII码...
    • oracle数据库相关操作注意事项
      修改Oracle SGA(共享内存) 很多网站说修改Oracle的内存通过命令 如果你这么做了,那么恭喜你,你的Oracle数据库无法启动了。如果你已经这么做了,恢复Oracle启动的方...
    • oracle数据库相关操作注意事项
      修改Oracle SGA(共享内存) 很多网站说修改Oracle的内存通过命令 如果你这么做了,那么恭喜你,你的Oracle数据库无法启动了。如果你已经这么做了,恢复Oracle启动的方...
    • 使用微信JDK实现微信接口签名验证
      要使用微信的接口必须在绑定的域名下测试;签名必须先向微信请求到access_token,然后用access_token再去请求jsapi_ticket,最后用jsapi_ticket和相关的参数按照ASCII码...
    • ABP开发指南系列教程(2) – 多层架构...
      为了减少复杂性和提高代码的可重用性,采用分层架构是一种被广泛接受的技术。为了实现分层的体系结构,ABP遵循DDD(领域驱动设计)的原则,将工程分为四个层: 展现层(...
    • ABP开发指南系列教程(1) – 入...
      ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称。 ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WE...
    • Windows下 JIRA + Agile + Mysql 破解...
      本文讲述了Windows下 JIRA + Agile + Mysql 破解安装示例教程
    • c#类的构造函数继承关系示例剖析
      本文通过示例代码讲解了c#子类的构造函数与父类的关系,子类怎样集成父类的构造函数的。