前言:
在實(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
<https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html>
微信開(kāi)放平臺(tái)綁定小程序流程:
登錄微信開(kāi)放平臺(tái) <https://open.weixin.qq.com/>?— 管理中心 — 小程序 — 綁定小程序(直接使用微信官方圖)
微信小程序獲取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ò)
微信小程序登錄流程
<https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html>
獲取到用戶的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
<https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html>
?+?code2Session?獲取到該用戶 UnionID:
其實(shí)這個(gè)方式就是實(shí)現(xiàn)了小程序的登錄流程,微信官方詳細(xì)說(shuō)明:
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
<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
<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ù)邏輯處理。
微信小程序端代碼:
/** *封裝用戶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)合ID console.log(res.data.session_key);//會(huì)話密鑰
//***注意****
//注意:這里是直接把session_key緩存起來(lái),在上面wx.getUserInfo會(huì)使用到
wx.setStorageSync("session_key",res.data.session_key); //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");
}}
}) })}
.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)擊:https://www.cnblogs.com/Can-daydayup/p/9368844.html
<https://www.cnblogs.com/Can-daydayup/p/9368844.html>
熱門(mén)工具 換一換
