.Net之微信小程序獲取用戶UnionID的實(shí)現(xiàn)
前言:
在實(shí)際項(xiàng)目開發(fā)中我們經(jīng)常會遇到賬號統(tǒng)一的問題,如何在不同端或者是不同的登錄方式下保證同一個會員或者用戶賬號唯一(便于用戶信息的管理)。這段時間就有一個這樣的需求,之前有個客戶做了一個微信小程序商城(店主端的),然后現(xiàn)在又要做一個會員購物端的小程序商場。首先之前用戶登錄憑證都是使用微信openid來做的唯一標(biāo)識,而現(xiàn)在客戶需求是要做到用戶在會員端小程序跳轉(zhuǎn)到到店主端小程序假如之前該用戶微信是在店主端審核通過的用戶則不需要在進(jìn)行資料提交審核操作,直接登錄。所以,所以我們使用了UnionID來進(jìn)行關(guān)聯(lián),如下是我們現(xiàn)在項(xiàng)目的基本流程(畫的丑莫見怪)。
說說UnionID機(jī)制:
如果開發(fā)者擁有多個移動應(yīng)用、網(wǎng)站應(yīng)用、和公眾帳號(包括小程序),可通過 UnionID 來區(qū)分用戶的唯一性,因?yàn)橹灰峭粋€微信開放平臺帳號下的移動應(yīng)用、網(wǎng)站應(yīng)用和公眾帳號(包括小程序),用戶的 UnionID 是唯一的。換句話說,同一用戶,對同一個微信開放平臺下的不同應(yīng)用,unionid是相同的。
官方UnionID機(jī)制詳細(xì)說明:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
微信開放平臺綁定小程序流程:
登錄微信開放平臺— 管理中心 — 小程序 — 綁定小程序(直接使用微信官方圖)
微信小程序獲取UnoinID的兩種方式
調(diào)用接口wx.getUserInfo,從解密數(shù)據(jù)(encryptedData)中獲取 UnionID(推薦使用):
推薦使用原因:無需關(guān)注微信公眾號即可獲取到UnionID。
調(diào)用接口wx.getUserInfo前提:用戶允許授權(quán)獲取用戶信息!
開發(fā)者后臺校驗(yàn)與解密開放數(shù)據(jù):
微信為了保證用戶信息,把用戶通過wx.getUserInfo接口獲取到的相關(guān)敏感信息進(jìn)行了加密。加密方式對稱加密(后面會提到),首先我們需要通過微信小程序登錄流程獲取到用戶的session_key(會話密鑰),然后我們可以報(bào)獲取到的會話密鑰使用緩存存起來,在通過用戶授權(quán)獲取用戶相關(guān)信息,如下是用戶授權(quán)成功獲取到的用戶信息:
基本流程圖如下:
(encryptedData)加密數(shù)據(jù)解密算法:
開發(fā)者如需要獲取敏感數(shù)據(jù),需要對接口返回的加密數(shù)據(jù)(encryptedData)進(jìn)行對稱解密。 解密算法如下:
- 對稱解密使用的算法為 AES-128-CBC,數(shù)據(jù)采用PKCS#7填充。
- 對稱解密的目標(biāo)密文為 Base64_Decode(encryptedData)。
- 對稱解密秘鑰 aeskey = Base64_Decode(session_key), aeskey 是16字節(jié)。
- 對稱解密算法初始向量 為Base64_Decode(iv),其中iv由數(shù)據(jù)接口返回
很遺憾的是微信居然沒有為我們大.Net提供解密算法demo,實(shí)屬讓人不算,最后自己根據(jù)網(wǎng)上的資料還是配上了符合微信對稱加密的解密算法。
代碼實(shí)現(xiàn):
首先關(guān)于session_key(會話密鑰)的獲取,請看下面的wx.login+code2Session 方式
調(diào)用接口wx.getUserInfo獲取encryptedData(加密數(shù)據(jù))和iv(初始向量):
// 用戶已經(jīng)授權(quán) wx.getUserInfo({ success: function(res) { console.log(res); var userInfo = res.userInfo //用戶基本信息 let sessionKey = wx.getStorageSync("session_key");//臨時會話密鑰,通過小程序登錄流程獲取到的 //請求.net webapi解密接口 wx.request({ url: 'https://www.xxxtest.com/api/User_oAuth/DecryptSensitiveData', data: { sessionKey:sessionKey, encryptedData:res.encryptedData, iv:res.iv }, header: { 'content-type': 'application/json' // 默認(rèn)值 }, success (res) { //解密返回過來的UnionID console.log(res.data) } }) } }) })
.Net WebApi 解密數(shù)據(jù)接口:
/// <summary> /// 解密微信對稱加密數(shù)據(jù),獲取用戶聯(lián)合運(yùn)營編號 /// </summary> /// <param name="sessionKey">臨時會話秘鑰</param> /// <param name="encryptedData">微信用戶敏感加密數(shù)據(jù)</param> /// <param name="iv">解密初始向量</param> /// <returns></returns> [HttpGet] public IHttpActionResult DecryptSensitiveData(string sessionKey,string encryptedData,string iv) { try { var getUnionId=DecryptByAesBytes(encryptedData, sessionKey, iv); return Json(new { code =1, msg="解密成功",result= getUnionId }); } catch (Exception ex) { return Json(new { code = 0, msg = "解密失敗,原因:"+ex.Message }); } } #region AES對稱解密 /// <summary> /// AES解密 /// </summary> /// <param name="encryptedData">待解密的字節(jié)數(shù)組</param> /// <param name="sessionKey">解密密鑰字節(jié)數(shù)組</param> /// <param name="iv">IV初始化向量字節(jié)數(shù)組</param> /// <param name="cipher">運(yùn)算模式</param> /// <param name="padding">填充模式</param> /// <returns></returns> private static string DecryptByAesBytes(string encryptedData, string sessionKey, string iv) { try { //非空驗(yàn)證 if (!string.IsNullOrWhiteSpace(encryptedData) && !string.IsNullOrWhiteSpace(sessionKey) && !string.IsNullOrWhiteSpace(iv)) { var decryptBytes = Convert.FromBase64String(encryptedData.Replace(' ', '+')); var keyBytes = Convert.FromBase64String(sessionKey.Replace(' ', '+')); var ivBytes = Convert.FromBase64String(iv.Replace(' ', '+')); var aes = new AesCryptoServiceProvider { Key = keyBytes, IV = ivBytes, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 }; var outputBytes = aes.CreateDecryptor().TransformFinalBlock(decryptBytes, 0, decryptBytes.Length); var decryptResult = Encoding.UTF8.GetString(outputBytes); dynamic decryptData = JsonConvert.DeserializeObject(decryptResult, new { unionid = "" }.GetType()); JJHL.Utility.Loghelper.WriteLog("AES對稱解密結(jié)果為:" + decryptResult); return decryptData.unionid; } else { return ""; } } catch (Exception e) { JJHL.Utility.Loghelper.WriteLog("AES對稱解密失敗原因:" + e.Message); return ""; } } #endregion
所遇異常:參數(shù)使用Convert.FromBase64String轉(zhuǎn)化時,提示“Base-64字符數(shù)組的無效長度” 的問題:
原因:加密參數(shù)中的"+"通過地址欄傳過來時,后臺會解析為空格(遇到的概率比較?。?/p>
解決:最好的做法是 使用encryptedData.Replace("+", "%2B")先將空格編碼,然后再作為參數(shù)傳給另一頁面?zhèn)鬟f,這樣頁面在提取參數(shù)時才會將“%2B”解碼為加號.但這兒為了簡化,將空格直接還原為"+"或者是直接在后臺將空格替換為“+”encryptedData.Replace(' ', '+');
直接通過wx.login+code2Session
獲取到該用戶 UnionID:
其實(shí)這個方式就是實(shí)現(xiàn)了小程序的登錄流程,微信官方詳細(xì)說明:
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
優(yōu)點(diǎn):無需用戶授權(quán)。
前提:用戶需要關(guān)注該微信公眾號。
小程序端調(diào)用接口wx.login獲取code憑證,在通過請求auth.code2Session接口獲取用戶信息(UnionID,openid,session_key會話密鑰)兩種方式:
1.直接通過wx.login請求到code憑證后,在請求該地址獲取用戶信息:
GET:https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
詳細(xì)說明請看微信官方文檔(代碼略):https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
2.通過請求wx.login獲取code憑證,在向.net webapi后端請求code2Session接口:
原因:因?yàn)槲覀冃枰獙Λ@取的用戶信息做相關(guān)業(yè)務(wù)邏輯處理。
微信小程序端代碼:
復(fù)制代碼 代碼如下:/***封裝用戶promise登錄,通過code憑證獲取用戶信息(UnionID,openid,session_key會話密鑰)*/userLogin: function() {var that = this;//定義promise方法return new Promise(function(resolve, reject) {//調(diào)用登錄接口wx.login({success: function(res) {if (res.code) {console.log("用戶登錄授權(quán)code為:" + res.code);//調(diào)用wx.request請求傳遞code憑證換取用戶openid,并獲取后臺用戶信息wx.request({url: 'https://www.xxxx.xxx.api/User_oAuth/GetUserInfo',//后臺請求用戶信息方法data: {code: res.code //code憑證},header: {'content-type':'application/json' // 默認(rèn)值},success(res) {console.log(res.data)if (res.data.errcode == 0) {//存入session緩存中console.log(res.data.openid);//微信用戶唯一標(biāo)識console.log(res.data.UnionID);//微信開發(fā)平臺聯(lián)合IDconsole.log(res.data.session_key);//會話密鑰 //***注意**** //注意:這里是直接把session_key緩存起來,在上面wx.getUserInfo會使用到 wx.setStorageSync("session_key",res.data.session_key);[code]//promise機(jī)制放回成功數(shù)據(jù) resolve(res.data); } else { reject('error'); } }, fail: function(res) { reject(res); wx.showToast({ title: '系統(tǒng)錯誤' }) }, complete: () => { } //complete接口執(zhí)行后的回調(diào)函數(shù),無論成功失敗都會調(diào)用 }) } else { reject("error"); }} })})}[/code
.Net WebApi 請求用戶信息接口:
/// <summary> /// 獲取用戶信息 /// </summary> /// <param name="code">信息數(shù)據(jù)code憑證</param> /// <returns></returns> [HttpGet] public IHttpActionResult GetUserInfo(string code) { string AppSecret = "小程序秘鑰"; string AppId = "應(yīng)用程序標(biāo)識"; try { //請求目標(biāo)地址和參數(shù)(authorization_code授權(quán)類型,此處只需填寫 authorization_code) string OauthUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + AppId + "&secret=" + AppSecret + "&js_code=" + code + "&grant_type=authorization_code";//序列化解析數(shù)據(jù) var Result = HttpGet(OauthUrl); return Json(new { openid = Result.openid, errcode = Result.errcode, UnionID = Result.unionid, session_key = Result.session_key }); } catch (Exception ex) { return Json(new { errcode = 1, msg = "獲取用戶信息失敗" + ex.Message }); } } /// <summary> /// 請求code2Session接口獲取用戶信息 /// </summary> /// <param name="requestDataAndUrl">目標(biāo)地址和參數(shù)</param> /// <returns></returns> public WxOauthModle HttpGet(string requestDataAndUrl) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestDataAndUrl); request.Method = "GET"; request.ContentType = "text/html;charset=UTF-8"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close(); return JsonConvert.DeserializeObject<WxOauthModle>(retString); } public class WxOauthModle { /// <summary> /// 用戶唯一標(biāo)識 /// </summary> public string openid { get; set; } /// <summary> /// 會話秘鑰 /// </summary> public string session_key { get; set; } /// <summary> /// 聯(lián)立編號 /// </summary> public string unionid { get; set; } /// <summary> /// 錯誤碼 /// </summary> public int errcode { get; set; } /// <summary> /// 錯誤信息 /// </summary> public string errmsg { get; set; } }
關(guān)于微信網(wǎng)頁開發(fā)通過UnionID機(jī)制解決用戶在不同公眾號,或在公眾號、移動應(yīng)用之間帳號統(tǒng)一問題:
詳情說明請點(diǎn)擊:http://chabaoo.cn/article/168982.htm
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
.net?core?中?WebApiClientCore的使用示例代碼
這篇文章主要介紹了.net?core?中?WebApiClientCore的使用示例代碼,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12ScriptManager.RegisterStartupScript()方法在ajax頁面無效的解決方法
ScriptManager.RegisterStartupScript()方法在ajax頁面無效的解決方法2010-03-03分享Visual Studio原生開發(fā)的10個調(diào)試技巧(2)
這篇文章又為大家分享了Visual Studio原生開發(fā)的10個調(diào)試技巧,感興趣的朋友可以參考一下2015-11-11asp.net中一個linq分頁實(shí)現(xiàn)代碼
asp.net中一個linq分頁實(shí)現(xiàn)代碼,需要的朋友可以參考下。2011-12-12Visual Studio 2017通過SSH調(diào)試Linux上.NET Core
這篇文章主要為大家詳細(xì)介紹了Visual Studio 2017通過SSH調(diào)試Linux 上.NET Core的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03aspx實(shí)現(xiàn)的 jquery ui 的 flexgrid demo
這幾天沒事研究著jquery,真是個好東西,慢慢的知道了有jquery ui,一開始就被華麗的界面和簡單的操作給吸引了,尤其是里面的flexgrid,對我而言可以說是非常寶貴的東西2009-12-12Repeater對數(shù)據(jù)進(jìn)行格式化處理
最近不止一個同學(xué),問我在Repeater里怎么格式化數(shù)據(jù),怎么處理。因?yàn)镽epeater 屬于服務(wù)器端控件。要么利用本身的控件事件來處理,要么在數(shù)據(jù)源上處理。2013-03-03