在實際項目開發中咱們常常會遇到帳號統一的問題,如何在不一樣端或者是不一樣的登陸方式下保證同一個會員或者用戶帳號惟一(便於用戶信息的管理)。這段時間就有一個這樣的需求,以前有個客戶作了一個微信小程序商城(店主端的),而後如今又要作一個會員購物端的小程序商場。首先以前用戶登陸憑證都是使用微信openid來作的惟一標識,而如今客戶需求是要作到用戶在會員端小程序跳轉到到店主端小程序假如以前該用戶微信是在店主端審覈經過的用戶則不須要在進行資料提交審覈操做,直接登陸。因此,因此咱們使用了UnionID來進行關聯,以下是咱們如今項目的基本流程(畫的醜莫見怪)。html
若是開發者擁有多個移動應用、網站應用、和公衆賬號(包括小程序),可經過 UnionID 來區分用戶的惟一性,由於只要是同一個微信開放平臺賬號下的移動應用、網站應用和公衆賬號(包括小程序),用戶的 UnionID 是惟一的。換句話說,同一用戶,對同一個微信開放平臺下的不一樣應用,unionid是相同的。web
官方UnionID機制詳細說明:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html算法
登陸微信開放平臺 — 管理中心 — 小程序 — 綁定小程序(直接使用微信官方圖)json
推薦使用緣由:無需關注微信公衆號便可獲取到UnionID。小程序
調用接口wx.getUserInfo前提:用戶容許受權獲取用戶信息!後端
微信爲了保證用戶信息,把用戶經過wx.getUserInfo接口獲取到的相關敏感信息進行了加密。加密方式對稱加密(後面會提到),首先咱們須要經過微信小程序登陸流程獲取到用戶的session_key(會話密鑰),而後咱們能夠報獲取到的會話密鑰使用緩存存起來,在經過用戶受權獲取用戶相關信息,以下是用戶受權成功獲取到的用戶信息:微信小程序
開發者如須要獲取敏感數據,須要對接口返回的加密數據(encryptedData) 進行對稱解密。 解密算法以下:api
很遺憾的是微信竟然沒有爲咱們大.Net提供解密算法demo,實屬讓人不算,最後本身根據網上的資料仍是配上了符合微信對稱加密的解密算法。數組
代碼實現:promise
首先關於session_key(會話密鑰)的獲取,請看下面的wx.login+code2Session 方式
// 用戶已經受權 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' // 默認值 }, success (res) { //解密返回過來的UnionID console.log(res.data) } }) } }) })
/// <summary> /// 解密微信對稱加密數據,獲取用戶聯合運營編號 /// </summary> /// <param name="sessionKey">臨時會話祕鑰</param> /// <param name="encryptedData">微信用戶敏感加密數據</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">待解密的字節數組</param> /// <param name="sessionKey">解密密鑰字節數組</param> /// <param name="iv">IV初始化向量字節數組</param> /// <param name="cipher">運算模式</param> /// <param name="padding">填充模式</param> /// <returns></returns> private static string DecryptByAesBytes(string encryptedData, string sessionKey, string iv) { try { //非空驗證 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對稱解密結果爲:" + decryptResult); return decryptData.unionid; } else { return ""; } } catch (Exception e) { JJHL.Utility.Loghelper.WriteLog("AES對稱解密失敗緣由:" + e.Message); return ""; } } #endregion
緣由:加密參數中的"+"經過地址欄傳過來時,後臺會解析爲空格(遇到的機率比較小)。
解決:最好的作法是 使用encryptedData.Replace("+", "%2B")先將空格編碼,而後再做爲參數傳給另外一頁面傳遞,這樣頁面在提取參數時纔會將「%2B」解碼爲加號.但這兒爲了簡化,將空格直接還原爲"+"或者是直接在後臺將空格替換爲「+」encryptedData.Replace(' ', '+');
code2Session
獲取到該用戶 UnionID:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
優勢:無需用戶受權。
前提:用戶須要關注該微信公衆號。
GET:https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
詳細說明請看微信官方文檔(代碼略):https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
緣由:由於咱們須要對獲取的用戶信息作相關業務邏輯處理。
/** *封裝用戶promise登陸,經過code憑證獲取用戶信息(UnionID,openid,session_key會話密鑰) */ userLogin: function() { var that = this; //定義promise方法 return new Promise(function(resolve, reject) { //調用登陸接口 wx.login({ success: function(res) { if (res.code) { console.log("用戶登陸受權code爲:" + res.code); //調用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' // 默認值 }, success(res) { console.log(res.data) if (res.data.errcode == 0) { //存入session緩存中 console.log(res.data.openid);//微信用戶惟一標識 console.log(res.data.UnionID);//微信開發平臺聯合ID console.log(res.data.session_key);//會話密鑰
//***注意****
//注意:這裏是直接把session_key緩存起來,在上面wx.getUserInfo會使用到
wx.setStorageSync("session_key",res.data.session_key);
//promise機制放回成功數據
resolve(res.data);
} else
{ reject('error'); }
}, fail: function(res)
{
reject(res);
wx.showToast({ title: '系統錯誤' })
}, complete: () => { } //complete接口執行後的回調函數,不管成功失敗都會調用
}) } else
{
reject("error");
}}
}) })}
/// <summary> /// 獲取用戶信息 /// </summary> /// <param name="code">信息數據code憑證</param> /// <returns></returns> [HttpGet] public IHttpActionResult GetUserInfo(string code) { string AppSecret = "小程序祕鑰"; string AppId = "應用程序標識"; try { //請求目標地址和參數(authorization_code受權類型,此處只需填寫 authorization_code)
string OauthUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + AppId + "&secret=" + AppSecret + "&js_code=" + code + "&grant_type=authorization_code";//序列化解析數據 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">目標地址和參數</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> /// 用戶惟一標識 /// </summary> public string openid { get; set; } /// <summary> /// 會話祕鑰 /// </summary> public string session_key { get; set; } /// <summary> /// 聯立編號 /// </summary> public string unionid { get; set; } /// <summary> /// 錯誤碼 /// </summary> public int errcode { get; set; } /// <summary> /// 錯誤信息 /// </summary> public string errmsg { get; set; } }