在小程序的web-view里使用微信的JS-SDK,步骤整理,以及遇到的坑

官方文档在这里:https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html

如果只是使用wx.miniProgram.navigateTo这种导航的接口,jssdk可以不用做配置,引用js后直接使用就行,如果chooseImage这种,就需要获取配置了,步骤如下:

  1. 先在后端通过AppID和AppSecret取到access_token,并缓存access_token
  2. 再通过access_token获取到jsapi_ticket,jsapi_ticket的值也要缓存
  3. 使用jsapi_ticket、当前URL、随机字符串、1970年1月1日00:00:00至今的秒数生成签名及及配置,返回给前端,签名记得使用https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign校验是否正确
  4. 前端拿到config后放到wx.config里执行
  5. 在wx.ready中注册事件

步骤比较多但是并不复杂,主要是这其中几个坑需要避开别踩到:

  1. AppID和AppSecret不能用小程序的,要用公众号的,否则会报invalid url domain的错误,官方文档上并没有说明这一点,所以这个坑非常深非常隐蔽,另外这个公众号要不要与小程序关联还不清楚,我用的是关联的。
  2. 签名用到的当前URl不要解码,拿到的是什么就用什么,否则即使签名校验通过也会报签名错误
  3. 如果你用了chooseImage这个api,需要获取到选择的图片的话,官方文档上说是在success的tempFilePaths参数里,实际上并不是,是在localIds里面

以下是部分代码:

.Net后台:

        #region JS-SDK配置
        /// <summary>
        /// 获取access_token(AppID和AppSecret不能使用小程序的,要使用公众号的)
        /// </summary>
        /// <returns>access_token</returns>
        public static string GetAccessToken()
        {
            object obj = NetCacheHelper.GetCache("access_token");
            string access_token = "";
            if (obj == null)
            {
                string AppID = SystemHelper.GetSystemStrValue("AppID");
                string AppSecret = SystemHelper.GetSystemStrValue("AppSecret");
                string url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + AppID + "&secret=" + AppSecret;
                string value = HttpHelper.Get(url);
                WxAccessTokenModel model = JsonConvert.DeserializeObject<WxAccessTokenModel>(value);
                NetCacheHelper.SetCache("access_token", model.access_token, TimeSpan.FromSeconds(model.expires_in - 60));
                access_token = model.access_token;
            }
            else
            {
                access_token = obj.ToString();
            }
            return access_token;
        }

        /// <summary>
        /// 获取jsapi_ticket
        /// </summary>
        /// <returns>jsapi_ticket</returns>
        public static string GetTicket()
        {
            string access_token = GetAccessToken();
            object obj = NetCacheHelper.GetCache("jsapi_ticket");
            string jsapi_ticket = "";
            if (obj == null)
            {
                string url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ access_token + "&type=jsapi";
                string value = HttpHelper.Get(url);
                WxTicketModel model = JsonConvert.DeserializeObject<WxTicketModel>(value);
                NetCacheHelper.SetCache("jsapi_ticket", model.ticket, TimeSpan.FromSeconds(model.expires_in - 60));
                jsapi_ticket = model.ticket;
            }
            else
            {
                jsapi_ticket = obj.ToString();
            }
            return jsapi_ticket;
        }

        /// <summary>
        /// 获取JSSDK的配置
        /// </summary>
        /// <param name="jsApiList">需要使用的JS接口列表</param>
        /// <param name="isDebug">是否是调试模式</param>
        /// <returns>JSSDK的配置</returns>
        public static string GetJSSDKConfig(List<string> jsApiList, bool isDebug)
        {
            string url = HttpHelper.GetCurrentFullURL(true, false, "weixin");
            return GetJSSDKConfig(jsApiList, isDebug, url);
        }

        /// <summary>
        /// 获取JSSDK的配置
        /// </summary>
        /// <param name="jsApiList">需要使用的JS接口列表</param>
        /// <param name="isDebug">是否是调试模式</param>
        /// <param name="url">页面URL</param>
        /// <returns>JSSDK的配置</returns>
        public static string GetJSSDKConfig(List<string> jsApiList, bool isDebug, string url)
        {
            #region 准备签名必须要的参数
            string noncestr = DataHelper.GetRandomString(16, true, true, true, false, "");
            string jsapi_ticket = GetTicket();
            string timestamp = DateHelper.Get19700101seconds(false).ToString();
            #endregion

            #region 进行签名
            Dictionary<string, string> dic = new Dictionary<string, string>();
            dic.Add("noncestr", noncestr);
            dic.Add("jsapi_ticket", jsapi_ticket);
            dic.Add("timestamp", timestamp);
            dic.Add("url", url);
            dic = dic.OrderBy(t => t.Key).ToDictionary(m => m.Key, m => m.Value);
            string sign = dic.Aggregate("", (current, d) => current + (d.Key + "=" + d.Value + "&")).Trim('&');
            string signature = EncryptionHelper.GetSHA1(sign).ToLower();
            #endregion

            #region 获得配置
            WxJSSDKConfig config = new WxJSSDKConfig();
            config.appId = SystemHelper.GetSystemStrValue("AppID");
            config.debug = isDebug;
            config.jsApiList = jsApiList;
            config.nonceStr = noncestr;
            config.signature = signature;
            config.timestamp = timestamp;
            #endregion

            string json = JsonConvert.SerializeObject(config);
            return json;
        }

        #endregion

.Net后台的部分帮助类和模型类:

    /// <summary>
    /// 获取微信access_token返回的数据格式
    /// </summary>
    public class WxAccessTokenModel
    {
        /// <summary>
        /// access_token
        /// </summary>
        public string access_token { get; set; }
        /// <summary>
        /// expires_in
        /// </summary>
        public int expires_in { get; set; }
    }
    /// <summary>
    /// 访问微信ticket返回的数据格式
    /// </summary>
    public class WxTicketModel
    {
        /// <summary>
        /// expires_in
        /// </summary>
        public int expires_in { get; set; }

        /// <summary>
        /// ticket
        /// </summary>
        public string ticket { get; set; }

        /// <summary>
        /// 错误代码
        /// </summary>
        public string errcode { get; set; }

        /// <summary>
        /// 错误消息
        /// </summary>
        public string errmsg { get; set; }
    }
    /// <summary>
    /// 微信JS-SDK的配置
    /// </summary>
    public class WxJSSDKConfig
    {
        /// <summary>
        /// 开启调试模式
        /// </summary>
        public bool debug { get; set; }
        /// <summary>
        /// 公众号的唯一标识
        /// </summary>
        public string appId { get; set; }
        /// <summary>
        /// 生成签名的时间戳
        /// </summary>
        public string timestamp { get; set; }
        /// <summary>
        /// 生成签名的随机串
        /// </summary>
        public string nonceStr { get; set; }
        /// <summary>
        /// 签名
        /// </summary>
        public string signature { get; set; }
        /// <summary>
        /// 需要使用的JS接口列表
        /// </summary>
        public List<string> jsApiList { get; set; }
    }
        ///<summary>
        ///生成随机字符串 
        ///</summary>
        ///<param name="length">目标字符串的长度</param>
        ///<param name="useNum">是否包含数字,1=包含,默认为包含</param>
        ///<param name="useLow">是否包含小写字母,1=包含,默认为包含</param>
        ///<param name="useUpp">是否包含大写字母,1=包含,默认为包含</param>
        ///<param name="useSpe">是否包含特殊字符,1=包含,默认为不包含</param>
        ///<param name="custom">要包含的自定义字符,直接输入要包含的字符列表</param>
        ///<returns>指定长度的随机字符串</returns>
        public static string GetRandomString(int length, bool useNum, bool useLow, bool useUpp, bool useSpe, string custom)
        {
            byte[] b = new byte[4];
            new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(b);
            Random r = new Random(BitConverter.ToInt32(b, 0));
            string s = null, str = custom;
            if (useNum == true) { str += "0123456789"; }
            if (useLow == true) { str += "abcdefghijklmnopqrstuvwxyz"; }
            if (useUpp == true) { str += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
            if (useSpe == true) { str += "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; }
            for (int i = 0; i < length; i++)
            {
                s += str.Substring(r.Next(0, str.Length - 1), 1);
            }
            return s;
        }
        /// <summary>
        /// 获取1970年1月1日00:00:00至今的秒数
        /// </summary>
        /// <param name="keepDecimal">是否保留小数点</param>
        /// <returns>1970年1月1日00:00:00至今的秒数</returns>
        public static double Get19700101seconds(bool keepDecimal)
        {
            DateTime time = DateTime.Parse("1970-01-01 00:00:00");
            DateTime now = DateTime.Now;
            TimeSpan ts = now - time;
            if (keepDecimal)
            {
                return ts.TotalSeconds;
            }
            else
            {
                return Math.Floor(ts.TotalSeconds);
            }
        }
        /// <summary>
        /// SHA1加密
        /// </summary>
        /// <param name="source">原字符串</param>
        /// <returns>加密后的字符串</returns>
        public static string GetSHA1(string source)
        {
            try
            {
                SHA1 sha1 = new SHA1CryptoServiceProvider();
                byte[] bytes_in = Encoding.UTF8.GetBytes(source);
                byte[] bytes_out = sha1.ComputeHash(bytes_in);
                sha1.Dispose();
                string result = BitConverter.ToString(bytes_out);
                result = result.Replace("-", "");
                return result;
            }
            catch (Exception)
            {
                return "";
            }
        }

.Net调用:

string config = WxHelper.GetJSSDKConfig(new List<string>() { "chooseImage", "previewImage", "uploadImage" }, false);

前端JS:

    var config = $("#Config").val();
    config = JSON.parse(config);
    wx.config(config);
    wx.ready(function () {
        document.querySelector('#addFile').onclick = function () {
            wx.chooseImage({
                success: function (res) {
                    wx.previewImage({
                        current: res.localIds[0], // 当前显示图片的http链接
                        urls: res.localIds// 需要预览的图片http链接列表
                    })
                }
            });
        };
    });

    wx.error(function (res) {
        
    });

 

<think>嗯,用户问的是在web-view中怎么实现微信登录。首先,我需要回忆一下微信登录的整体流程。微信登录通常有两种方式:一种是微信客户端内的网页授权,另一种是微信开放平台提供的扫码登录。但web-view通常是在微信小程序或者其他应用中内嵌的网页,可能需要结合小程序微信登录功能。 首先,用户可能是在微信小程序使用web-view组件加载了一个H5页面,然后想要在这个H5页面实现微信登录。这时候需要考虑微信小程序的登录流程和H5页面之间的通信。根据微信的文档,web-view中的H5页面可以通过微信JS-SDK来调用微信的相关接口,但可能权限受限。 或者,用户可能是在其他应用内嵌的web-view中实现微信登录,比如在APP中通过web-view加载一个网页,这时候可能需要使用微信开放平台的OAuth2.0授权流程,引导用户跳转到微信的授权页面,获取code后再回调到服务器端交换access_token。 需要明确用户的具体场景,是微信小程序内的web-view还是其他应用的web-view。如果是小程序内的web-view,可能需要使用小程序的登录接口,然后在web-view中通过URL参数传递登录凭证,由H5页面和后端服务器验证。不过小程序web-view可能无法直接使用微信网页授权,因为微信网页授权需要在微信浏览器内打开,而小程序web-view环境可能不同。 如果是普通的web-view,比如在非微信环境中的APP内嵌网页,那么需要使用微信开放平台的网页授权,通过重定向到微信的授权页面获取code,然后后端用code获取access_token和用户信息。这需要注册微信开放平台的应用,获取appid和secret,并设置回调域名。 另外,要注意web-view中可能存在的跨域问题,以及如何在H5页面中安全地传递登录状态。可能需要通过postMessage等方式与宿主应用通信,或者通过URL参数传递临时凭证,由后端进行验证。 总结下来,可能的步骤包括:1. 在微信开放平台注册应用,获取appid;2. 在web-view中构造微信授权URL,跳转到微信的授权页面;3. 用户授权后,微信重定向到回调地址,并携带code;4. 后端使用code换取access_token和用户信息;5. 前端根据后端返回的信息处理登录状态。 但如果是小程序中的web-view,可能需要结合小程序的wx.login获取code,然后通过web-view的URL传递给H5页面,H5页面再将code发送到后端,后端通过微信开放平台的接口校验code的有效性,并返回用户信息。不过需要注意小程序的code和开放平台的code可能不同,需要确认接口的适用性。 此外,还要考虑安全性,比如code的一次性使用,防止CSRF攻击,以及确保回调地址的正确配置,避免授权信息泄露。可能还需要处理用户拒绝授权的情况,提供友好的错误提示。 最后,可能需要提供具体的代码示例,比如如何在H5页面中生成授权链接,如何处理回调,以及后端如何用code换取token。同时提醒用户注意配置正确的域名白名单,避免接口调用失败。</think>在WebView中实现微信登录通常需要结合微信开放平台的OAuth2.0网页授权流程。以下是分步指南: -- ### **1. 准备工作** - **注册微信开放平台账号** 前往[微信开放平台](https://open.weixin.qq.com)注册应用,获取 `AppID` 和 `AppSecret`。 - **设置授权回调域名** 在开放平台后台配置`授权回调域名`(如 `www.yourdomain.com`),需与你的H5页面域名一致。 --- ### **2. 前端实现流程** #### **步骤1:跳转到微信授权页面** 在WebView中构造授权链接,引导用户跳转: ```javascript const appId = '你的AppID'; const redirectUri = encodeURIComponent('https://www.yourdomain.com/callback'); const scope = 'snsapi_login'; // 需要用户手动同意授权 const authUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=YOUR_STATE#wechat_redirect`; // 在WebView中跳转 window.location.href = authUrl; ``` #### **步骤2:处理微信回调** 用户授权后,微信会重定向到你的回调地址(`redirect_uri`),并附加`code`和`state`参数: ``` https://www.yourdomain.com/callback?code=CODE_VALUE&state=YOUR_STATE ``` #### **步骤3:用code换取用户信息** 前端将`code`发送给后端,后端通过微信接口换取`access_token`和用户信息: ```javascript // 前端(H5页面)获取code const code = new URLSearchParams(window.location.search).get('code'); // 发送到后端 fetch('/api/wechat-login', { method: 'POST', body: JSON.stringify({ code }), headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.userInfo) { // 登录成功,处理用户信息 } }); ``` --- ### **3. 后端实现流程** #### **步骤1:用code换取access_token** 调用微信接口: ``` GET https://api.weixin.qq.com/sns/oauth2/access_token? appid=APPID& secret=SECRET& code=CODE& grant_type=authorization_code ``` #### **步骤2:获取用户信息** 使用`access_token`调用微信接口: ``` GET https://api.weixin.qq.com/sns/userinfo? access_token=ACCESS_TOKEN& openid=OPENID& lang=zh_CN ``` --- ### **4. 安全注意事项** 1. **验证state参数** 防止CSRF攻击,确保回调的`state`与发起请求时一致。 2. **code一次性有效** 每个`code`仅能使用一次,且有效期5分钟。 3. **敏感信息存储在后端** `AppSecret`和`access_token`需保存在服务端,避免泄露。 --- ### **5. 在微信小程序WebView中的特殊处理** 如果WebView嵌入在微信小程序中,需结合小程序登录流程: 1. 小程序通过`wx.login`获取`code`,传给后端换取`openid`。 2. 将`openid`通过URL参数传递给WebView的H5页面。 3. H5页面将`openid`发送到后端,直接关联用户身份(需确保已绑定)。 --- ### **示例代码(后端Node.js)** ```javascript const axios = require('axios'); app.post('/api/wechat-login', async (req, res) => { const { code } = req.body; const appId = 'YOUR_APPID'; const secret = 'YOUR_SECRET'; // 换取access_token const tokenRes = await axios.get( `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${secret}&code=${code}&grant_type=authorization_code` ); // 获取用户信息 const userRes = await axios.get( `https://api.weixin.qq.com/sns/userinfo?access_token=${tokenRes.data.access_token}&openid=${tokenRes.data.openid}` ); res.json({ userInfo: userRes.data }); }); ``` --- 通过以上步骤,即可在WebView中实现微信登录。需根据实际场景调整参数和安全策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值