在实际项目开发中咱们常常会遇到帐号统一的问题,如何在不一样端或者是不一样的登陆方式下保证同一个会员或者用户帐号惟一(便于用户信息的管理)。这段时间就有一个这样的需求,以前有个客户作了一个微信小程序商城(店主端的),而后如今又要作一个会员购物端的小程序商场。首先以前用户登陆凭证都是使用微信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; } }