一、简介
微信公众号开发中,JSSDK(JavaScript SDK)是实现微信原生能力的关键。在使用微信分享、拍照、录音、获取地理位置等功能之前,必须通过JSSDK的权限验证配置(即签名算法)来确保安全。
本文以完整的PHP实现为例,详细介绍如何获取微信JSSDK所需要的jsapi_ticket、生成权限签名,以及对常见问题的处理方案。
二、JS-SDK使用权限签名算法概述
签名生成规则如下:参与签名的字段包括noncestr(随机字符串)、jsapi_ticket(有效的票据)、timestamp(时间戳)、url(当前网页的URL,不包含#及其后面部分)。
步骤:
- 对所有待签名参数按照字段名的ASCII码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1
- 所有参数名均为小写字符
- 对string1作sha1加密,字段名和字段值都采用原始值,不进行URL转义
三、完整PHP实现
<?php
/**
* 微信JSSDK页面授权实现类
* 使用方法:实例化此类,然后调用getSignPackage()方法即可
*/
class JSSDK
{
private $appId;
private $appSecret;
public function __construct($appId, $appSecret)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
}
/**
* 获取JSSDK签名包
* 包含appId、nonceStr、timestamp、url、signature等
*/
public function getSignPackage()
{
// 获取jsapi_ticket
$jsapiTicket_data = $this->getJsApiTicket();
// 生成随机字符串
$nonceStr = $this->getNonceStr();
// 当前时间戳
$timestamp = time();
// 当前页面URL(不含#及其后面部分)
$url = $this->getUrl();
if ($jsapiTicket_data['errcode'] == 0) {
$jsapiTicket = $jsapiTicket_data['ticket'];
// 这里参数的顺序要按照key值ASCII码升序排序
$string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url";
$signature = sha1($string);
return array(
"appId" => $this->appId,
"nonceStr" => $nonceStr,
"timestamp" => $timestamp,
"url" => $url,
"signature" => $signature,
"rawString" => $string,
"errcode" => $jsapiTicket_data['errcode'],
"errmsg" => $jsapiTicket_data['errmsg']
);
} else {
return array(
"appId" => $this->appId,
"nonceStr" => $nonceStr,
"timestamp" => $timestamp,
"url" => $url,
"signature" => null,
"rawString" => null,
"errcode" => $jsapiTicket_data['errcode'],
"errmsg" => $jsapiTicket_data['errmsg']
);
}
}
/**
* 获取access_token
* 注意:access_token有效期为7200秒,需要缓存!
*/
private function getAccessToken()
{
$appId = $this->appId;
$appSecret = $this->appSecret;
$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $appId . '&secret=' . $appSecret;
$res = $this->api_request($url);
if (isset($res->access_token)) {
return array(
'errcode' => 0,
'errmsg' => 'success',
'access_token' => $res->access_token,
'expires_in' => $res->expires_in
);
} else {
return array(
'errcode' => $res->errcode,
'errmsg' => $res->errmsg,
'access_token' => null,
'expires_in' => null
);
}
}
/**
* 获取jsapi_ticket
* 有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket
*/
private function getJsApiTicket()
{
$access_token_data = $this->getAccessToken();
if ($access_token_data['errcode'] == 0) {
$access_token = $access_token_data['access_token'];
$url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' . $access_token . '&type=jsapi';
$res = $this->api_request($url);
if ($res->errcode == 0) {
return array(
'errcode' => $res->errcode,
'errmsg' => $res->errmsg,
'ticket' => $res->ticket,
'expires_in' => $res->expires_in
);
} else {
return array(
'errcode' => $res->errcode,
'errmsg' => $res->errmsg,
'ticket' => null,
'expires_in' => null
);
}
} else {
return array(
'errcode' => $access_token_data['errcode'],
'errmsg' => $access_token_data['errmsg'],
'ticket' => null,
'expires_in' => null
);
}
}
/**
* 获取随机字符串nonceStr
*/
private function getNonceStr($length = 16)
{
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$nonceStr = "";
for ($i = 0; $i < $length; $i++) {
$nonceStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $nonceStr;
}
/**
* 获取当前页面URL
* URL不包含#及其后面部分
*/
private function getUrl()
{
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
return $url;
}
/**
* 微信API请求方法
*/
private function api_request($url, $data = null)
{
$ch = curl_init();
$opts = array(
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 500,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_URL => $url,
);
curl_setopt_array($ch, $opts);
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
$output = curl_exec($ch);
if (curl_errno($ch)) {
var_dump(curl_error($ch));
die;
}
curl_close($ch);
$res = json_decode($output);
return $res;
}
}
四、使用示例
<?php
// 配置你的AppID和AppSecret
$appId = '你的AppID';
$appSecret = '你的AppSecret';
// 实例化JSSDK类并获取签名包
$jssdk = new JSSDK($appId, $appSecret);
$signPackage = $jssdk->getSignPackage();
?>
<!-- 页面引入微信JSSDK -->
[removed][removed]
[removed]
wx.config({
debug: false, // 调试模式
appId: '<?php echo $signPackage["appId"]; ?>',
timestamp: <?php echo $signPackage["timestamp"]; ?>,
nonceStr: '<?php echo $signPackage["nonceStr"]; ?>',
signature: '<?php echo $signPackage["signature"]; ?>',
jsApiList: [
'updateAppMessageShareData', // 更新分享给朋友
'updateTimelineShareData', // 更新分享到朋友圈
'chooseImage', // 拍照或从相册选图
'getLocation', // 获取地理位置
'scanQRCode' // 扫一扫
]
});
wx.ready(function () {
console.log('JSSDK配置成功');
// 分享配置
wx.updateAppMessageShareData({
title: '分享标题',
desc: '分享描述',
link: [removed].href,
imgUrl: '分享图标URL',
success: function () {
console.log('分享设置成功');
}
});
wx.updateTimelineShareData({
title: '朋友圈标题',
link: [removed].href,
imgUrl: '分享图标URL',
success: function () {
console.log('朋友圈设置成功');
}
});
});
wx.error(function (res) {
console.log('JSSDK配置失败:' + JSON.stringify(res));
});
[removed]
五、重要注意事项
5.1 缓存机制(必须实现)
access_token和jsapi_ticket有效期均为7200秒,微信接口调用次数有限制,频繁获取会被限流!
推荐缓存方案:
<?php
/**
* 带缓存的JSSDK实现示例
*/
class JSSDKWithCache extends JSSDK
{
private $cacheFile = '/tmp/jssdk_cache.json';
/**
* 从缓存读取access_token,不存在或过期则重新获取
*/
public function getAccessTokenWithCache()
{
$cache = $this->readCache();
if (isset($cache['access_token']) && $cache['expires_at'] > time()) {
return $cache['access_token'];
}
// 重新获取并缓存
$data = parent::getAccessToken();
if ($data['errcode'] == 0) {
$this->writeCache(array(
'access_token' => $data['access_token'],
'expires_at' => time() + $data['expires_in'] - 200 // 提前200秒过期
));
return $data['access_token'];
}
return null;
}
/**
* 读取缓存文件
*/
private function readCache()
{
if (file_exists($this->cacheFile)) {
return json_decode(file_get_contents($this->cacheFile), true);
}
return array();
}
/**
* 写入缓存文件
*/
private function writeCache($data)
{
file_put_contents($this->cacheFile, json_encode($data));
}
}
5.2 JS接口安全域名设置
在调用JSSDK之前,必须先在微信公众平台完成以下配置:
- 登录微信公众平台:https://mp.weixin.qq.com
- 进入"设置与开发" → "基本配置" → "JS接口安全域名"
- 设置要使用的域名(不带http://,不带路径)
- 点击"添加"保存
5.3 URL签名注意事项
- 签名用的url必须是调用JS接口页面的完整URL
- URL不包含#及其后面部分,需要在JavaScript中通过
location.href.split('#')[0]获取 - 如果是动态页面,需要用PHP动态获取当前URL
- 页面一旦分享,微信客户端会在链接末尾加入其他参数,必须动态获取当前链接
六、常见问题及解决
6.1 错误:config:invalid signature
原因:签名生成错误
排查步骤:
- 确认jsapi_ticket是否有效且未过期
- 确认url是否包含#及后面的内容
- 确认nonceStr与wx.config中的nonceStr完全一致
- 确认生成签名的url与当前页面url一致
- 确认nonceStr和timestamp是大小写敏感
- 使用开发者工具或打日志输出签名信息,与官方签名校验工具对比
6.2 错误:config:url not in whitelist
原因:调用JSSDK的页面域名未在JS接口安全域名中配置
解决:在微信公众平台的"JS接口安全域名"中添加对应域名
6.3 无法调用微信JS接口
可能原因:
- 引入了错误的JSSDK文件(建议使用官方CDN)
- jsApiList中未包含要调用的接口名称
- 未通过wx.ready回调调用接口
6.4 分享链接签名失败
原因:用户分享后微信会在URL末尾追加参数
解决:对于Vue/SPA等单页应用,必须在每次页面切换时重新签名,或通过前端动态获取URL传到后端进行签名
// Vue项目中动态获取URL进行签名的方式
wx.ready(function () {
// 异步加载的页面,需要重新配置
wx.config({
/* ... */
});
});
// 或者在页面路由变化时重新获取签名
axios.post('/jssdk/sign', { url: location.href.split('#')[0] })
.then(function (res) {
wx.config({
appId: res.data.appId,
timestamp: res.data.timestamp,
nonceStr: res.data.nonceStr,
signature: res.data.signature,
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
});
});
七、完整流程图
JSSDK签名的完整流程如下:
- 前端 → 访问微信页面
- 后端 → 获取access_token(使用AppID和AppSecret调用微信接口)
- 后端 → 使用access_token获取jsapi_ticket
- 后端 → 使用jsapi_ticket、随机字符串、时间戳、URL生成签名
- 后端 → 将签名包(appId、nonceStr、timestamp、signature、url)返回前端
- 前端 → 使用wx.config注入配置信息
- 前端 → 在wx.ready中调用微信JSSDK接口
八、总结
微信JSSDK页面授权(签名算法)是微信公众号H5开发的核心环节,需要注意以下关键点:
- access_token和jsapi_ticket必须缓存,避免频繁调用接口被封禁
- 签名URL必须与调用页面的完整URL完全一致
- 必须在微信公众平台设置JS接口安全域名
- 所有签名参数和wx.config中的参数必须完全一致
- 在wx.ready回调中调用JSSDK接口,不要在其他位置调用

发表评论 取消回复