ibcadmin 发表于 2019-8-13 18:07:59

微信JSSDK签名

<h3 id="title" >微信JS-SDK说明文档</h3>
<p>   https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115</p>
<h3>生成签名</h3>
<h4>   1.签名规则</h4>
<p>    参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。</p>
<p>    对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。</p>
<p>    这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都接纳原始值,不进行URL 转义。</p>
<h4>   2.注意事项</h4>
<p>    1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。</p>
<p>    2.签名用的url必须是调用JS接口页面的完整URL。</p>
<p>    3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。</p>
<p>    4.调用接口时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,点击查看设置方法,否则将无法调用成功。小程序无需配置IP白名单。</p>
<h4>   3.签名逻辑</h4>
<p>    所知,签名字段有noncestr,jsapi_ticket,timestamp,url。那这四个值怎么来呢?</p>
<p>    noncestr:随机字符串,可以直接生成。</p>

string nonceStr=Guid.NewGuid().ToString("N");

<p>    jsapi_ticket:公众号用于调用微信JS接口的临时单子(签名密钥)。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。</p>
<p>    获取jsapi_ticket时就要用到access_token了,access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。</p>
<p>    我们可以通过下面的接口取得access_token</p>

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

<p> </p>
<p>    这里要用到3个参数(grant_type,appid,secret);</p>
<table style="height: 88px; float: left; width: 844px;" border="0">
<tbody>
<tr>
<td style="text-align: center;">参数</td>
<td style="text-align: center;">是否必须</td>
<td style="text-align: center;">说明</td>
</tr>
<tr>
<td style="text-align: center;">grant_type</td>
<td style="text-align: center;">是</td>
<td style="text-align: center;">获取access_token填写client_credential</td>
</tr>
<tr>
<td style="text-align: center;">appid</td>
<td style="text-align: center;">是</td>
<td style="text-align: center;">第三方用户唯一凭证</td>
</tr>
<tr>
<td style="text-align: center;">secret</td>
<td style="text-align: center;">是</td>
<td style="text-align: center;">第三方用户唯一凭证密钥,即appsecret</td>
</tr>
</tbody>
</table>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>    其中,grant_type的值即为client_credential,AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)。</p>
<p>    我在上篇随笔记录了AppID和AppSecret的获取方式,链接如下:</p>
<p>   https://www.cnblogs.com/p1024q/p/11321864.html</p>
<p>    正常情况下,微信会返回如下JSON数据</p>

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

<p>    其中, access_token的值就是我们要用的值,expires_in 是凭证有效时间(7200秒)</p>
<p>    取到access_token后,要将值存到缓存不大于7200秒,再调用下面的接口</p>

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

<p>   成功则返回如下JSON</p>

{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}

<p>   ticket值就是要用的jsapi_ticket。</p>
<p>    timestamp:时间戳.。</p>
<p>    url:当前网页的URL,不包含#及其后面部分。作为接口请求参数通过前端传过来。</p>
<p>   有了这四个值,就能根据签名规则进行签名,得到签名值<code>signature。</code></p>
<p>   签名成功,需要返回下面几个参数给前端作验签使用。</p>
<table style="height: 88px; float: left; width: 960px;" border="0">
<tbody>
<tr>
<td style="text-align: center;">参数名</td>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
<tr>
<td style="text-align: center;">timestamp</td>
<td style="text-align: center;">int</td>
<td style="text-align: center;">生成签名的时间戳</td>
</tr>
<tr>
<td style="text-align: center;">noncestr</td>
<td style="text-align: center;">string</td>
<td style="text-align: center;">生成签名的随机串</td>
</tr>
<tr>
<td style="text-align: center;">signature</td>
<td style="text-align: center;">string</td>
<td style="text-align: center;">签名</td>
</tr>
</tbody>
</table>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>   废话不多说,直接上后端签名代码:</p>

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         }

<p> </p>
<p>   返回给前端的对象</p>

      /// <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; }
      }

<p> </p>
<p>时间戳</p>

      /// <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;
      }

<p> </p>
<p>请求方法</p>

      //请求基类
      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;
            }
      }

<p> </p>
<p>签名密钥</p>

      /// <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;
      }

<p> </p>
<p>AccessToken</p>

      /// <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;
      }

<p> </p>
<p>签名算法</p>

      /// <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);//加密
      }

<p> </p>
<p>SHA1加密</p>

      
      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);
            }
      }

<p> </p>
<p> </p>
<h3>前端验签</h3>
<h4>   1.引入JS文件</h4>
<p>    在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js</p>
<h4>   2.调用后端接口并注入权限验证</h4>

$(function(){
    var url=encodeURIComponent(location.href.split('#')); //对当前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接口列表   
            });
      }
    });

});

<p> </p>
<p> </p>
<p>    至此,后端签名,前端验签过程结束。</p>
<p>    在这过程中,掉过几次坑。最让我印象深刻的是测试的时候怎么样都是签名错误(返回invalid signature)。考虑到可能是url的问题,所以在前端做了编码,后端做了解码,然后验签成功。</p>
<p>    测试签名正确与否,微信有个校验工具,如下:</p>
<p>    https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign</p>
<p>    将签名的四个参数输入,生成的签名与后端生成的签名作对比,sign值一致说明签名是正确的,不一致就需要检查程序逻辑问题了。</p>
<p> </p><br>来源:<a href="https://www.cnblogs.com/p1024q/p/11323631.html" target="_blank">https://www.cnblogs.com/p1024q/p/11323631.html</a><br>免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 微信JSSDK签名