微信網(wǎng)頁登錄邏輯與實(shí)現(xiàn)

加載微信網(wǎng)頁sdk 繪制登陸二維碼:新tab頁面繪制 / 本頁面iframe繪制 用戶掃碼登陸,前端跳入回調(diào)網(wǎng)址 回調(diào)網(wǎng)址進(jìn)一步做邏輯處理,如果是頁內(nèi)iframe繪制二維碼,需要通知頂級(jí)頁
微信網(wǎng)頁SDK加載
// 備忘錄模式: 防止重復(fù)加載export const loadWeChatjs = (() => {let exists = false; // 打點(diǎn)const src = '//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'; // 微信sdk網(wǎng)址return () => new Promise((resolve, reject) => {// 防止重復(fù)加載if(exists) return resolve(window.WxLogin);let script = document.createElement('script');script.src = src;script.type = 'text/JavaScript';script.onerror = reject; // TODO: 失敗時(shí)候, 可以移除script標(biāo)簽script.onload = () => {exists = true;resolve(window.WxLogin);};document.body.appendChild(script);});})();
繪制登陸二維碼
根據(jù)《微信登陸開發(fā)指南》,將參數(shù)傳遞給window.WxLogin()即可。
// 微信默認(rèn)配置const baseoption = {self_redirect: true, // true: 頁內(nèi)iframe跳轉(zhuǎn); false: 新標(biāo)簽頁打開id: 'wechat-container',appid: 'wechat-appid',scope: 'snsapi_login',redirect_uri: encodeURIComponent('//1.1.1.1/'),state: '',};export const loadQRCode = (option, intl = false, width, height) => {const _option = {...baseOption, ...option};return new Promise((resolve, reject) => {try {window.WxLogin(_option);const ele = document.getElementById(_option['id']);const iframe = ele.querySelector('iframe');iframe.width = width? width : '300';iframe.height = height? height : '420';// 處理國(guó)際化intl && (iframe.src = iframe.src + '&lang=en');resolve(true);} catch(error) {reject(error);}});};
在需要使用的業(yè)務(wù)組件中,可以在周期函數(shù)componentDidMount調(diào)用,下面是demo代碼:
componentDidMount() {const wxOption = {// ...};loadWeChatJs().then(WxLogin => loadQRCode(wxOption)).catch(error => console.log(`Error: ${error.message}`));}
回調(diào)網(wǎng)址與iframe通信
這一塊我覺得是微信登陸交互中最復(fù)雜和難以理解的一段邏輯。開頭有講過,微信二維碼渲染有2中方式,一種是打開新的標(biāo)簽頁,另一種是在指定id的容器中插入iframe。
毫無疑問,第二種交互方式更友好,因?yàn)橐婕安煌?jí)層的頁面通信,代碼處理也更具挑戰(zhàn)。
為了方便說明,請(qǐng)先看模擬的數(shù)據(jù)配置:
// redirect 地址會(huì)被后端拿到, 后端重定向到此地址, 前端會(huì)訪問此頁面// redirect 地址中的參數(shù), 是前端人員留給自己使用的; 后端會(huì)根據(jù)業(yè)務(wù)需要, 添加更多的字段, 然后一起返回前端const querystr = '?' + stringify({redirect: encodeURIComponent(`${window.location.origin}/account/redirect?` + stringify({to: encodeURIComponent(window.location.origin),origin: encodeURIComponent(window.location.origin),state: 'login'})),type: 'login'});const wxOption = {id: 'wechat-container',self_redirect: true,redirect_uri: encodeURIComponent(`//1.1.1.1/api/socials/weixin/authorizations${querystr}`) // 微信回調(diào)請(qǐng)求地址};
前后端、微信服務(wù)器、用戶端交互邏輯
按照上面的配置,我描述一下前端、用戶端、微信服務(wù)器和后端交互的邏輯:
前端根據(jù)wxOption加載了二維碼,所有信息都放在了二維碼中。同時(shí)監(jiān)聽微信服務(wù)器的消息。
用戶手機(jī)掃碼,通知微信服務(wù)器確定登陸。
微信服務(wù)器接受到用戶的掃碼請(qǐng)求,轉(zhuǎn)發(fā)給前端。
前端收到微信服務(wù)器傳來消息,根據(jù)wxOption的redirect_uri參數(shù),跳轉(zhuǎn)到此url地址。注意:
這個(gè)接口地址是后端的,請(qǐng)求方式是GET
前端通過拼接params攜帶參數(shù)
地址會(huì)被拼接微信服務(wù)器傳來的一個(gè)臨時(shí)token,用于交給后端換取用戶公眾密鑰
后端接收到/api/socials/weixin/authorizations${querystr}的請(qǐng)求,decode解碼querystr中的信息。然后向微信服務(wù)端請(qǐng)求用戶公眾密鑰。根絕前后端的約定(demo中用的是redirect字段),重定向到前端指定的redirect字段,并且拼接用戶公眾密鑰等更多信息。
前端知悉重定向,跳到重定向的路由(demo中用的是/account/redirect)
在對(duì)應(yīng)的路由處理后端傳來的用戶密鑰等數(shù)據(jù)即可
至此,微信認(rèn)證的四端交互邏輯完成
跨Iframe通信
前面流程走完了,現(xiàn)在的情況是頁面中iframe的二維碼區(qū)域,已經(jīng)被替換成了/account/redirect?...的內(nèi)容。
為了實(shí)現(xiàn)通信,需要在頁面的周期中監(jiān)聽message事件,并在組件卸載時(shí),卸載此事件:
componentDidMount() {// ... ...window.addEventListener('message', this.msgReceive, false);}componentWillUnmount() {window.removeEventListener('message', this.msgReceive);}msgReceive(event) {// 監(jiān)測(cè)是否是安全iframeif(!event.isTrusted) {return;}console.log(event.data); // 獲取iframe中傳來的數(shù)據(jù), 進(jìn)一步進(jìn)行邏輯處理}
而在/account/redirect?...路由對(duì)應(yīng)的組件中,我們需要解析路由中的params參數(shù),按照業(yè)務(wù)邏輯檢查后,將結(jié)果傳遞給前面的頁面:
componentDidMount() {// step1: 獲取url中params參數(shù)const querys = getQueryvariable(this.props.location.search);// step2: 檢查querys中的數(shù)據(jù)是否符合要求 ...// step3: 向頂級(jí)頁面?zhèn)鬟f消息return window.parent && window.parent.postMessage('data', '*');}
至此,微信網(wǎng)頁認(rèn)證的流程完成。
學(xué)習(xí)更多技能
請(qǐng)點(diǎn)擊下方公眾號(hào)
![]()

