現在掃碼登入是一種很常見的登入方式。當使用者需要登入某個網站時,網站會提供一種掃碼登入的方式,使用者開啟相應的手機App,掃描網站上顯示的二維碼,然後在App中確認登入,網站監測到使用者確認登入後,跳轉到登入成功頁面。從這個形式上看,掃碼登入就是將使用者在手機App中的登入狀態同步到網站中,這篇文章就來一窺這個同步是如何發生的。
同一產品中的掃碼登入
假設有一款產品,這個產品通過手機端App和PC端應用為使用者提供服務,為了方便使用者在PC端上登入,產品提供了一個掃碼登入的功能,即PC端應用上展示一個登入二維碼,使用者使用手機端App掃碼並確認登入,然後使用者就可以在PC端上登入成功。在這個例子中,手機端App和PC端應用同屬於一個產品,這是一種常見的產品形態,微信、微博、知乎等等都是這種產品形態的代表。
為了方便介紹,這裡再假設PC端應用是一個Web站點,下面就來看一下這種登入方式的運作原理:
如上圖所示,整個過程比較簡單,這裡大概分為如下幾個步驟:
1、使用者發起二維碼登入:此時網站會先生成一個二維碼,同時把這個二維碼對應的標識儲存起來,以便跟蹤二維碼的掃碼狀態,然後將二維碼頁面返回到瀏覽器中;瀏覽器先展示這個二維碼,再按照Javascript指令碼的指示發起掃碼狀態的輪詢。所謂輪詢就是瀏覽器每隔幾秒呼叫網站的API查詢二維碼的掃碼登入結果,查詢時攜帶二維碼的標識。有的文章說這裡可以使用WebSocket,雖然WebSocket響應比較及時,但是從相容性和複雜度考慮,大部分方案還是會選擇輪詢或者長輪詢,畢竟此時通訊稍微延遲下也沒多大關係。
2、使用者掃碼確認登入:使用者開啟手機App,使用App自帶的掃碼功能,掃描瀏覽器中展現的二維碼,然後App提取出二維碼中的登入資訊,顯示登入確認的頁面,這個頁面可以是App的Native頁面,也可以是遠端H5頁面,這裡採用Native頁面,使用者點選確認或者同意按鈕後,App將二維碼資訊和當前使用者的Token一起提交到網站API,網站API確認使用者Token有效後,更新在步驟1中建立的二維碼標識的狀態為“確認登入”,同時繫結當前使用者。
3、網站驗證登入成功:在步驟1中,二維碼登入頁面啟動了一個掃碼狀態的輪詢,如果使用者已經“確認登入”,則輪詢訪問網站API時,網站會生成二維碼繫結使用者的登入Session,然後向前端返回登入成功訊息。這裡登入狀態維護是採用的Session機制,也可以換成其它的機制,比如JWT。
為了保證登入的安全,有必要採取一些安全措施,可能包括以下若干方法:
-
對二維碼承載的資訊按照某種規則進行處理,App可以在掃碼時進行驗證,避免任何掃碼都去請求登入;
-
對二維碼設定一個過期時間,過期就自動刪除,這樣使其佔用的資源保持在合理範圍之內;
-
限制二維碼只能使用一次,防止重放攻擊;
-
二維碼使用足夠長的隨機性字串,防止被惡意窮舉佔用;
-
使用HTTPS傳輸,保護登入資料不被竊聽和篡改。
第三方應用的掃碼登入
現在很多網站除了提供自身賬號的登入方式以外,還提供了微信掃碼登入、微博掃碼登入等方式,本來這對使用者來說是十分方便的,不過很多站點為了獲取使用者手機號,首次登入時還需要用手機驗證碼再登入一次,使用者會有點被欺騙的感覺,不過這個問題不是本文要探討的。
這裡以微信掃碼登入某網站為例,某網站就是第三方應用,所謂第三方是相對微信自身來說的。相比同一產品中的掃碼登入,網站使用微信掃碼登入會更復雜一些,因為這涉及到不同應用之間的登入安全。下面就給出這一登入方式的詳細運作原理,時序圖比較長,不過只要耐心點就能完全搞清楚,甚至自己實現一個類似的第三方掃碼登入方案。
這裡對一些關鍵點特別說明下:
1、步驟3 生成微信登入請求記錄:當使用者掃碼並同意登入之後,步驟25中瀏覽器會重定向到第三方應用,如果之前沒有建立一條登入請求記錄,網站並不能確定這次登入就是自己發起的,這可能導致跨站請求偽造攻擊。比如使用某個應用的微信登入二維碼,騙取使用者的授權,然後最終回撥跳轉到其它站點,被回撥站點只能被動接受,雖然下一步驗證授權Code通不過,微信也可能會認為第三方應用出了某種問題,搞不好被封掉。因此第三方應用建立一條登入請求記錄之後,還要把記錄的標識拼接到訪問微信登入二維碼的url中,微信會在使用者同意登入後原樣返回這個標識,步驟26中第三方應用可以驗證這個標識是不是有效的。
2、步驟17 顯示應用名稱和請求授權資訊:因為微信支援很多的第三方應用,需要明確告知使用者正在登入哪個應用,應用可以訪問自己的什麼資訊,這都是使用者做出登入決定的必要資訊。因此掃碼之後,微信手機端就需要去微信開放平臺查詢下二維碼對應的第三方應用資訊。
3、步驟24 登入臨時授權Code:微信開放平臺沒有直接向瀏覽器返回登入使用者的資訊,這是因為第三方應用還需要對使用者進行授權並保持會話的狀態,這適合在應用的服務端來處理;而且直接返回使用者資訊到瀏覽器也是不安全的,並不能保證二維碼登入請求就是通過指定的第三方應用發起的。第三方應用會在步驟27中攜帶這個授權Code,加上應用的AppId和AppSecret,再去向微信開放平臺發起登入請求,臨時授權Code只能使用1次,存下來也不能再用,且只能用在指定的應用(即繫結了AppId),AppSecret是應用從服務端提取的,用來驗證應用的身份,這些措施保證了微信授權登入的安全性。不過驗證通過後還是沒有直接返回使用者資訊,而是返回了一個access token,應用可以使用這個token再去請求獲取使用者資訊的介面,這是因為開放平臺提供了很多介面,訪問這些介面都需要有授權才行,所以發放了一個access token給第三方應用,這種授權登入方式叫做OAuth 2.0。基於安全方面的考慮,access token的有效期比較短,開放平臺一般還會發放一個refresh token,access token過期之後,第三方應用可以拿著這個refresh token再去換一個新的access token,如果refresh token也過期了或者使用者取消了授權,則不能獲取到新的access token,第三方應用此時應該登出使用者的登入。這些token都不能洩漏,所以需要儲存在第三方應用的服務端。
這裡有一個有意思的問題:為什麼微信的二維碼登入頁面Url中沒有第三方應用的簽名?
以極客時間的這個微信登入二維碼頁面的Url為例:
其中appid是微信開放平臺分配給極客時間的應用Id;redirect_uri是使用者授權登入後微信回撥的極客時間url,雖然看起來很長,其實就是在極客時間內的頁面之間跳來跳去;state是極客時間生成的一個登入id,不同的state會生成不同的二維碼,微信回撥極客時間的時候會帶著這個state。
這個url中只有極客時間的appid,沒有極客時間的簽名,也就是說微信會生成極客時間的登入二維碼,但是不驗證這個二維碼登入請求是不是極客時間發起的,那麼任何人都可以生成極客時間的微信登入二維碼頁面。這好像不太嚴謹。
這樣安全嗎?攻擊者可以很簡單的獲取某個應用的微信登入二維碼,然後再騙取使用者的登入授權(這個也相對簡單,弄個假冒的網站,或者直接話術忽悠,使用者很可能不仔細看掃碼確認的內容),再通過瀏覽器攔截或者DNS攻擊獲取到臨時授權Code,直接越過檢查應用State,前邊這些都相對容易。但是向微信驗證臨時授權Code的時候必須攜帶App Secret,這個就很難了,除非第三方應用開發者自己洩漏了這一核心機密。對於使用者資訊的保護,整體上來說是安全的,攻擊者基本上拿不到訪問使用者資訊的access token。如果在url中加上這個簽名,對使用者資訊的保護作用也沒有加強,授權Code還是很容易獲取;而且簽名一般也是依賴App Secret,如果洩漏了App Secret,簽名也就失去了意義。
如果有人不斷地生成某個應用的微信登入二維碼怎麼辦?這是其他型別的攻擊了,微信可以採取一些限流措施,甚至直接封掉某些IP,這對正常使用者沒什麼影響。
微信二維碼登入的變種
微信除了跳轉到二維碼登入頁面的方式,還提供了第三方網站中內嵌二維碼的方式。通過微信JS SDK生成二維碼並在網頁的指定區域展現,使用者掃碼同意登入後,微信JS SDK發起重定向或者在iframe中開啟應用回撥頁面,傳遞臨時授權Code和應用State,後續的流程就都一樣了。
另外這裡的第三方應用如果是移動端App,微信開放平臺也支援掃碼登入的方式,區別在於需要整合微信SDK,獲取二維碼和接收使用者授權Code都是通過SDK回撥的方式,後續的流程也都一樣。
以上就是本文的主要內容了,鑑於本人才疏學淺,如有錯漏歡迎指正。
收穫更多架構知識,請關注公眾號 螢火架構。原創內容,轉載請註明出處。