Web應用多賬號系統設計及微信掃碼登錄實現
1 前言概述
公司對功能測試,性能測試,安全測試等等都做了比較好的自動化后,急需要一個MIS系統來統一管理這些結果及報表。
此MIS系統特點如下:
- 僅內部人員使用
- 部署在公網
基於如上特點,顯然讓公司的人為這樣一個內部系統而實現一個完整的賬號不太現實,要兼顧隱私性和便捷性的需求,作者想到了使用微信掃碼登錄來做為身份認證,然后后台管理員審核,這樣就可以達到如下的效果:
- 可以實現無門檻注冊(微信掃一掃就完成注冊),保證了便捷性
- 系統對未審核通過的人員進行隔離,保證了隱私性
然后在開發完畢此系統后,覺得有必要再總結而且小升華一下,於是就有了本文的寫作動機。
2 多賬號原理
本來本文的目的是 “實現微信掃碼登錄” ,但是后來覺得僅僅是為了實現這個功能,本文的立意又顯得太低。所以就在此處擴展一下為 “多賬號授權登錄系統” 。
在近些年來,隨着互聯網越來越開發和協作,目前的系統登錄方式也越來越多,已經遠遠超越了以前的單用戶名的方式了。除了用戶名密碼外,一般網站還提供如下的登錄方式:
-
- 第三方授權
-
- 微信/QQ/新浪微博(國內)
- Google/Facebook/Github(國外)
-
- 綁定賬號
-
- 手機號
- 郵箱號
基於如上的幾種登錄方式,就形成了如下的 “多賬號登錄體系圖”:
基本原理:
-
- 第三方授權
-
- 能夠從可信第三方獲取到相應的返回值(用戶信息),然后和 user_id 綁定
- 不需要額外再輸入密碼便完成鑒權過程
- 會創建一組今后可以修改的 user_id 作為 占位用戶
- 鑒權成功后設置session狀態
-
- 綁定賬號
-
- 事先已經完成了 user_id 的注冊
- 完成了相應賬號綁定,即表示認可和 user_id 均能登錄
- 使用和 user_id 同樣的或者不同的密碼體系(一般使用相同密碼)登錄完成鑒權
- 鑒權成功后設置session狀態
關於 綁定賬號 的方式比較簡單,此處就不再贅述。
基於 第三方授權 的方式,則比較精妙,可學習性比較強,因為基於互聯網越來越開放的特性,此方式肯定會越來越多的被應用,越來越成為主流。下面將以 微信掃碼 授權登錄為例子來進行講解。
3 掃碼登錄邏輯
使用 微信掃碼 授權登錄的邏輯圖如下:
其中主要處理的事情如下:
- 向第三方發起鑒權請求
- 第三方鑒權回調
- 和MIS系統本地 user_id 體系關聯(新建用戶)
- 設置session登錄狀態
- 處理不同結果的顯示界面
4 微信掃碼過程
使用過微信掃碼登錄系統的人會有如下的過程體驗(以著名社交網站 知乎 為例子):
- 打開 知乎 主頁,點擊 “微信登錄” 的圖標
- 瀏覽器重定向到微信域(見下圖標記1)下面的二維碼頁面
- 用戶掏出手機打開微信,掃一掃
- 在手機微信上點擊授權
- PC上面的二維碼頁面顯示授權成功,並轉向到 知乎 首頁,認證成功
整個過程對於終端用戶來說,只有短短幾秒,而且不用輸入任何密碼,可以說是一種非常安全又便捷的體驗。
那么問題來了,通過微信掃描二維碼,並完成MIS系統注冊登錄這個短短幾秒的時間里面,到底發生了哪些事情?
通過瀏覽器抓包,對幾個關鍵通訊過程進行分析。
PC瀏覽器會依次發起兩個長連接(比較長時處於 pending 狀態)的請求:
- 等待手機端的微信掃碼(上圖標記2)
- 等待手機微信點擊 “確認登錄” 按鈕(上圖標記3)
這兩個狀態都會反饋到PC端的二維碼頁面,在手機端完成確認后,PC瀏覽器上面的頁面就會生定向到授權后的頁面(如 知乎 首頁)。
具體各方通訊時序圖如下:
上圖對整個過程中通訊涉及的對象進行了清楚的描述,關於上圖數字標注部分注解如下:
- 網站服務器向微信API傳入帶有 回調url 的參數
- 手機微信通過攝像頭掃二維碼,從 光學原理 上完成數據的傳遞
- PC瀏覽器上查詢掃碼狀態的長連接收到返回的狀態值,並更新提示
- PC瀏覽器上查詢手機客戶端點擊確認按鈕的狀態值,並更新提示,然后重定向到 過程1 中傳遞url地址上
- 網站服務器在授權成功后,完成本系統的用戶注冊或者登錄的業務邏輯
- 網站服務器重定向到用戶登錄成功的界面中(如果對於新注冊用戶不需要額外的審核的話)
關於微信掃碼認證部分的開發,本文不再贅述,只給出如下注意事項:
- 微信平台的各種API接口請參考:微信開放平台提供的官方文檔
- 微信掃碼登錄的開發權限需要在微信開放平台中進行企業資質認證(個人用戶無法獲得)
- 回調url 的域必需在微信開放平台中進行填寫備案,本地開發時傳遞的 回調url 參數必須和備案一致
5 代碼實現
根據如上原理,最后將提供具體實現代碼以供參考 ,為了簡潔,有一些通用的工具函數的具體實現就不貼出來了。
使用 python3.5 實現 微信掃碼登錄Web應用程序 的參考代碼如下所示。
對應 上圖標識1 中的代碼實現:
class WeChatAuth(MyBaseHandler): """ 點擊后直接重定向到微信登錄界面 - wechat QR掃碼登錄,web端 - 直接重定向到微信的頁面 """ def get(self): state = get_uuid1_key() # 生成唯一的碼 wx_qr_param = dict( appid=wx_webapp.appid, # redirect_uri=wx_webapp.qr_auth_cb_url, redirect_uri='http://your.domain.com/wechat/wechat-auth-callback/', response_type='code', scope=wx_webapp.login_scope, state=state ) ##wechat_redirect wx_qr_url = 'https://open.weixin.qq.com/connect/qrconnect?%s#wechat_redirect' \ % urllib.parse.urlencode(wx_qr_param) self.redirect(wx_qr_url)
對應 上圖標識5 中的代碼實現:
class WeChatAuthCallback(MyBaseHandler): """ 微信第三方認證之后,開始將此用戶在本系統沉淀下來 - 用於微信服務器傳回code的值 - 此處要再請求獲得access_token """ async def get(self): wx_code = self.get_argument('code', '') wx_state = self.get_argument('state', '') if wx_code == '': res = ConstData.msg_forbidden dlog.debug(res) self.write(res) return dlog.debug('wx_code:%s,wx_state:%s' % (wx_code, wx_state)) access_token_res = wx_webapp.get_auth_access_token(code=wx_code, state=wx_state) user_info = wx_webapp.get_auth_user_info(auth_access_token_res=access_token_res) """:type:WeChatUser""" # 微信返回的用戶信息串 if user_info is None: res = ConstData.msg_forbidden dlog.debug(res) self.write(res) return wechat_user = await MisWeChatUser.objects.get(openid=user_info.openid, unionid=user_info.unionid) """:type:MisWeChatUser""" # 一個Open_id下面所有的id都是靠union來區分賬號 if wechat_user is not None: user = await User.objects.get(user_id=wechat_user.user_id) assert user is not None if user.active: if await user.is_online(): await self.update_session() # 更新時間 else: await self.create_session(user) # 新增加一個session self.write('in authorized page') # self.redirect('/') # todo 重定向到登錄授權后的主頁 return # 如果不存在wechat備案信息,則需要備案wechat信息,而且新注冊初始賬號 default_new_user_id = 'u_' + get_uuid1_key() new_wechat_user = MisWeChatUser( openid=user_info.openid, nickname=user_info.nickname, unionid=user_info.unionid, # user_id=wx_webapp.appid + '_' + user_info.unionid, # 通過微信號登錄生成的一個唯一的用戶名,后面可以提供修改 user_id=default_new_user_id, appid=wx_webapp.appid ) new_wechat_user.set_default_rc_tag() # rand_salt = get_rand_salt() new_user = User( user_id=default_new_user_id, # salt=rand_salt, # 防止別人md5撞庫反向破解的隨機數 # passwd=StringField() # 密碼,通過第三方登錄的默認不設置 first_name=user_info.nickname, status=FieldDict.user_status_init, # 表示是可更改狀態 active=False, ) new_user.set_default_rc_tag() await new_wechat_user.save() await new_user.save() self.write('in unauthorized page') # self.redirect(URL_ROOT) # todo 導入到未授權的頁面
6 功能測試
設計兩組測試用例。
檢查微信用戶掃碼后能否完成上述流程:
- 用A微信賬號掃碼登錄,查看是否自動注冊
- 是否提示重定向到 “未授權頁面”
在數據庫中修改A微信自動注冊的用戶狀態為審核通過后再掃碼登錄:
- 修改A用戶狀態為 active=True
- 是否提示重定向到 “授權頁面”
- 是否在數據庫中看到登錄的session狀態
測試截圖如下:
7 小結
如果我是一個產品經理,如果我做一個web應用的產品,那么在產品早期階段,我肯定會選擇微信登錄的方式,因為這種方式的登錄門檻實在是太低了,用戶試用產品的門檻也降到了最低,后續的活躍程度至少不會受到登錄的門檻的影響。
可惜,還有好多產品經理不懂這個,這么重要的入口都沒有稍微花點心思去打磨。
作者: | Harmo哈莫 |
---|---|
作者介紹: | https://zhengwh.github.io |
技術博客: | http://www.cnblogs.com/beer |
Email: | dreamzsm@gmail.com |
QQ: | 1295351490 |
時間: | 2016-02 |
版權聲明: | 歡迎以學習交流為目的讀者隨意轉載,但是請 【注明出處】 |
支持本文: | 如果文章對您有啟發,可以點擊博客右下角的按鈕進行 【推薦】 |