我需要學下OAuth2.0嗎?
沒看之前以為OAuth2.0是登入認證授權的東西,自己的項目裡應該是需要的。實際上OAuth是為了第三方應用通路我們資源用的,大多數開發者基本不會用到這個東西。對于自己應用的認證授權,還是基于攔截器的token,SpringSecurity即可。隻有做平台級别,像微信,微網誌,github這種級别才會用到。而真到那個時候,再看也行,也通常不會是你來做。簡而言之,非必須。
接下來,我将從幾個方面了解和學習使用OAuth2.0。對不對就不管了,反正我也幾乎不會用到。ps.有個項目用到了,是以才會有本文。
- OAuth2.0介紹和功能
- 微信開放平台和github的OAuth2.0接入應用
- 自己寫一個OAuth2.0服務
- Springboot OAuth2.0內建
快速了解OAuth2.0
資源很多,看起來比較麻煩,可以直接看Authorization Code授權碼流程,以微信登入為例子的介紹。
OAuth2.0是什麼
官方介紹是: OAuth 2.0授權架構允許第三方應用程式通過協調資源所有者和HTTP服務之間的審批互動,或允許第三方應用程式自己獲得通路權限,進而獲得對HTTP服務的有限通路。也就是授權别人(client)通路我們的資源。
The OAuth 2.0 authorization framework enables a third-party
application to obtain limited access to an HTTP service, either on
behalf of a resource owner by orchestrating an approval interaction
between the resource owner and the HTTP service, or by allowing the
third-party application to obtain access on its own behalf. This
specification replaces and obsoletes the OAuth 1.0 protocol described
in RFC 5849.
别人(client)是什麼?
登入石墨文檔可以輸入賬号密碼登入,也可以選擇微信登入,微信掃碼确認後,就登入了石墨文檔。石墨文檔通過微信認證的方式實作了自身的認證登入。這個石墨文檔就是client的角色(Role)。
什麼時候需要讓别人(client)通路我們的資源?
最常見的是微信授權登入,對client來說,是使用者授權client拿到使用者在微信上的資訊,比如性别,唯一id。
我們怎麼才能做到授權給别人通路我們的資源?
我們自己怎麼擷取自己的資源?我們登入後就可以擷取到自己的賬号資訊等資源了。那麼怎麼給到别人?直接把我們的賬号密碼給第三者太不安全了。一個是自己的賬号密碼存在洩露的風險,一個是自己的賬号權限太大,萬一被别人删除了或者看到了不該看到的東西怎麼辦。在使用阿裡雲的時候,我們可以給自己的賬号開子賬号,給子賬号一些權限通路哪些服務(授權太複雜了),這樣我們就可以達到最初的目的了: 讓别人安全的通路我們的資源。
OAuth通過引入授權層并将client的角色與資源所有者的角色分離, 并且給client單獨的憑證(access_token),client通過access_token擷取有限的資源。
OAuth2.0定義的角色
resource owner
資源的擁有者,通常就是使用者,比如登入的使用者。
resource server
資源服務,提供資源的服務。需要access_token才允許被調用。比如微信api,通過access_token調用它可以擷取到使用者的性别等資訊。
client
用戶端,第三方用戶端,被授權通路的應用。比如石墨文檔通過微信登入的時候,石墨文檔就是client角色,它要使用者授權擷取使用者微信的資訊。
authorization server
資源認證授權服務。使用者登入到本服務後,可以選擇授權給第三方。比如微信登入,微信認證服務就是authorization server的角色。可以頒發給client code,可以通過code換取access token. 可以通過access_token認證使用者。
乍一看可能有點迷亂。站在登入使用者的角度,簡單的分為3個陣營: 使用者本身(resource owner), 使用者要登入的應用(client), 使用者的資源(resource server and authrization server). authorization server和resource server是一體的,一家的,比如都是微信的。這樣清晰一些。隻是對于微信内部,個人資訊,朋友圈,公衆号,小程式啥的,資源挺多,相當于多個resource server, 内部就分成了authorization server和多個resource server.
搞清楚這裡面的角色後,再來看協定的流程。
OAuth2.0的協定流程
整體抽象協定流程如下。
- A: client找使用者(resource owner)請求授權,說你得讓我擷取你的微信昵稱. 然後使用者就看到微信授權頁面(authorization server)顯示是否允許client擷取昵稱。
- B: 使用者點确認,微信就會給client一個臨時授權code。
- C: client拿着上一步得到的授權code去找authorization server換一個access_token.
- D: authorization server認證了client的參數,确認後,給client傳回access_token.
- E: client拿access_token去擷取資訊,比如擷取使用者昵稱,頭像。
- F: client如願以償。
直接看上面的圖,看到B和C都特麼叫Authorization Grant, 授權發放。一會兒使用者給client發放,一會兒client又找authorization發放,這是要哪樣。我當年就是看了無數次這個圖沒看懂。接着,一般的文章又會介紹OAuth2.0的4種授權方式,結合上圖的授權,就會非常容易混淆,看不懂了。
官網描述授權發放:
An authorization grant is a credential representing the resource
owner's authorization (to access its protected resources) used by the
client to obtain an access token. This specification defines four
grant types -- authorization code, implicit, resource owner password
credentials, and client credentials -- as well as an extensibility
mechanism for defining additional types.
授權發放模式是指client擷取access_token的方式。官方給了4種,
- authorization code: 授權碼
- implicit: 隐藏式
- resource owner password: 使用者的賬号密碼方式
- client: client的id認證方式
- 當然,也可以自由擴充,能認證傳回access_token就行。
grant是指授權給client,4中授權模式對應的是上圖的BCD,即擷取access_token的過程有4種方式。
access token存儲方式?
access token就是通路資源的憑證,令牌。它的安全很重要。為了防止洩露被竊取,通常是client後端服務用token擷取資源,是存儲在client背景服務的,比如redis,mysql中,和使用者關聯存儲。在浏覽器上是展現不出的。那中間人就不能網絡攔截到token。即access_token不應該在浏覽器通路網絡中出現。
以下用石墨文檔(client)采用微信(authorization server & resource server)登入的方案來描述了解4種授權方式。
Authorization Code授權碼
authorization code就不翻譯了,代碼裡也是這麼寫的,翻譯了就可能對不上了。這種方式擷取token,首先使用者讓微信給一個code給client。client拿着code找微信換一個access_token,以後就用access_token擷取使用者的唯一id。
這個圖對象比較多。先認識對象,微信使用者(resource owner),浏覽器,石墨文檔(client),微信開放平台(authorization server),微信api(resource server)。
首先,石墨文檔(client)需要在微信開放平台(authorization)注冊,擷取appId和appSecret. 這組憑證是石墨自己通路微信開放平台的。access_token則是代表使用者本人,注意差別。
1.微信使用者通過浏覽器通路了石墨文檔,然後登陸頁,然後點了微信登入按鈕
這時候,浏覽器請求石墨微信登入回調位址,石墨背景傳回302,response附屬location. 浏覽器重定向跳轉到微信二維碼認證頁面。
https://open.weixin.qq.com/connect/qrconnect?appid=wx5a67899f4af8b0b1&redirect_uri=https%3A%2F%2Fshimo.im%2Flizard-api%2Fauth%2Fwechat%2Flogin_callback&response_type=code&scope=snsapi_login&state=740a608f-23b4-4abd-b941-e13d2b5c0dbe#wechat_redirect
注意幾個參數:
- appid: 石墨文檔作為client在微信開發平台的憑證id
- redirect_uri: 認證通過後,微信開放平台添加一個code參數到這個url後面,然後浏覽器重定向到這個url。這個url是石墨文檔背景接收code的接口。
- response_type: code。固定
- scope: 授權範圍,想擷取使用者哪些資料的api。網頁登入為snsapi_login
- state: 這個是防刷的,防止csrf跨站請求僞造攻擊。微信認證成功後,回調的時候會原樣傳回給石墨(client)。如果沒這個,client接收參數就隻有code,别人就是随意僞造碰撞code。而石墨生成了一次性token作為state,回調接口隻有一次有效期。這裡石墨用的uuid.
2.使用者微信掃描二維碼,通過認證
使用者微信點選了确認,就代表了授權通過了,允許石墨擷取access_token. 浏覽器微信二維碼頁面收到code,拼接redirect的url,浏覽器重定向到石墨背景
https://shimo.im/lizard-api/auth/wechat/login_callback?code=081r8QFa1aYaRA03J7Ha1gUXl42r8QFk&state=740a608f-23b4-4abd-b941-e13d2b5c0dbe
這個回調位址code就是微信開放平台(authorization server)頒發給石墨文檔(client)的code。
石墨文檔背景接收到code和state後,校驗state是否有效,然後拿code和appId,appSecret去通路微信接口擷取使用者資訊。這一步是在石墨文檔背景進行的。浏覽器看不到access_token. 而對于上一步生成的code,即便有人拿到code,應該已經失效了。code和state都是一次性的有效期。這樣保證了access_token的安全性。
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
傳回:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
3.石墨文檔擷取到使用者唯一id後,根據背景使用者賬号的綁定關系,确認目前登入使用者登入
Implicit
Implicit翻譯是隐藏式,這種針對純web前端應用,沒有背景,就沒辦法像上面一樣了,令牌隻能放在前端。這種也叫client side,又稱為User Agent Flow,先說具體用法。
client直接請求authorization server擷取access_token. 請求參數是client id和redirect url, 最後認證傳回後拼接#access_code. 類似下圖。
由于access_token網絡傳輸,并不安全,很少使用這種方案。qq互聯提供了一種,見 https://wiki.connect.qq.com/%e4%bd%bf%e7%94%a8implicit_grant%e6%96%b9%e5%bc%8f%e8%8e%b7%e5%8f%96access_token
1.請求認證的url參數: clientId, redirectUrI, Scope,state
- client_id: 注冊應用的 id,比如石墨文檔在qq注冊應用的appid
- redirect_uri: 認證成功後要回調的位址,這個要和注冊的時候一緻
- response_type: token
- state: 同樣的一次性随機數,會原樣傳回給client,防止csrf攻擊。
2.認證成功後,回調位址中的access_token
qq認證成功後的示例:
http://graph.qq.com/demo/index.jsp?#access_token=FE04************************CCE2&expires_in=7776000&state=test
可以看到參數access_token是通過#傳遞的。這裡涉及一個叫中間人攻擊的安全問題,見http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html。
注意,令牌access_token的位置是 URL 錨點(fragment),而不是查詢字元串(querystring),這是因為 OAuth 2.0 允許跳轉網址是 HTTP 協定,是以存在"中間人攻擊"的風險,而浏覽器跳轉時,錨點不會發到伺服器,就減少了洩漏令牌的風險
qq提示
可通過js方法:window.location.hash來擷取URL中#後的參數值。
password
password需要使用者把微信賬号和密碼給石墨文檔,石墨拿着去微信換token,這種放棄看了,不可能給的,微信也不支援。
client
這種就是純背景方案。client拿着client_id和secret去換access_token。通常就是我們背景服務調用的時候用到。比如使用aws的s3或者阿裡雲的oss上傳檔案。我們需要通過ak,sk認證,擷取一個token,拿token去上傳檔案。這個流程寫起來還挺麻煩的,一般都會封裝好用戶端給我們用。這裡就不涉及使用者了,隻是用戶端自己的認證了。
customize
自定義,我們也可以username + password登入走OAuth2.0校驗。隻是沒client,或者說每個登入使用者類似client。登入後傳回access_token, 使用者可以通路其他資源。
更新令牌
前面講的4個授權方式,都是為了擷取access_token。傳回格式類似微信的:
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
openid,unionid并不是必須的,這是微信認證使用者的唯一的id。在微信的設計中,
- access_token: 接口調用憑證,用來擷取資源資訊,比如使用者的頭像,昵稱等。逾時時間很短。微信設定為2h。
- expire_in: access_token的逾時時間,機關秒
- refresh_token: 使用者重新整理access_token用的token。逾時時間比較久。微信設定逾時為30天,失效後需要使用者重新授權。
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
傳回
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
請求重新整理token:
- appid: client id,石墨注冊在微信平台的id
- grant_type: refresh_token固定
- refresh_token: 上一步擷取到的token