一、简介

微信公众号开发中,微信JS-SDK是实现自定义分享功能的核心技术。在使用微信分享、拍照、录音、获取地理位置等功能之前,必须通过权限验证配置(签名算法)来确保安全。

本文以完整的PHP实现为例,详细介绍如何获取微信JSSDK所需要的jsapi_ticket、生成权限签名,以及对常见问题的处理方案。

二、JS-SDK使用权限签名算法概述

签名生成规则如下:参与签名的字段包括noncestr(随机字符串)、jsapi_ticket(有效的票据)、timestamp(时间戳)、url(当前网页的URL,不包含#及其后面部分)。

签名步骤:

  1. 对所有待签名参数按照字段名的ASCII码从小到大排序(字典序)
  2. 使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1
  3. 所有参数名均为小写字符
  4. 对string1作sha1加密,字段名和字段值都采用原始值,不进行URL转义

三、完整PHP实现

3.1 微信JSSDK类(Wxsdk)

<?php
/**
 * 微信JSSDK分享接口实现类
 * 用于获取签名包,供前端wx.config使用
 */
class Wxsdk
{
    private $appId;
    private $appSecret;

    /**
     * 构造函数
     * @param string $appId 公众号AppID
     * @param string $appSecret 公众号AppSecret
     */
    public function __construct($appId, $appSecret)
    {
        $this->appId = $appId;
        $this->appSecret = $appSecret;
    }

    /**
     * 获取JSSDK签名包
     * @param string $url 当前页面URL(可选,不传则自动获取)
     * @return array 签名信息数组
     */
    public function getSignPackage($url = '')
    {
        // 获取jsapi_ticket
        $jsapiTicket = $this->getJsApiTicket();
        // 生成时间戳
        $timestamp = time();
        // 生成随机字符串
        $nonceStr = $this->createNonceStr();
        // 获取当前页面URL
        if (empty($url)) {
            $url = $this->getCurrentUrl();
        } else {
            // 解码前端传递过来的URL
            $url = urldecode($url);
        }

        // 这里参数的顺序要按照key值ASCII码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url";
        $signature = sha1($string);

        return [
            "appId"     => $this->appId,
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => $signature,
            "rawString" => $string
        ];
    }

    /**
     * 生成随机字符串
     * @param int $length 字符串长度
     * @return string
     */
    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 获取jsapi_ticket
     * 有效期7200秒,需要全局缓存
     * @return string
     */
    private function getJsApiTicket()
    {
        // jsapi_ticket 应该全局存储与更新,以下代码以写入到文件中做示例
        $data = json_decode(file_get_contents("jssdk/jsapi_ticket.json"));
        if ($data->expire_time < time xss=removed>getAccessToken();
            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" . $accessToken . "&type=jsapi";
            $res = json_decode($this->httpGet($url));
            $ticket = isset($res->ticket) ? $res->ticket : null;
            if ($ticket) {
                $data->expire_time = time() + 7000;
                $data->jsapi_ticket = $ticket;
                file_put_contents("jssdk/jsapi_ticket.json", json_encode($data));
            }
        } else {
            $ticket = $data->jsapi_ticket;
        }
        return $ticket;
    }

    /**
     * 获取access_token
     * 有效期7200秒,需要全局缓存
     * @return string
     */
    private function getAccessToken()
    {
        // access_token 应该全局存储与更新,以下代码以写入到文件中做示例
        $data = json_decode(file_get_contents("jssdk/access_token.json"));
        if ($data->expire_time < time url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=">appId . "&secret=" . $this->appSecret;
            $res = json_decode($this->httpGet($url));
            $access_token = isset($res->access_token) ? $res->access_token : null;
            if ($access_token) {
                $data->expire_time = time() + 7000;
                $data->access_token = $access_token;
                file_put_contents("jssdk/access_token.json", json_encode($data));
            }
        } else {
            $access_token = $data->access_token;
        }
        return $access_token;
    }

    /**
     * 获取当前页面完整URL
     * @return string
     */
    private function getCurrentUrl()
    {
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        return "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
    }

    /**
     * HTTP GET请求
     * @param string $url 请求地址
     * @return string
     */
    private function httpGet($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        $output = curl_exec($ch);
        if (curl_errno($ch)) {
            die('Curl error: ' . curl_error($ch));
        }
        curl_close($ch);
        return $output;
    }
}

3.2 初始化JSON缓存文件

在jssdk目录下创建两个JSON缓存文件:

access_token.json

{
    "access_token": "",
    "expire_time": 0
}

jsapi_ticket.json

{
    "jsapi_ticket": "",
    "expire_time": 0
}

四、前后端整合使用

4.1 后端接口(签名接口)

<?php
// sign.php - 前端通过AJAX请求获取签名
require_once 'Wxsdk.php';

// 配置你的AppID和AppSecret
$appId = '你的AppID';
$appSecret = '你的AppSecret';

$wxsdk = new Wxsdk($appId, $appSecret);

// 获取前端传递过来的当前页面URL
$url = isset($_POST['url']) ? $_POST['url'] : '';

// 获取签名包
$signPackage = $wxsdk->getSignPackage($url);

// 返回JSON
header('Content-Type: application/json');
echo json_encode([
    'code' => 1,
    'msg' => 'success',
    'data' => $signPackage
]);

4.2 前端JavaScript代码

<!-- 引入微信JSD -->
[removed][removed]
[removed]
// 页面加载时获取签名配置
jQuery.post("/sign.php", {
    url: encodeURIComponent[removed].href.split('#')[0]),
    t: new Date().getTime()
}, function(result) {
    if (result.code != 1) {
        console.error('获取签名失败');
        return;
    }
    
    var sign = result.data;
    
    wx.config({
        debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来
        appId: sign.appId, // 必填,公众号的唯一标识
        timestamp: sign.timestamp, // 必填,生成签名的时间戳
        nonceStr: sign.nonceStr, // 必填,生成签名的随机串
        signature: sign.signature, // 必填,签名
        jsApiList: [
            'updateAppMessageShareData', // 分享给朋友(新接口)
            'updateTimelineShareData',   // 分享到朋友圈(新接口)
            'onMenuShareAppMessage',     // 分享给朋友(旧接口,兼容)
            'onMenuShareTimeline',       // 分享到朋友圈(旧接口,兼容)
            'onMenuShareQQ',             // 分享到QQ
            'onMenuShareWeibo',          // 分享到微博
            'onMenuShareQZone'           // 分享到QQ空间
        ]
    });

    // 配置成功后执行
    wx.ready(function() {
        console.log('JSSDK配置成功');
        
        // 自定义分享给朋友
        wx.updateAppMessageShareData({
            title: '自定义分享标题', // 分享标题
            desc: '自定义分享描述', // 分享描述
            link: [removed].href, // 分享链接
            imgUrl: 'https://example.com/share-icon.png', // 分享图标
            success: function() {
                console.log('分享设置成功');
            }
        });
        
        // 自定义分享到朋友圈
        wx.updateTimelineShareData({
            title: '朋友圈分享标题', // 分享标题
            link: [removed].href, // 分享链接
            imgUrl: 'https://example.com/share-icon.png', // 分享图标
            success: function() {
                console.log('朋友圈设置成功');
            }
        });
    });

    // 配置失败
    wx.error(function(res) {
        console.log('JSSDK配置失败:' + JSON.stringify(res));
    });
}, 'json');

五、重要注意事项

5.1 缓存机制(必须实现)

access_tokenjsapi_ticket有效期均为7200秒,微信接口调用次数有限制,频繁获取会被限流!

推荐缓存方案:

  • 文件缓存:如上文示例,将token写入JSON文件
  • Redis缓存:适合高并发场景,设置过期时间为7000秒
  • 数据库缓存:适合分布式系统

5.2 JS接口安全域名设置

在调用JSSDK之前,必须先在微信公众平台完成以下配置:

  1. 登录微信公众平台:https://mp.weixin.qq.com
  2. 进入"设置与开发" → "基本配置" → "JS接口安全域名"
  3. 设置要使用的域名(不带http://,不带路径)
  4. 点击"添加"保存

5.3 URL签名注意事项

  • 签名用的url必须是调用JS接口页面的完整URL
  • URL不包含#及其后面部分,需要在JavaScript中通过location.href.split('#')[0]获取
  • 如果是动态页面,需要用PHP动态获取当前URL
  • 页面一旦分享,微信客户端会在链接末尾加入其他参数,必须动态获取当前链接
  • 前端传递URL时需要encodeURIComponent编码,后端用urldecode解码

5.4 IP白名单配置

如果服务器有IP白名单限制,需要将服务器IP添加到微信公众平台的IP白名单中:

  1. 进入"基本配置" → "IP白名单"
  2. 添加服务器公网IP地址

六、常见问题及解决

6.1 错误:config:invalid signature

原因:签名生成错误

排查步骤:

  1. 确认jsapi_ticket是否有效且未过期
  2. 确认url是否包含#及后面的内容
  3. 确认nonceStr与wx.config中的nonceStr完全一致
  4. 确认生成签名的url与当前页面url一致
  5. 确认nonceStr和timestamp是大小写敏感
  6. 使用开发者工具或打日志输出签名信息,与官方签名校验工具对比

6.2 错误:config:url not in whitelist

原因:调用JSSDK的页面域名未在JS接口安全域名中配置

解决:在微信公众平台的"JS接口安全域名"中添加对应域名

6.3 无法调用微信JS接口

可能原因:

  • 引入了错误的JSSDK文件(建议使用官方CDN)
  • jsApiList中未包含要调用的接口名称
  • 未通过wx.ready回调调用接口
  • AppID和AppSecret配置错误

6.4 分享链接签名失败

原因:用户分享后微信会在URL末尾追加参数

解决:对于Vue/SPA等单页应用,必须在每次页面切换时重新签名,或通过前端动态获取URL传到后端进行签名

// Vue项目中动态获取URL进行签名的方式
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']
        });
    });

6.5 签名校验工具

可以使用微信官方签名校验工具验证签名是否正确:

http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign

七、完整流程图

JSSDK签名的完整流程如下:

  1. 前端 → 访问微信页面,获取当前URL并编码
  2. 前端 → 通过AJAX将URL传递给后端签名接口
  3. 后端 → 获取access_token(使用AppID和AppSecret调用微信接口)
  4. 后端 → 使用access_token获取jsapi_ticket
  5. 后端 → 使用jsapi_ticket、随机字符串、时间戳、URL生成签名
  6. 后端 → 将签名包(appId、nonceStr、timestamp、signature、url)返回前端
  7. 前端 → 使用wx.config注入配置信息
  8. 前端 → 在wx.ready中调用微信JSSDK接口

八、总结

微信JSSDK页面授权(签名算法)是微信公众号H5开发的核心环节,需要注意以下关键点:

  • access_token和jsapi_ticket必须缓存,避免频繁调用接口被封禁
  • 签名URL必须与调用页面的完整URL完全一致
  • 必须在微信公众平台设置JS接口安全域名
  • 所有签名参数和wx.config中的参数必须完全一致
  • 在wx.ready回调中调用JSSDK接口,不要在其他位置调用
  • 前端传递URL时需要encodeURIComponent编码,后端需要urldecode解码

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部