博客列表 >微信JSSDK签名

微信JSSDK签名

弘德誉曦的博客
弘德誉曦的博客原创
2021年07月25日 13:44:341306浏览

微信JSSDK签名

微信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:随机字符串,可以直接生成。

  1. 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

  1. https请求方式: GET
  2. 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数据

  1. {"access_token":"ACCESS_TOKEN","expires_in":7200}

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

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

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

   成功则返回如下JSON

  1. {
  2. "errcode":0,
  3. "errmsg":"ok",
  4. "ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
  5. "expires_in":7200
  6. }

   ticket值就是要用的jsapi_ticket。

    timestamp:时间戳.。

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

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

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

参数名类型说明
timestampint生成签名的时间戳
noncestrstring生成签名的随机串
signaturestring签名

 

 

 

 

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

  1. 1 static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();//日志
  2. 2
  3. 3 public WXShare GetWxShareInfo(string url)
  4. 4        {
  5. 5             DateTime now = DateTime.Now;
  6. 6             var timestamp = DateTimeHelper.GetTimeStamp(now);//取十位时间戳
  7. 7             var guid = Guid.NewGuid().ToString("N");//随机串
  8. 8             var ticket = "";//签名密钥
  9. 9             try {
  10. 10                 WXShare s= new WXShare();
  11. 11 //取缓存中的Ticket,没有则重新生成Ticket值(也可以将Ticket值保存到文件中,此时从文件中读取Ticket)
  12. 12                 WxShareCache Cache = new WxShareCache(Key).GetCache<WxShareCache>();
  13. 13                 if (Cache == null || string.IsNullOrWhiteSpace(Cache.Ticket)) {
  14. 14                     Cache = new WxShareCache(Key);
  15. 15                     Cache.Ticket = GetTicket();//获取Ticket值
  16. 16                    Cache.SetCache(Cache);//添加缓存
  17. 17                     ticket = Cache.Ticket;
  18. 18                 } else {
  19. 19                     ticket = Cache.Ticket;
  20. 20                }
  21. 21                 url = HttpUtility.UrlDecode(url);//url解码
  22. 22                 string sign = GetSignature(ticket,guid,timestamp,url);
  23. 23                 s.noncestr = guid;
  24. 24                 s.timestamp = timestamp;
  25. 25                 s.sign = sign;
  26. 26                 logger.Warn($"url:{url},时间戳:{timestamp},随机数:{guid},ticket:{ticket},sign值:{sign}");//记录日志
  27. 27                 return s;
  28. 28             } catch (Exception ex) {
  29. 29                logger.Warn(ex);
  30. 30                 throw ex;
  31. 31            }
  32. 32         }

 

   返回给前端的对象

  1.        /// <summary>
  2.        /// 返回实体
  3.        /// </summary>
  4.        public class WXShare
  5.        {
  6.            /// <summary>
  7.            /// 随机码
  8.            /// </summary>
  9.            public string noncestr { get; set; }
  10.            /// <summary>
  11.            /// 时间戳
  12.            /// </summary>
  13.            public int timestamp { get; set; }
  14.           /// <summary>
  15.           /// 签名值
  16.           /// </summary>
  17.            public string signature { get; set; }
  18.        }

 

时间戳

  1.        /// <summary>
  2.        /// 十位时间戳
  3.        /// </summary>
  4.        /// <param name="dt"></param>
  5.        /// <returns></returns>
  6.        public static int GetTimeStamp(DateTime dt)
  7.        {
  8.            DateTime dateStart = new DateTime(1970, 1, 1, 8, 0, 0);
  9.            int timeStamp = Convert.ToInt32((dt - dateStart).TotalSeconds);
  10.            return timeStamp;
  11.        }

 

请求方法

  1.        //请求基类
  2.        private static HttpClient _client = null;
  3.        public static HttpClient Client {
  4.            get {
  5.                if (_client == null) {
  6.                    var handler = new HttpClientHandler() {
  7.                        AutomaticDecompression = DecompressionMethods.GZip,
  8.                        AllowAutoRedirect = false,
  9.                        UseCookies = false,
  10.                    };
  11.                    _client = new HttpClient(handler);
  12.                    _client.Timeout = TimeSpan.FromSeconds(5);
  13.                    _client.DefaultRequestHeaders.Add("Accept","application/json");
  14.                }
  15.                return _client;
  16.            }
  17.        }

 

签名密钥

  1.        /// <summary>
  2.        /// GetTicket
  3.        /// </summary>
  4.        /// <returns></returns>
  5.        public static string GetTicket()
  6.        {
  7.            string token = GetAccessToken();//获取AccessToken
  8.            IDictionary<string,string> dic = new Dictionary<string,string>();
  9.            dic["access_token"] = token;
  10.            dic["type"] = "jsapi";
  11.            FormUrlEncodedContent content = new FormUrlEncodedContent(dic);
  12.            var response = Client.PostAsync("https://api.weixin.qq.com/cgi-bin/ticket/getticket",content).Result;
  13.            if (response.StatusCode != HttpStatusCode.OK)
  14.                return "";
  15.            var result = response.Content.ReadAsStringAsync().Result;
  16.            JObject obj = JObject.Parse(result);
  17.            string ticket = obj["ticket"]?.ToString()??"";
  18.            return ticket;
  19.        }

 

AccessToken

  1.        /// <summary>
  2.        /// GetAccessToken
  3.        /// </summary>
  4.        /// <returns></returns>
  5.        public static string GetAccessToken()
  6.        {
  7.            IDictionary<string,string> dic = new Dictionary<string,string>();
  8.            dic["grant_type"] = "client_credential";
  9.            dic["appid"] = "";//自己的appid
  10.            dic["secret"] = "";//自己的appsecret
  11.            FormUrlEncodedContent content = new FormUrlEncodedContent(dic);
  12.            var response = Client.PostAsync("https://api.weixin.qq.com/cgi-bin/token",content).Result;
  13.            if (response.StatusCode != HttpStatusCode.OK)
  14.                return "";
  15.            var result = response.Content.ReadAsStringAsync().Result;
  16.            JObject obj = JObject.Parse(result);
  17.            string token = obj["access_token"]?.ToString()??"";
  18.            return token;
  19.        }

 

签名算法

  1.        /// <summary>
  2.        /// 签名算法
  3.        /// </summary>
  4.        /// <param name="ticket">ticket</param>
  5.        /// <param name="noncestr">随机字符串</param>
  6.        /// <param name="timestamp">时间戳</param>
  7.        /// <param name="url"></param>
  8.        /// <returns></returns>
  9.        public static string GetSignature(string ticket,string noncestr,long timestamp,string url)
  10.        {
  11.            var string1Builder = new StringBuilder();
  12.            //拼接字符串
  13.            string1Builder.Append("jsapi_ticket=").Append(ticket).Append("&")
  14.                          .Append("noncestr=").Append(noncestr).Append("&")
  15.                          .Append("timestamp=").Append(timestamp).Append("&")
  16.                          .Append("url=").Append(url.IndexOf("#") >= 0 ? url.Substring(0,url.IndexOf("#")) : url);
  17.            string str = string1Builder.ToString();
  18.            return SHA1(str);//加密
  19.        }

 

SHA1加密

  1.        
  2.        public static string SHA1(string content)
  3.        {
  4.            return SHA1(content,Encoding.UTF8);
  5.        }
  6.        /// <summary>
  7.        /// SHA1 加密
  8.        /// </summary>
  9.        /// <param name="content">需要加密字符串</param>
  10.        /// <param name="encode">指定加密编码</param>
  11.        /// <returns>返回40位小写字符串</returns>
  12.        public static string SHA1(string content,Encoding encode)
  13.        {
  14.            try {
  15.                SHA1 sha1 = new SHA1CryptoServiceProvider();
  16.                byte[] bytes_in = encode.GetBytes(content);
  17.                byte[] bytes_out = sha1.ComputeHash(bytes_in);
  18.                sha1.Dispose();
  19.                string result = BitConverter.ToString(bytes_out);
  20.                result = result.Replace("-","").ToLower();//转小写
  21.                return result;
  22.            } catch (Exception ex) {
  23.                throw new Exception("SHA1加密出错:" + ex.Message);
  24.            }
  25.        }

 

 

前端验签

   1.引入JS文件

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

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

  1. $(function(){  
  2.    var url=encodeURIComponent(location.href.split('#')[0]); //对当前url编码
  3.    //ajax注入权限验证  
  4.    $.ajax({  
  5.        url:"ajax",  
  6.        type:'GET',
  7.        data: {url:url},  
  8.        error: function(XMLHttpRequest, textStatus, errorThrown){  
  9.            alert("发生错误:"+errorThrown);  
  10.        },  
  11.        success: function(res){
  12.            var appId = "";//与后端的appid相同  
  13.            var noncestr = res.noncestr;  
  14.            var timestamp = res.timestamp;  
  15.            var signature = res.signature;  
  16.            wx.config({  
  17.                debug: true, //开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。  
  18.                appId: appId, //必填,公众号的唯一标识  
  19.                timestamp: timestamp, // 必填,生成签名的时间戳  
  20.                nonceStr: noncestr, //必填,生成签名的随机串  
  21.                signature: signature,// 必填,签名  
  22.                jsApiList: ['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ',  
  23.                            'onMenuShareWeibo','onMenuShareQZone','chooseImage',  
  24.                            'uploadImage','downloadImage','startRecord','stopRecord',  
  25.                            'onVoiceRecordEnd','playVoice','pauseVoice','stopVoice',  
  26.                            'translateVoice','openLocation','getLocation','hideOptionMenu',  
  27.                            'showOptionMenu','closeWindow','hideMenuItems','showMenuItems',  
  28.                            'showAllNonBaseMenuItem','hideAllNonBaseMenuItem','scanQRCode'] //必填,需要使用的JS接口列表,所有JS接口列表  
  29.            });  
  30.        }  
  31.    });  
  32.  
  33. });

 

 

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

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

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

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

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

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议