1. 引言
週末逛簡書,看了一篇寫的極好的文章,點選大紅心點贊,就直接給我跳轉到登入介面了,原來點贊是需要登入的。
可是沒有我並沒有簡書賬號,一直使用的QQ的整合登入。下面有一排社交登入按鈕,我們可以用第三方社交賬號登陸即可。點選QQ圖示,就給我跳轉到了QQ登入授權頁面,如下圖:
從圖片上我們可以看到主要包括兩個部分,一個是左邊的使用者登入,一個是右邊告知簡書將獲取哪些許可權。輸入QQ賬號和密碼,點選授權並登入,就成功登入到簡書了,併成功獲取到了我QQ的賬號和暱稱,如下圖:
簡書整合的社交登入,大大簡化了我們的註冊登入流程,真是一號在手上網無憂啊。
這看似簡單的整合,但背後的技術原理『OAuth2.0』可沒那麼簡單,那我們廢話不多說,一探究竟吧。
2. OAuth 2.0
OAuth 2.0是用於授權的行業標準協議。OAuth 2.0取代了在2006年建立的原始OAuth協議上所做的工作。OAuth 2.0專注於客戶端開發人員的簡單性,同時為Web應用程式,桌面應用程式,手機和客廳裝置提供特定的授權流程。
在傳統的client-server認證模型中,客戶端請求訪問伺服器上受限的資源(protected resource),需要通過使用資源所有者(resource owner)的憑證在伺服器上進行認證。為了支援第三方應用程式訪問受限資源,資源所有者需要向第三方應用共享其憑證。這就會造成以下問題:
- 第三方應用為了後續使用,會儲存資源所有者的憑證主要是密碼。
- 服務端需要支援密碼認證,儘管密碼認證不安全。
- 第三方應用獲得對資源的過度訪問而不僅侷限於受限資源,且資源所有者沒有辦法對其進行限制。
- 資源所有者無法收回許可權,除非修改密碼。
- 如果第三方應用的密碼被破解,就會導致所有被該密碼保護的資料被洩露。
想一想這樣一個場景,如果簡書是直接使用QQ使用者名稱密碼登入,簡書就很有可能會為了後續業務的需要而擅自儲存QQ使用者名稱及密碼,簡書只要拿到了QQ使用者名稱密碼就可以訪問不僅僅QQ暱稱、頭像等資訊,甚至可以獲取到QQ使用者的所有通訊錄列表。如果簡書的賬號密碼洩露,就會直接影響到QQ資料的安全。這是一個可怕的問題。
所以OAuth應運而生,來解決這一問題。
3. OAuth 2.0授權流程
下面我們就以簡書使用QQ授權登入為例,來捋一捋OAuth 2.0的流程。
先來看看OAuth 2.0的流程,如下圖所示:
這裡面主要包含四個角色:
- Client:需要授權的客戶端,本文中就是【簡書】。
- Resource Owner:資源所有者,在本文中你可能會以為是 QQ,但要想清楚,QQ是屬於個人的,所以在本文中資源所有者是指【QQ使用者】。
- Authorization Server:認證伺服器,本文中特指【QQ互聯平臺】。
- Resource Server:資源伺服器,顧名思義,用來專門儲存資源的伺服器,接受通過訪問令牌進行訪問。本文特指【QQ使用者資訊中心】。
3.1. 第一步:引導使用者到認證伺服器
聖傑開啟簡書網頁,簡書跳轉到登入介面,要求使用者登入。可是聖傑未在簡書註冊帳號,所以就點選了QQ圖示,使用QQ帳號進行整合登入。跳轉到QQ登入介面後,QQ要求使用者授權。
這一步中簡書主要做了這樣一件事就是引導使用者到認證伺服器。
很顯然【QQ互聯平臺】就是認證伺服器。
如何引導?當然是頁面跳轉。
那認證伺服器如何知道是簡書過來的認證請求?
當然是傳參。
那需要傳遞哪些引數呢?
- response_type:表示響應型別,必選項,此處的值固定為"code";
- client_id:表示客戶端的ID,用來標誌授權請求的來源,必選項;
- redirect_uri:成功授權後的回撥地址;
- scope:表示申請的許可權範圍,可選項;
- state:表示客戶端的當前狀態,可以指定任意值,認證伺服器會原封不動地返回這個值。
我們們看看簡書實際傳送的授權請求Url是:https://graph.qq.com/oauth2.0/authorize?client_id=100410602 &redirect_uri=http://www.jianshu.com/users/auth/qq_connect/callback &response_type=code &state=bb38108d1aaf567c72da0f1167e87142d0e20cb2bb24ec5a
無圖無真相,我們們看看控制檯的網路監控:
如圖所示,除了scope引數外,其他四個引數均有傳參。
此時你可能唯一對state引數比較迷惑,傳遞一個state引數,認證伺服器會原封不動返回,那還幹嘛要傳遞state引數呢?
我的理解是,簡書用一個guid加長版字串來唯一標識一個授權請求。這樣才會正確獲取授權伺服器返回的授權碼。
這裡你可能會問了,既然我知道了這些引數,我豈不是可以偽造簡書認證請求,修改redirect_uri
引數跳轉到個人的網站,然後不就可以獲取QQ授權?
跟我一樣太傻太天真,簡書在QQ互聯平臺申請時肯定已經預留備案了要跳轉返回的URL。QQ互聯平臺在收到簡書的授權請求時肯定會驗證回撥Url的。
3.2. 第二步:使用者同意為第三方客戶端授權
這一步,對於使用者來說,只需要使用資源所有者(QQ)的使用者名稱密碼登入,並同意授權即可。點選授權並登入後,授權伺服器首先會post一個請求回伺服器進行使用者認證,認證通過後授權伺服器會生成一個授權碼,然後伺服器根據授權請求的redirect_uri
進行跳轉,並返回授權碼code
和授權請求中傳遞的state
。
這裡要注意的是:授權碼有一個短暫的時效
無圖無真相,我們們還是看一下控制檯網路監控:
從圖中即可驗證我們上面所說,最終跳轉回簡書的Url為:http://www.jianshu.com/users/auth/qq_connect/callback?code=093B9307E38DC5A2C3AD147B150F2AB3 &state=bb38108d1aaf567c72da0f1167e87142d0e20cb2bb24ec5a
和之前的授權請求URL進行對比,可以發現redirect_uri
、state
完全一致。
而code=093B9307E38DC5A2C3AD147B150F2AB3
就是返回的授權碼。
3.3. 第三步:使用授權碼向認證伺服器申請令牌
從這一步開始,對於使用者來說是察覺不到的。簡書後臺默默的在做後續的工作。
簡書拿到QQ互聯平臺返回的授權碼後,需要根據授權碼再次向認證伺服器申請令牌(access token)。
到這裡有必要再理清兩個概念:
- 授權碼(Authorization Code):相當於授權伺服器口頭告訴簡書,使用者同意授權使用他的QQ登入簡書了。
- 令牌(Access Token):相當於臨時身份證。
那如何申請令牌呢?
簡書需要後臺傳送一個get請求到認證伺服器(QQ互聯平臺)。
那要攜帶哪些必要資訊呢?
是的,要攜帶以下引數:
- grant_type:表示授權型別,此處的值固定為"authorization_code",必選項;
- client_id:表示從QQ互聯平臺申請到的客戶端ID,用來標誌請求的來源,必選項;
- client_secret:這個是從QQ互聯平臺申請到的客戶端認證金鑰,機密資訊十分重要,必選項;
- redirect_uri:成功申請到令牌後的回撥地址;
- code:上一步申請到的授權碼。
根據以上資訊我們可以模擬一個申請AccessToken的請求:https://graph.qq.com/oauth2.0/token?client_id=100410602 &client_secret=123456jianshu &redirect_uri=http://www.jianshu.com/users/auth/qq_connect/callback &grant_type=authorization_code &code=093B9307E38DC5A2C3AD147B150F2AB3
傳送完該請求後,認證伺服器驗證通過後就會發放令牌,並返回到簡書後臺,其中應該包含以下資訊:
- access_token:令牌
- expires_in:access token的有效期,單位為秒。
- refresh_token:在授權自動續期步驟中,獲取新的Access_Token時需要提供的引數。
同樣,我們可以模擬出一個返回的token:http://www.jianshu.com/users/auth/qq_connect/callback?access_token=548ADF2D5E1C5E88H4JH15FKUN51F &expires_in=36000&refresh_token=53AD68JH834HHJF9J349FJADF3
這個時候簡書還有一件事情要做,就是把使用者token寫到cookie裡,進行使用者登入狀態的維持。我們們還是開啟控制器驗證一下。
從圖中可以看出簡書把使用者token儲存在名為remember_user_token
的cookie裡。
不用打cookie的歪主意了,肯定是加密了的。
可以嘗試下手動把remember_user_token
這條cookie刪除,保證重新整理介面後需要你重新登入簡書。
3.4. 第四步:向資源伺服器申請資源
有了token,向資源伺服器提供的資源介面傳送一個get請求不就行了,資源伺服器校驗令牌無誤,就會向簡書返回資源(QQ使用者資訊)。
同樣我們們也來模擬一個使用token請求QQ使用者基本資訊資源的URL:https://graph.qq.com/user/get_user_info?client_id=100410602 &qq=2098769873 &access_token=548ADF2D5E1C5E88H4JH15FKUN51F
到這一步OAuth2.0的流程可以說是結束了,但是對於簡書來說還有重要的事情要做。那就是:
拿到token、reresh_token和使用者資料這麼重要的東西不存資料庫傻呀?
3.5. 第五步:令牌延期(重新整理)
你肯定對第四步返回的refresh_token
比較好奇。
它是用來對令牌進行延期(重新整理)的。為什麼會有兩種說法呢,因為可能認證伺服器會重新生成一個令牌,也有可能
對過期的令牌進行延期。
比如說,QQ互聯平臺為了安全性考慮,返回的access_token
是有時間限制的,假如使用者某天不想授權了呢,總不能給了個access_token
你幾年後還能用吧。我們上面模擬返回的令牌有效期為10小時。10小時後,使用者開啟瀏覽器逛簡書,瀏覽器中使用者的token對應的cookie已過期。簡書發現瀏覽器沒有攜帶token資訊過來,就明白token失效了,需要重新向認證平臺申請授權。如果讓使用者再點選QQ進行登入授權,這明顯使用者體驗不好。咋搞呢?refresh_token
就派上了用場,可以直接跳過前面申請授權碼的步驟,當發現token失效了,簡書從瀏覽器攜帶的cookie中的sessionid找到儲存在資料庫中的refresh_token
,然後再使用refresh_token
進行token續期(重新整理)。
那用refresh_token進行token續期需要怎麼做呢?
同樣需要向認證伺服器傳送一個get請求。
需要哪些引數?
- grant_type:表示授權型別,此處的值固定為"refresh_token",必選項;
- client_id:表示從QQ互聯平臺申請到的客戶端ID,用來標誌請求的來源,必選項;
- client_secret:這個是從QQ互聯平臺申請到的客戶端認證金鑰,機密資訊十分重要,必選項;
- refresh_token:即申請令牌返回的refresh_token。
根據上述資訊,我們又可以模擬一個令牌重新整理的URL:https://graph.qq.com/oauth2.0/token?client_id=100410602 &client_secret=123456jianshu &redirect_uri=http://www.jianshu.com/users/auth/qq_connect/callback &grant_type=refresh_token &refresh_token=53AD68JH834HHJF9J349FJADF3
那返回的結果呢?
和第四步返回的結果一樣。
這裡你可能又有疑問了,那既然每次進行令牌延期後都會重新返回一個refresh_token
,那豈不是我可以使用refresh_token
無限延期?
天真如我啊,refresh_token
也是有過期時間的。而這個過期時間具體是由認證伺服器決定的。
一般來說refresh_token
的過期時間要大於access_token
的過期時間。只有這樣,access_token
過期時,才可以使用refresh_token
進行令牌延期(重新整理)。
舉個簡單例子:
假設簡書從QQ互聯平臺預設獲取到的access_token
的有效期是1天,refresh_token
的有效期為一週。
使用者今天使用QQ登入授權後,過了兩天再去逛簡書,簡書發現token失效,立馬用refresh_token
重新整理令牌,默默的完成了授權的延期。
假如使用者隔了兩週再去逛簡書,簡書一核對,access_token
、refresh_token
全都失效,就只能乖乖引導使用者到授權頁面重新授權,也就是回到OAuth2.0的第一步。
4.0 總結
本文以簡書通過QQ進行授權登入為例,對OAuth2.0 的授權流程進行了梳理,希望通讀此文,對你有所幫助。
如果對OAuth2.0有所瞭解的話,你應該明白本文其實是對OAuth2.0中授權碼模式授權方式的講解。
如果想了解OAuth2.0其他幾種授權方式,建議參考理解OAuth 2.0 - 阮一峰的網路日誌。