1. 程式人生 > >.Net之微信小程式獲取使用者UnionID

.Net之微信小程式獲取使用者UnionID

前言:

  在實際專案開發中我們經常會遇到賬號統一的問題,如何在不同端或者是不同的登入方式下保證同一個會員或者使用者賬號唯一(便於使用者資訊的管理)。這段時間就有一個這樣的需求,之前有個客戶做了一個微信小程式商城(店主端的),然後現在又要做一個會員購物端的小程式商場。首先之前使用者登入憑證都是使用微信openid來做的唯一標識,而現在客戶需求是要做到使用者在會員端小程式跳轉到到店主端小程式假如之前該使用者微信是在店主端稽核通過的使用者則不需要在進行資料提交稽核操作,直接登入。所以,所以我們使用了UnionID來進行關聯,如下是我們現在專案的基本流程(畫的醜莫見怪)。

說說UnionID機制:

  如果開發者擁有多個移動應用、網站應用、和公眾帳號(包括小程式),可通過 UnionID 來區分使用者的唯一性,因為只要是同一個微信開放平臺帳號下的移動應用、網站應用和公眾帳號(包括小程式),使用者的 UnionID 是唯一的。換句話說,同一使用者,對同一個微信開放平臺下的不同應用,unionid是相同的。

官方UnionID機制詳細說明:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html

微信開放平臺繫結小程式流程:

登入微信開放平臺 — 管理中心 — 小程式 — 繫結小程式(直接使用微信官方圖)

微信小程式獲取UnoinID的兩種方式:

呼叫介面 wx.getUserInfo,從解密資料(encryptedData)中獲取 UnionID(推薦使用):

推薦使用原因:無需關注微信公眾號即可獲取到UnionID。

呼叫介面wx.getUserInfo前提:使用者允許授權獲取使用者資訊!

開發者後臺校驗與解密開放資料:

  微信為了保證使用者資訊,把使用者通過wx.getUserInfo介面獲取到的相關敏感資訊進行了加密。加密方式對稱加密(後面會提到),首先我們需要通過微信小程式登入流程獲取到使用者的session_key(會話金鑰),然後我們可以報獲取到的會話金鑰使用快取存起來,在通過使用者授權獲取使用者相關資訊,如下是使用者授權成功獲取到的使用者資訊:

基本流程圖如下:

 

 

(encryptedData)加密資料解密演算法:

開發者如需要獲取敏感資料,需要對介面返回的加密資料(encryptedData) 進行對稱解密。 解密演算法如下:

  1. 對稱解密使用的演算法為 AES-128-CBC,資料採用PKCS#7填充。
  2. 對稱解密的目標密文為 Base64_Decode(encryptedData)。
  3. 對稱解密祕鑰 aeskey = Base64_Decode(session_key), aeskey 是16位元組。
  4. 對稱解密演算法初始向量 為Base64_Decode(iv),其中iv由資料介面返回

很遺憾的是微信居然沒有為我們大.Net提供解密演算法demo,實屬讓人不算,最後自己根據網上的資料還是配上了符合微信對稱加密的解密演算法。

程式碼實現:

首先關於session_key(會話金鑰)的獲取,請看下面的wx.login+code2Session 方式

呼叫介面wx.getUserInfo獲取encryptedData(加密資料)和iv(初始向量):

// 使用者已經授權
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)
}
})
}
})
})

.Net WebApi 解密資料介面:

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

注意引數使用Convert.FromBase64String轉化,提示“Base-64字元陣列的無效長度” 的問題:

原因:加密引數中的"+"通過位址列傳過來時,後臺會解析為空格。

解決:最好的做法是 使用encryptedData.Replace("+", "%2B")先將空格編碼,然後再作為引數傳給另一頁面傳遞,這樣頁面在提取引數時才會將“%2B”解碼為加號.但這兒為了簡化,將空格直接還原為"+"或者是直接在後臺將空格替換為“+”encryptedData.Replace(' ', '+');

直接通過 wx.login + code2Session 獲取到該使用者 UnionID:

其實這個方式就是實現了小程式的登入流程,微信官方詳細說明:

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

優點:無需使用者授權。

前提:使用者需要關注該微信公眾號。

小程式端呼叫介面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

詳細說明請看微信官方文件(程式碼略):https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html

2.通過請求wx.login獲取code憑證,在向.net webapi後端請求code2Session介面:

原因:因為我們需要對獲取的使用者資訊做相關業務邏輯處理。

微信小程式端程式碼:
/**
*封裝使用者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");
}}
}) })}
.Net WebApi 請求使用者資訊介面:
        /// <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; }
        }

關於微信網頁開發通過UnionID機制解決使用者在不同公眾號,或在公眾號、移動應用之間帳號統一問題:

詳情說明請點選:https://www.cnblogs.com/Can-daydayup/p/9368844.html