基于IdentityServer的系统对接微信公众号
業(yè)務(wù)需求
公司有兩個(gè)業(yè)務(wù)系統(tǒng),A和B,AB用戶之間屬于多對(duì)一的關(guān)系,數(shù)據(jù)庫(kù)里面也就是兩張表,A表有個(gè)外鍵指向B。現(xiàn)在需要實(shí)現(xiàn)以下幾個(gè)功能。
A用戶掃描B的二維碼,填寫相關(guān)的注冊(cè)信息,注冊(cè)完成之后自動(dòng)屬于B。也就是表A的外加字段指向B。
老用戶和微信openid綁定。
用戶在公眾號(hào)里面自動(dòng)登錄。
項(xiàng)目結(jié)構(gòu)
公司項(xiàng)目基于.net core 2.1 + Vue,
后端有以下幾個(gè)子系統(tǒng):
基于IdentityServer4?的asp.net mvc,簡(jiǎn)稱account 項(xiàng)目,配了域名account.xxx.com
兩個(gè)業(yè)務(wù)系統(tǒng)api。A和B。分別域名配置aapi.xxx.com 和bapi.xxx.com
其他。。
前端有以下幾個(gè)系統(tǒng),都是基于Vue的SPA:
A業(yè)務(wù)系統(tǒng),域名a.xxx.com
B業(yè)務(wù)系統(tǒng),域名b.xxx.com
其他。
登錄這塊的邏輯實(shí)現(xiàn)方式是類似的。都是基于IdentityModel/oidc-client-js
簡(jiǎn)單介紹一下IdentityServer這個(gè)東西。
用戶登錄A或B系統(tǒng),就是調(diào)用A和B對(duì)應(yīng)的webapi,webapi配置了自己的驗(yàn)證服務(wù)器是account服務(wù)器,account驗(yàn)證未通過(guò),前端就得到401狀態(tài)碼,通過(guò)oidc-client-js的內(nèi)部方法引導(dǎo)用戶進(jìn)行登錄。跳轉(zhuǎn)account的頁(yè)面,用戶輸入用戶名密碼,登錄成功,account服務(wù)器判斷是A or B過(guò)來(lái)的登錄請(qǐng)求,帶上token回跳到配置的對(duì)應(yīng)頁(yè)面。業(yè)務(wù)系統(tǒng)再次用獲取到的token請(qǐng)求api,調(diào)用成功。用一個(gè)圖來(lái)說(shuō)。
實(shí)現(xiàn)方法
用了盛派微信sdk,特別感謝大佬的貢獻(xiàn)。
推薦一下微信沙箱環(huán)境,項(xiàng)目做完下來(lái)除了"無(wú)法在測(cè)試的公眾號(hào)里面推送小程序消息”無(wú)法實(shí)現(xiàn)之外(因?yàn)橥扑偷男枰娞?hào)和小程序有一個(gè)綁定關(guān)系),其他都o(jì)k。
因?yàn)榘姹镜年P(guān)系,account系統(tǒng)升級(jí)了asp.net core 2.2。
先實(shí)現(xiàn)上面第一個(gè)需求
這里用到微信里面生成帶參數(shù)的二維碼功能。B系統(tǒng)創(chuàng)建了用戶之后,生成一個(gè)對(duì)應(yīng)的guid,然后把這個(gè)guid作為參數(shù),調(diào)用sdk就能得到二維碼的url。
var qrRstTicketRst = await QrCodeApi.CreateAsync(weixinSetting.WeixinAppId, 30, 100000,
QrCode_ActionName.QR_LIMIT_STR_SCENE, sceneId);
var url = QrCodeApi.GetShowQrCodeUrl(qrRstTicketRst.ticket);
這里我們項(xiàng)目中用到的是永久二維碼,雖然這個(gè)二維碼上限10W個(gè),我們業(yè)務(wù)系統(tǒng)B用戶不會(huì)超過(guò)那么多。
B用戶展示二維碼給A用戶,A用戶掃描,根據(jù)文檔:
如果用戶還未關(guān)注公眾號(hào),則用戶可以關(guān)注公眾號(hào),關(guān)注后微信會(huì)將帶場(chǎng)景值關(guān)注事件推送給開發(fā)者。
如果用戶已經(jīng)關(guān)注公眾號(hào),在用戶掃描后會(huì)自動(dòng)進(jìn)入會(huì)話,微信也會(huì)將帶場(chǎng)景值掃描事件推送給開發(fā)者。
觸發(fā)代碼里面分別對(duì)應(yīng)的是OnEvent_SubscribeRequest?和OnEvent_ScanRequest,兩個(gè)方法里面的代碼基本上是一樣的。RequestMessageEvent_Scan.EventKey可以得到上面的guid值。Subscribe事件里面得到的EventKey會(huì)比Scan的多一個(gè)qrscene前綴,處理的時(shí)候要注意一點(diǎn)。兩個(gè)方法參數(shù)都能通過(guò)FromUserName獲取到掃描的用戶的openId,然后在這個(gè)方法里面返回一個(gè)帶參數(shù)(A的openId,和B的guid)的注冊(cè)鏈接,A用戶注冊(cè)的時(shí)候就提交了這兩個(gè)參數(shù),后臺(tái)就能拿到。
順道說(shuō)一句,公眾號(hào)里面用戶每次操作只能被動(dòng)返回一條消息。如要主動(dòng)推送,需要用模板消息的方式。
實(shí)現(xiàn)第二個(gè)需求
對(duì)于老用戶,這里需要一個(gè)賬號(hào)綁定的功能。也就是業(yè)務(wù)系統(tǒng)的賬號(hào)和openId做一個(gè)關(guān)聯(lián)。綁定的關(guān)鍵在于這個(gè)如何獲取這個(gè)openId,這里有兩種方式。
用戶點(diǎn)擊公眾號(hào)的菜單,后端獲取到這個(gè)事件,在OnEvent_ClickRequest中,判斷RequestMessageEvent_Click.EventKey==xxx,返回一個(gè)帶openId的綁定頁(yè)面的鏈接給用戶。比如/bind?openId=xxx,用戶點(diǎn)擊這個(gè)鏈接,系統(tǒng)引導(dǎo)用戶登錄,然后點(diǎn)擊綁定按鈕,實(shí)現(xiàn)綁定。
基于微信網(wǎng)頁(yè)授權(quán),這個(gè)在自動(dòng)登錄里面也用到了,所以下面解釋。
實(shí)現(xiàn)第三個(gè)需求
系統(tǒng)中用戶和微信的openId已經(jīng)綁定,所以,只要知道每次訪問(wèn)頁(yè)面的openId就應(yīng)該能實(shí)現(xiàn)自動(dòng)登錄。openId是通過(guò)微信網(wǎng)頁(yè)授權(quán)的方式獲取到。流程可以看文檔。簡(jiǎn)單來(lái)說(shuō),先拿code,再換token,同時(shí)拿到openId。實(shí)現(xiàn)步驟分以下幾步。
添加一個(gè)公眾號(hào)菜單,type是view,也就是點(diǎn)了之后會(huì)打開一個(gè)頁(yè)面,頁(yè)面地址直接用獲取code的url.
"type": "view",
"name": "登錄A",
"url":"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxb66259f2a353&redirect_uri=http%3A%2F%2Faccount.xxx.cn%2Fweixincallback%2Fcallback&response_type=code&scope=snsapi_base&state=spa.A#wechat_redirect"
}
url中的state參數(shù)會(huì)和code一起返回給設(shè)置的redirectUrl,這個(gè)可以用來(lái)給我們?cè)赼ccount登錄中心判斷是需要登錄A還是B,以便最后回跳到對(duì)應(yīng)的業(yè)務(wù)頁(yè)面。
這里在沙箱配置跳轉(zhuǎn)域名的時(shí)候注意一下,只要寫域名就好。
打開頁(yè)面,需要用戶點(diǎn)允許授權(quán)。通過(guò)之后瀏覽器會(huì)把code 和 state參數(shù)帶這get請(qǐng)求redirect_url
callback頁(yè)面的邏輯
callback 接收code和state兩個(gè)參數(shù)。
public async Task<IActionResult> Callback(string code, string state){...}用這個(gè)code調(diào)用sdk里面的api獲取token,同時(shí)可以拿到openid。
var tokenResult = await OAuthApi.GetAccessTokenAsync(AppId, AppSecret, code);if (tokenResult.errcode != ReturnCode.請(qǐng)求成功)
{
throw new BizException("獲取微信用戶信息失敗");
}
var openId = tokenResult.openid;
通過(guò)open獲取用戶信息。
var userInfo = await userService.GetByOpenId(openId);然后調(diào)用HttpContext.SignInAsync登錄。
public static async Task SignInAsync(this HttpContext context, string subject, string name, AuthenticationProperties properties, params Claim[] claims){
var clock = context.GetClock();
var user = new IdentityServerUser(subject)
{
DisplayName = name,
AdditionalClaims = claims,
AuthenticationTime = clock.UtcNow.UtcDateTime
};
await context.SignInAsync(user, properties);
}
HttpContext是當(dāng)前請(qǐng)求的上下文。
subject可以理解為用戶的標(biāo)識(shí)。
name可以理解是用戶顯示的名字。
AuthenticationProperties是此次認(rèn)證的一些配置,比如有效時(shí)長(zhǎng)之類的。
Claim可以理解為這個(gè)subject帶的一些屬性。
await HttpContext.SignInAsync(userMobile, userName, props, claims);調(diào)用完之后就登錄成功。
然后通過(guò)帶來(lái)的state參數(shù)判斷需要跳轉(zhuǎn)的client。
var client = await clientStore.FindClientByIdAsync(state);return Redirect($"{client.PostLogoutRedirectUris.FirstOrDefault()}?logined=true");
這里帶一個(gè)logined=true參數(shù),用來(lái)給client做一些邏輯。
總結(jié)
首先要感謝的肯定是盛派微信sdk的contributors,沒(méi)有他們系統(tǒng)對(duì)接起來(lái)應(yīng)該會(huì)慢很多。
然后我想說(shuō),IdentityServer是個(gè)好東西,現(xiàn)在公司.NET相關(guān)的系統(tǒng)都已經(jīng)用這個(gè)實(shí)現(xiàn)統(tǒng)一的登錄邏輯了,系統(tǒng)維護(hù)的代價(jià)小了許多。
說(shuō)起來(lái)其實(shí)也是第一次對(duì)接微信公眾號(hào)相關(guān)的東西,在走通這條路之前走了不少?gòu)澛?#xff0c;不過(guò)好在走通了。希望對(duì)其他人有幫助。
總結(jié)
以上是生活随笔為你收集整理的基于IdentityServer的系统对接微信公众号的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 结合使用 Draft 与 Tencent
- 下一篇: 在Windows上使用Docker运行.