OAuth2.0理解和用法

現在網路的資料到處都是,很容易搜索到自己想要的答案。但答案通常只能解決自己一部分的問題。如果自己想要有一套自己的解決方案,還得重新擼一遍靠譜。

我需要學下OAuth2.0嗎?

沒看之前以為OAuth2.0是登錄認證授權的東西,自己的項目里應該是需要的。實際上OAuth是為了第三方應用訪問我們資源用的,大多數開發者基本不會用到這個東西。對於自己應用的認證授權,還是基於攔截器的token,SpringSecurity即可。只有做平台級別,像微信,微博,github這種級別才會用到。而真到那個時候,再看也行,也通常不會是你來做。簡而言之,非必須。

接下來,我將從幾個方面了解和學習使用OAuth2.0。對不對就不管了,反正我也幾乎不會用到。ps.有個項目用到了,所以才會有本文。

  1. OAuth2.0介紹和功能
  2. 微信開放平台和github的OAuth2.0接入應用
  3. 自己寫一個OAuth2.0服務
  4. 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. 瀏覽器重定向跳轉到微信二維碼認證頁面。

//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

注意幾個參數:

  1. appid: 石墨文檔作為client在微信開發平台的憑證id
  2. redirect_uri: 認證通過後,微信開放平台添加一個code參數到這個url後面,然後瀏覽器重定向到這個url。這個url是石墨文檔後台接收code的介面。
  3. response_type: code。固定
  4. scope: 授權範圍,想獲取用戶哪些資料的api。網頁登錄為snsapi_login
  5. state: 這個是防刷的,防止csrf跨站請求偽造攻擊。微信認證成功後,回調的時候會原樣返回給石墨(client)。如果沒這個,client接收參數就只有code,別人就是隨意偽造碰撞code。而石墨生成了一次性token作為state,回調介面只有一次有效期。這裡石墨用的uuid.

2.用戶微信掃描二維碼,通過認證
用戶微信點擊了確認,就代表了授權通過了,允許石墨獲取access_token. 瀏覽器微信二維碼頁面收到code,拼接redirect的url,瀏覽器重定向到石墨後台

//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的安全性。

//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互聯提供了一種,見 //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認證成功後的示例:

//graph.qq.com/demo/index.jsp?#access_token=FE04************************CCE2&expires_in=7776000&state=test

可以看到參數access_token是通過#傳遞的。這裡涉及一個叫中間人攻擊的安全問題,見//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天,失效後需要用戶重新授權。
//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

參考

Tags: