Web應用多賬號系統設計及微信掃碼登入實現

weixin_34120274發表於2016-05-28

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. 開啟 知乎 主頁,點選 “微信登入” 的圖示
  2. 瀏覽器重定向到微信域(見下圖示記1)下面的二維碼頁面
  3. 使用者掏出手機開啟微信,掃一掃
  4. 在手機微信上點選授權
  5. PC上面的二維碼頁面顯示授權成功,並轉向到 知乎 首頁,認證成功

整個過程對於終端使用者來說,只有短短几秒,而且不用輸入任何密碼,可以說是一種非常安全又便捷的體驗。

那麼問題來了,通過微信掃描二維碼,並完成MIS系統註冊登入這個短短几秒的時間裡面,到底發生了哪些事情?

通過瀏覽器抓包,對幾個關鍵通訊過程進行分析。

PC瀏覽器會依次發起兩個長連線(比較長時處於 pending 狀態)的請求:

  • 等待手機端的微信掃碼(上圖示記2)
  • 等待手機微信點選 “確認登入” 按鈕(上圖示記3)

這兩個狀態都會反饋到PC端的二維碼頁面,在手機端完成確認後,PC瀏覽器上面的頁面就會生定向到授權後的頁面(如 知乎 首頁)。

具體各方通訊時序圖如下:

上圖對整個過程中通訊涉及的物件進行了清楚的描述,關於上圖數字標註部分註解如下:

  1. 網站伺服器向微信API傳入帶有 回撥url 的引數
  2. 手機微信通過攝像頭掃二維碼,從 光學原理 上完成資料的傳遞
  3. PC瀏覽器上查詢掃碼狀態的長連線收到返回的狀態值,並更新提示
  4. PC瀏覽器上查詢手機客戶端點選確認按鈕的狀態值,並更新提示,然後重定向到 過程1 中傳遞url地址上
  5. 網站伺服器在授權成功後,完成本系統的使用者註冊或者登入的業務邏輯
  6. 網站伺服器重定向到使用者登入成功的介面中(如果對於新註冊使用者不需要額外的稽核的話)

關於微信掃碼認證部分的開發,本文不再贅述,只給出如下注意事項:

  • 微信平臺的各種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   功能測試

設計兩組測試用例。

檢查微信使用者掃碼後能否完成上述流程:

  1. 用A微信賬號掃碼登入,檢視是否自動註冊
  2. 是否提示重定向到 “未授權頁面”

在資料庫中修改A微信自動註冊的使用者狀態為稽核通過後再掃碼登入:

  1. 修改A使用者狀態為 active=True
  2. 是否提示重定向到 “授權頁面”
  3. 是否在資料庫中看到登入的session狀態

測試截圖如下:

7   小結

如果我是一個產品經理,如果我做一個web應用的產品,那麼在產品早期階段,我肯定會選擇微信登入的方式,因為這種方式的登入門檻實在是太低了,使用者試用產品的門檻也降到了最低,後續的活躍程度至少不會受到登入的門檻的影響。

可惜,還有好多產品經理不懂這個,這麼重要的入口都沒有稍微花點心思去打磨。


作者: Harmo哈莫
作者介紹: https://zhengwh.github.io
技術部落格: http://www.cnblogs.com/beer
Email: dreamzsm@gmail.com
QQ: 1295351490
時間: 2016-02
版權宣告: 歡迎以學習交流為目的讀者隨意轉載,但是請 【註明出處】
支援本文: 如果文章對您有啟發,可以點選部落格右下角的按鈕進行 【推薦】

相關文章