欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

微信JSSDK签名

程序员文章站 2023-08-21 19:32:46
微信JS-SDK说明文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 生成签名 1.签名规则 参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), ......

微信js-sdk说明文档

     https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115

生成签名

   1.签名规则

    参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的url,不包含#及其后面部分) 。

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

    这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行url 转义。

   2.注意事项

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

    2.签名用的url必须是调用js接口页面的完整url。

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

    4.调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器ip地址添加到ip白名单中,点击查看设置方法,否则将无法调用成功。小程序无需配置ip白名单。

   3.签名逻辑

    所知,签名字段有noncestr,jsapi_ticket,timestamp,url。那这四个值怎么来呢?

    noncestr:随机字符串,可以直接生成。

string noncestr=guid.newguid().tostring("n");

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

    获取jsapi_ticket时就要用到access_token了,access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。

    我们可以通过下面的接口取得access_token

https请求方式: get
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appid&secret=appsecret

 

    这里要用到3个参数(grant_type,appid,secret);

参数 是否必须 说明
grant_type 获取access_token填写client_credential
appid 第三方用户唯一凭证
secret 第三方用户唯一凭证密钥,即appsecret

 

 

 

 

    其中,grant_type的值即为client_credential,appid和appsecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。

    我在上篇随笔记录了appid和appsecret的获取方式,链接如下:

     https://www.cnblogs.com/p1024q/p/11321864.html

    正常情况下,微信会返回如下json数据

{"access_token":"access_token","expires_in":7200}

    其中, access_token的值就是我们要用的值,expires_in 是凭证有效时间(7200秒)

    取到access_token后,要将值存到缓存不大于7200秒,再调用下面的接口

http请求方式: get
https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=access_token&type=jsapi

   成功则返回如下json

{
"errcode":0,
"errmsg":"ok",
"ticket":"bxldikrxvbtpdhsm05e5u5suoxnkd8-41zo3mhkoyn5ofkwitdggnr2fwj0m9e8nyzwkvzvdvtaugwvsdshfka",
"expires_in":7200
}

   ticket值就是要用的jsapi_ticket。

    timestamp:时间戳.。

    url:当前网页的url,不包含#及其后面部分。作为接口请求参数通过前端传过来。

   有了这四个值,就能根据签名规则进行签名,得到签名值signature。

   签名成功,需要返回下面几个参数给前端作验签使用。

参数名 类型 说明
timestamp int 生成签名的时间戳
noncestr string 生成签名的随机串
signature string 签名

 

 

 

 

   废话不多说,直接上后端签名代码:

 1 static nlog.logger logger = nlog.logmanager.getcurrentclasslogger();//日志
 2 
 3 public wxshare getwxshareinfo(string url)
 4         {
 5             datetime now = datetime.now;
 6             var timestamp = datetimehelper.gettimestamp(now);//取十位时间戳
 7             var guid = guid.newguid().tostring("n");//随机串
 8             var ticket = "";//签名密钥
 9             try {
10                 wxshare s= new wxshare();
11 //取缓存中的ticket,没有则重新生成ticket值(也可以将ticket值保存到文件中,此时从文件中读取ticket)
12                 wxsharecache cache = new wxsharecache(key).getcache<wxsharecache>();
13                 if (cache == null || string.isnullorwhitespace(cache.ticket)) {
14                     cache = new wxsharecache(key);
15                     cache.ticket = getticket();//获取ticket值
16                     cache.setcache(cache);//添加缓存
17                     ticket = cache.ticket;
18                 } else {
19                     ticket = cache.ticket;
20                 }
21                 url = httputility.urldecode(url);//url解码
22                 string sign = getsignature(ticket,guid,timestamp,url);
23                 s.noncestr = guid;
24                 s.timestamp = timestamp;
25                 s.sign = sign;
26                 logger.warn($"url:{url},时间戳:{timestamp},随机数:{guid},ticket:{ticket},sign值:{sign}");//记录日志
27                 return s;
28             } catch (exception ex) {
29                 logger.warn(ex);
30                 throw ex;
31             }
32         }

 

   返回给前端的对象

        /// <summary>
        /// 返回实体
        /// </summary>
        public class wxshare
        {
            /// <summary>
            /// 随机码
            /// </summary>
            public string noncestr { get; set; }
            /// <summary>
            /// 时间戳
            /// </summary>
            public int timestamp { get; set; }
           /// <summary>
           /// 签名值
           /// </summary>
            public string signature { get; set; }
        }

 

时间戳

        /// <summary>
        /// 十位时间戳
        /// </summary>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static int gettimestamp(datetime dt)
        {
            datetime datestart = new datetime(1970, 1, 1, 8, 0, 0);
            int timestamp = convert.toint32((dt - datestart).totalseconds);
            return timestamp;
        }

 

请求方法

        //请求基类
        private static httpclient _client = null;
        public static httpclient client {
            get {
                if (_client == null) {
                    var handler = new httpclienthandler() {
                        automaticdecompression = decompressionmethods.gzip,
                        allowautoredirect = false,
                        usecookies = false,
                    };
                    _client = new httpclient(handler);
                    _client.timeout = timespan.fromseconds(5);
                    _client.defaultrequestheaders.add("accept","application/json");

                }
                return _client;
            }
        }

 

签名密钥

        /// <summary>
        /// getticket
        /// </summary>
        /// <returns></returns>
        public static string getticket()
        {
            string token = getaccesstoken();//获取accesstoken
            idictionary<string,string> dic = new dictionary<string,string>();
            dic["access_token"] = token;
            dic["type"] = "jsapi";
            formurlencodedcontent content = new formurlencodedcontent(dic);
            var response = client.postasync("https://api.weixin.qq.com/cgi-bin/ticket/getticket",content).result;
            if (response.statuscode != httpstatuscode.ok)
                return "";
            var result = response.content.readasstringasync().result;
            jobject obj = jobject.parse(result);
            string ticket = obj["ticket"]?.tostring()??"";
            return ticket;
        }

 

accesstoken

        /// <summary>
        /// getaccesstoken
        /// </summary>
        /// <returns></returns>
        public static string getaccesstoken()
        {
            idictionary<string,string> dic = new dictionary<string,string>();
            dic["grant_type"] = "client_credential";
            dic["appid"] = "";//自己的appid
            dic["secret"] = "";//自己的appsecret

            formurlencodedcontent content = new formurlencodedcontent(dic);
            var response = client.postasync("https://api.weixin.qq.com/cgi-bin/token",content).result;
            if (response.statuscode != httpstatuscode.ok)
                return "";
            var result = response.content.readasstringasync().result;
            jobject obj = jobject.parse(result);
            string token = obj["access_token"]?.tostring()??"";
            return token;
        }

 

签名算法

        /// <summary>
        /// 签名算法
        /// </summary>
        /// <param name="ticket">ticket</param>
        /// <param name="noncestr">随机字符串</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string getsignature(string ticket,string noncestr,long timestamp,string url)
        {
            var string1builder = new stringbuilder();
            //拼接字符串
            string1builder.append("jsapi_ticket=").append(ticket).append("&")
                          .append("noncestr=").append(noncestr).append("&")
                          .append("timestamp=").append(timestamp).append("&")
                          .append("url=").append(url.indexof("#") >= 0 ? url.substring(0,url.indexof("#")) : url);
            string str = string1builder.tostring();
            return sha1(str);//加密
        }

 

sha1加密

        
        public static string sha1(string content)
        {
            return sha1(content,encoding.utf8);
        }

        /// <summary>
        /// sha1 加密
        /// </summary>
        /// <param name="content">需要加密字符串</param>
        /// <param name="encode">指定加密编码</param>
        /// <returns>返回40位小写字符串</returns>
        public static string sha1(string content,encoding encode)
        {
            try {
                sha1 sha1 = new sha1cryptoserviceprovider();
                byte[] bytes_in = encode.getbytes(content);
                byte[] bytes_out = sha1.computehash(bytes_in);
                sha1.dispose();
                string result = bitconverter.tostring(bytes_out);
                result = result.replace("-","").tolower();//转小写
                return result;
            } catch (exception ex) {
                throw new exception("sha1加密出错:" + ex.message);
            }
        }

 

 

前端验签

   1.引入js文件

    在需要调用js接口的页面引入如下js文件,(支持https):

   2.调用后端接口并注入权限验证

$(function(){  
    var url=encodeuricomponent(location.href.split('#')[0]); //对当前url编码
    //ajax注入权限验证  
    $.ajax({  
        url:"ajax",  
        type:'get',
        data: {url:url},   
        error: function(xmlhttprequest, textstatus, errorthrown){  
            alert("发生错误:"+errorthrown);  
        },  
        success: function(res){
            var appid = "";//与后端的appid相同  
            var noncestr = res.noncestr;  
            var timestamp = res.timestamp;  
            var signature = res.signature;  
            wx.config({  
                debug: true, //开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。  
                appid: appid, //必填,公众号的唯一标识  
                timestamp: timestamp, // 必填,生成签名的时间戳  
                noncestr: noncestr, //必填,生成签名的随机串  
                signature: signature,// 必填,签名  
                jsapilist: ['onmenusharetimeline','onmenushareappmessage','onmenushareqq',  
                            'onmenushareweibo','onmenushareqzone','chooseimage',  
                            'uploadimage','downloadimage','startrecord','stoprecord',  
                            'onvoicerecordend','playvoice','pausevoice','stopvoice',  
                            'translatevoice','openlocation','getlocation','hideoptionmenu',  
                            'showoptionmenu','closewindow','hidemenuitems','showmenuitems',  
                            'showallnonbasemenuitem','hideallnonbasemenuitem','scanqrcode'] //必填,需要使用的js接口列表,所有js接口列表   
            });  
        }  
    });  
  
}); 

 

 

    至此,后端签名,前端验签过程结束。

    在这过程中,掉过几次坑。最让我印象深刻的是测试的时候怎么样都是签名错误(返回invalid signature)。考虑到可能是url的问题,所以在前端做了编码,后端做了解码,然后验签成功。

    测试签名正确与否,微信有个校验工具,如下:

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

    将签名的四个参数输入,生成的签名与后端生成的签名作对比,sign值一致说明签名是正确的,不一致就需要检查程序逻辑问题了。