.Net之微信小程序獲取用戶UnionID的實(shí)現(xiàn)
前言:
在實(shí)際項(xiàng)目開(kāi)發(fā)中我們經(jīng)常會(huì)遇到賬號(hào)統(tǒng)一的問(wèn)題,如何在不同端或者是不同的登錄方式下保證同一個(gè)會(huì)員或者用戶賬號(hào)唯一(便于用戶信息的管理)。這段時(shí)間就有一個(gè)這樣的需求,之前有個(gè)客戶做了一個(gè)微信小程序商城(店主端的),然后現(xiàn)在又要做一個(gè)會(huì)員購(gòu)物端的小程序商場(chǎng)。首先之前用戶登錄憑證都是使用微信openid來(lái)做的唯一標(biāo)識(shí),而現(xiàn)在客戶需求是要做到用戶在會(huì)員端小程序跳轉(zhuǎn)到到店主端小程序假如之前該用戶微信是在店主端審核通過(guò)的用戶則不需要在進(jìn)行資料提交審核操作,直接登錄。所以,所以我們使用了UnionID來(lái)進(jìn)行關(guān)聯(lián),如下是我們現(xiàn)在項(xiàng)目的基本流程(畫(huà)的丑莫見(jiàn)怪)。

說(shuō)說(shuō)UnionID機(jī)制:
如果開(kāi)發(fā)者擁有多個(gè)移動(dòng)應(yīng)用、網(wǎng)站應(yīng)用、和公眾帳號(hào)(包括小程序),可通過(guò) UnionID 來(lái)區(qū)分用戶的唯一性,因?yàn)橹灰峭粋€(gè)微信開(kāi)放平臺(tái)帳號(hào)下的移動(dòng)應(yīng)用、網(wǎng)站應(yīng)用和公眾帳號(hào)(包括小程序),用戶的 UnionID 是唯一的。換句話說(shuō),同一用戶,對(duì)同一個(gè)微信開(kāi)放平臺(tái)下的不同應(yīng)用,unionid是相同的。
官方UnionID機(jī)制詳細(xì)說(shuō)明:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
微信開(kāi)放平臺(tái)綁定小程序流程:
登錄微信開(kāi)放平臺(tái)— 管理中心 — 小程序 — 綁定小程序(直接使用微信官方圖)

微信小程序獲取UnoinID的兩種方式
調(diào)用接口wx.getUserInfo,從解密數(shù)據(jù)(encryptedData)中獲取 UnionID(推薦使用):
推薦使用原因:無(wú)需關(guān)注微信公眾號(hào)即可獲取到UnionID。
調(diào)用接口wx.getUserInfo前提:用戶允許授權(quán)獲取用戶信息!
開(kāi)發(fā)者后臺(tái)校驗(yàn)與解密開(kāi)放數(shù)據(jù):
微信為了保證用戶信息,把用戶通過(guò)wx.getUserInfo接口獲取到的相關(guān)敏感信息進(jìn)行了加密。加密方式對(duì)稱加密(后面會(huì)提到),首先我們需要通過(guò)微信小程序登錄流程獲取到用戶的session_key(會(huì)話密鑰),然后我們可以報(bào)獲取到的會(huì)話密鑰使用緩存存起來(lái),在通過(guò)用戶授權(quán)獲取用戶相關(guān)信息,如下是用戶授權(quán)成功獲取到的用戶信息:

基本流程圖如下:

(encryptedData)加密數(shù)據(jù)解密算法:
開(kāi)發(fā)者如需要獲取敏感數(shù)據(jù),需要對(duì)接口返回的加密數(shù)據(jù)(encryptedData)進(jìn)行對(duì)稱解密。 解密算法如下:
- 對(duì)稱解密使用的算法為 AES-128-CBC,數(shù)據(jù)采用PKCS#7填充。
- 對(duì)稱解密的目標(biāo)密文為 Base64_Decode(encryptedData)。
- 對(duì)稱解密秘鑰 aeskey = Base64_Decode(session_key), aeskey 是16字節(jié)。
- 對(duì)稱解密算法初始向量 為Base64_Decode(iv),其中iv由數(shù)據(jù)接口返回
很遺憾的是微信居然沒(méi)有為我們大.Net提供解密算法demo,實(shí)屬讓人不算,最后自己根據(jù)網(wǎng)上的資料還是配上了符合微信對(duì)稱加密的解密算法。
代碼實(shí)現(xiàn):
首先關(guān)于session_key(會(huì)話密鑰)的獲取,請(qǐng)看下面的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");//臨時(shí)會(huì)話密鑰,通過(guò)小程序登錄流程獲取到的
//請(qǐng)求.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) {
//解密返回過(guò)來(lái)的UnionID
console.log(res.data)
}
})
}
})
})
.Net WebApi 解密數(shù)據(jù)接口:
/// <summary>
/// 解密微信對(duì)稱加密數(shù)據(jù),獲取用戶聯(lián)合運(yùn)營(yíng)編號(hào)
/// </summary>
/// <param name="sessionKey">臨時(shí)會(huì)話秘鑰</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對(duì)稱解密
/// <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對(duì)稱解密結(jié)果為:" + decryptResult);
return decryptData.unionid;
}
else
{
return "";
}
}
catch (Exception e)
{
JJHL.Utility.Loghelper.WriteLog("AES對(duì)稱解密失敗原因:" + e.Message);
return "";
}
}
#endregion
所遇異常:參數(shù)使用Convert.FromBase64String轉(zhuǎn)化時(shí),提示“Base-64字符數(shù)組的無(wú)效長(zhǎng)度” 的問(wèn)題:
原因:加密參數(shù)中的"+"通過(guò)地址欄傳過(guò)來(lái)時(shí),后臺(tái)會(huì)解析為空格(遇到的概率比較?。?。
解決:最好的做法是 使用encryptedData.Replace("+", "%2B")先將空格編碼,然后再作為參數(shù)傳給另一頁(yè)面?zhèn)鬟f,這樣頁(yè)面在提取參數(shù)時(shí)才會(huì)將“%2B”解碼為加號(hào).但這兒為了簡(jiǎn)化,將空格直接還原為"+"或者是直接在后臺(tái)將空格替換為“+”encryptedData.Replace(' ', '+');
直接通過(guò)wx.login+code2Session獲取到該用戶 UnionID:
其實(shí)這個(gè)方式就是實(shí)現(xiàn)了小程序的登錄流程,微信官方詳細(xì)說(shuō)明:
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
優(yōu)點(diǎn):無(wú)需用戶授權(quán)。
前提:用戶需要關(guān)注該微信公眾號(hào)。
小程序端調(diào)用接口wx.login獲取code憑證,在通過(guò)請(qǐng)求auth.code2Session接口獲取用戶信息(UnionID,openid,session_key會(huì)話密鑰)兩種方式:
1.直接通過(guò)wx.login請(qǐng)求到code憑證后,在請(qǐng)求該地址獲取用戶信息:
GET:https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
詳細(xì)說(shuō)明請(qǐng)看微信官方文檔(代碼略):https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
2.通過(guò)請(qǐng)求wx.login獲取code憑證,在向.net webapi后端請(qǐng)求code2Session接口:
原因:因?yàn)槲覀冃枰獙?duì)獲取的用戶信息做相關(guān)業(yè)務(wù)邏輯處理。
微信小程序端代碼:
復(fù)制代碼 代碼如下:/***封裝用戶promise登錄,通過(guò)code憑證獲取用戶信息(UnionID,openid,session_key會(huì)話密鑰)*/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請(qǐng)求傳遞code憑證換取用戶openid,并獲取后臺(tái)用戶信息wx.request({url: 'https://www.xxxx.xxx.api/User_oAuth/GetUserInfo',//后臺(tái)請(qǐng)求用戶信息方法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)識(shí)console.log(res.data.UnionID);//微信開(kāi)發(fā)平臺(tái)聯(lián)合IDconsole.log(res.data.session_key);//會(huì)話密鑰 //***注意**** //注意:這里是直接把session_key緩存起來(lái),在上面wx.getUserInfo會(huì)使用到 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)錯(cuò)誤' }) }, complete: () => { } //complete接口執(zhí)行后的回調(diào)函數(shù),無(wú)論成功失敗都會(huì)調(diào)用 }) } else { reject("error"); }} })})}[/code
.Net WebApi 請(qǐng)求用戶信息接口:
/// <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)識(shí)";
try
{
//請(qǐng)求目標(biāo)地址和參數(shù)(authorization_code授權(quán)類型,此處只需填寫(xiě) 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>
/// 請(qǐng)求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)識(shí)
/// </summary>
public string openid { get; set; }
/// <summary>
/// 會(huì)話秘鑰
/// </summary>
public string session_key { get; set; }
/// <summary>
/// 聯(lián)立編號(hào)
/// </summary>
public string unionid { get; set; }
/// <summary>
/// 錯(cuò)誤碼
/// </summary>
public int errcode { get; set; }
/// <summary>
/// 錯(cuò)誤信息
/// </summary>
public string errmsg { get; set; }
}
關(guān)于微信網(wǎng)頁(yè)開(kāi)發(fā)通過(guò)UnionID機(jī)制解決用戶在不同公眾號(hào),或在公眾號(hào)、移動(dòng)應(yīng)用之間帳號(hào)統(tǒng)一問(wèn)題:
詳情說(shuō)明請(qǐng)點(diǎn)擊:http://www.fzitv.net/article/168982.htm
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ASP.NET記住登陸用戶名的具體實(shí)現(xiàn)
ASP.NET記住登陸用戶名的具體實(shí)現(xiàn),需要的朋友可以參考一下2013-06-06
MVC、MVP和MVVM分別是什么_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
MVC,MVP 和 MVVM分別是什么?MVC(Model-View-Controller)是最常見(jiàn)的軟件架構(gòu)之一,業(yè)界有著廣泛應(yīng)用。它本身很容易理解,但是要講清楚,它與衍生的 MVP 和 MVVM 架構(gòu)的區(qū)別就不容易了。2017-08-08
ASP.NET返回上一頁(yè)面的實(shí)現(xiàn)代碼
今天用ASP.NET做一個(gè)類似淘寶中的訂單管理系統(tǒng),遇到的問(wèn)題:主界面通過(guò)一個(gè)超鏈接跳轉(zhuǎn)到子界面后,試了好長(zhǎng)時(shí)間還是無(wú)法從子界面返回主界面。2011-04-04
ASP.NET?Core?MVC中的標(biāo)簽助手(TagHelper)用法
這篇文章介紹了ASP.NET?Core?MVC中標(biāo)簽助手(TagHelper)的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04
輕量級(jí)ORM框架Dapper應(yīng)用之實(shí)現(xiàn)In操作
這篇文章介紹了使用Dapper實(shí)現(xiàn)In操作的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
ASP.NET簡(jiǎn)單獲取服務(wù)端和客戶端計(jì)算機(jī)名稱的方法
這篇文章主要介紹了ASP.NET簡(jiǎn)單獲取服務(wù)端和客戶端計(jì)算機(jī)名稱的方法,涉及asp.net獲取服務(wù)器端計(jì)算機(jī)名以及根據(jù)IP獲取客戶端主機(jī)名的相關(guān)技巧,需要的朋友可以參考下2016-08-08
菜渣開(kāi)源一個(gè)基于?EMIT?的?AOP?庫(kù)(.NET?Core)的方法
CZGL.AOP?是?基于?EMIT?編寫(xiě)的?一個(gè)簡(jiǎn)單輕量的AOP框架,支持非侵入式代理,支持.NET?Core/ASP.NET?Core,以及支持多種依賴注入框架,本文介紹菜渣開(kāi)源一個(gè)基于?EMIT?的?AOP?庫(kù)(.NET?Core)的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-06-06

