轉:10 分鐘理解什麼是 OAuth 2.0 協議
10 分鐘理解什麼是 OAuth 2.0 協議
預覽目錄
什麼是 OAuth 2.0
OAuth 2.0 是一個行業的標準授權協議。OAuth 2.0 專注於簡化客戶端開發人員,同時為 Web 應用程式,桌面應用程式,手機和客廳裝置提供特定的授權流程。
它的最終目的是為第三方應用頒發一個有時效性的令牌 token。使得第三方應用能夠通過該令牌獲取相關的資源。常見的場景就是:第三方登入。當你想要登入某個論壇,但沒有賬號,而這個論壇接入瞭如 QQ、Facebook 等登入功能,在你使用 QQ 登入的過程中就使用的 OAuth 2.0 協議。
如果你想了解更多,其官方網址為:https://oauth.net/2/
角色
首先需要介紹的是 OAuth 2.0 協議中的一些角色,整個授權協議的流程都將圍繞著這些角色:
resource owner
,資源所有者,能夠允許訪問受保護資源的實體。如果是個人,被稱為 end-user。resource server
,資源伺服器,託管受保護資源的伺服器。client
,客戶端,使用資源所有者的授權代表資源所有者發起對受保護資源的請求的應用程式。如:web網站,移動應用等。authorization server
,授權伺服器,能夠向客戶端頒發令牌。user-agent
,使用者代理,幫助資源所有者與客戶端溝通的工具,一般為 web 瀏覽器,移動 APP 等。
可能有些朋友依然不太理解,這裡舉例說明:假如我想要在 coding.net
這個網站上用 github.com
的賬號登入。那麼 coding 相對於 github 就是一個客戶端。而我們用什麼操作的呢?瀏覽器,這就是一個使用者代理。當從 github 的授權伺服器獲得 token 後,coding 是需要請求 github 賬號資訊的,從哪請求?從 github 的資源伺服器。
協議流程
上圖詳細的描述了這四個角色之間的步驟流程:
- (A) Client 請求 Resource Owner 的授權。授權請求可以直接向 Resource Owner 請求,也可以通過 Authorization Server 間接的進行。
- (B) Client 獲得授權許可。
- © Client 向 Authorization Server 請求訪問令牌。
- (D) Authorization Server 驗證授權許可,如果有效則頒發訪問令牌。
- (E) Client 通過訪問令牌從 Resource Server 請求受保護資源。
- (F) Resource Server 驗證訪問令牌,有效則響應請求。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Figure 1: Abstract Protocol Flow
授權
一個客戶端想要獲得授權,就需要先到服務商那註冊你的應用。一般需要你提供下面這些資訊:
- 應用名稱
- 應用網站
- 重定向 URI 或回撥 URL(redirect_uri)
重定向 URI 是服務商在使用者授權(或拒絕)應用程式之後重定向使用者的地址,因此也是用於處理授權程式碼或訪問令牌的應用程式的一部分。在你註冊成功之後,你會從服務商那獲取到你的應用相關的資訊:
- 客戶端標識 client_id
- 客戶端金鑰 client_secret
client_id
用來表識客戶端(公開),client_secret
用來驗證客戶端身份(保密)。
授權型別
OAuth 2.0 列舉了四種授權型別,分別用於不同的場景:
- Authorization Code(授權碼 code):伺服器與客戶端配合使用。
- Implicit(隱式 token):用於移動應用程式或 Web 應用程式(在使用者裝置上執行的應用程式)。
- Resource Owner Password Credentials(資源所有者密碼憑證 password):資源所有者和客戶端之間具有高度信任時(例如,客戶端是裝置的作業系統的一部分,或者是一個高度特權應用程式),以及當其他授權許可型別(例如授權碼)不可用時被使用。
- Client Credentials(客戶端證書 client_credentials):當客戶端代表自己表演(客戶端也是資源所有者)或者基於與授權伺服器事先商定的授權請求對受保護資源的訪問許可權時,客戶端憑據被用作為授權許可。
下面來具體說說這四種授權。注意重定向一定要用 302。
授權碼模式
該方式需要資源伺服器的參與,應用場景大概是:
- 資源擁有者(使用者)需要登入客戶端(APP),他選擇了第三方登入。
- 客戶端(APP)重定向到第三方授權伺服器。此時客戶端攜帶了客戶端標識(client_id),那麼第三方就知道這是哪個客戶端,資源擁有者確定(拒絕)授權後需要重定向到哪裡。
- 使用者確認授權,客戶端(APP)被重定向到註冊時給定的 URI,並攜帶了第三方給定的 code。
- 在重定向的過程中,客戶端拿到 code 與
client_id
、client_secret
去授權伺服器請求令牌,如果成功,直接請求資源伺服器獲取資源,整個過程,使用者代理是不會拿到令牌 token 的。 - 客戶端(APP)拿到令牌 token 後就可以向第三方的資源伺服器請求資源了。
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
Note: The lines illustrating steps (A), (B), and (C) are broken into
two parts as they pass through the user-agent.
Figure 3: Authorization Code Flow
具體說明,這裡以 coding 和 github 為例。當我想在 coding 上通過 github 賬號登入時:
1、GET 請求
點選登入,重定向到 github 的授權端點:
https://github.com/login/oauth/authorize?
response_type=code&
client_id=a5ce5a6c7e8c39567ca0&
redirect_uri=https://coding.net/api/oauth/github/callback&
scope=user:email
欄位 | 描述 |
---|---|
response_type | 必須,固定為 code,表示這是一個授權碼請求。 |
client_id | 必須,在 github 註冊獲得的客戶端 ID。 |
redirect_uri | 可選,通過客戶端註冊的重定向 URI(一般要求且與註冊時一致)。 |
scope | 可選,請求資源範圍,多個空格隔開。 |
state | 可選(推薦),如果存在,原樣返回給客戶端。 |
返回值:
https://coding.net/api/oauth/github/callback?code=fb6a88dc09e843b33f
欄位 | 描述 |
---|---|
code | 必須。授權碼 |
state | 如果出現在請求中,必須包含。 |
授權錯誤
第一種,客戶端沒有被識別或錯誤的重定向 URI,授權伺服器沒有必要重定向資源擁有者到重定向URI,而是通知資源擁有者發生了錯誤。
第二種,客戶端被正確地授權了,但是其他某些事情失敗了。這種情況下下面地錯誤響應會被髮送到客戶端,包括在重定向 URI 中。
https://coding.net/api/oauth/github/callback?
error=redirect_uri_mismatch&
error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&
error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch
欄位 | 描述 |
---|---|
error | 必須,必須是預先定義的錯誤碼。 |
error_description | 可選,錯誤描述 |
error_uri | 可選,指向可解讀錯誤的 URI |
state | 必須,如果出現在授權請求中 |
2、POST 請求
獲取令牌 token,當獲取到授權碼 code 後,客戶端需要用它獲取訪問令牌:
https://github.com/login/oauth/access_token?
client_id=a5ce5a6c7e8c39567ca0&
client_secret=xxxx&
grant_type=authorization_code&
code=fb6a88dc09e843b33f&
redirect_uri=https://coding.net/api/oauth/github/callback
欄位 | 描述 |
---|---|
client_id | 必須,客戶端標識。 |
client_secret | 必須,客戶端金鑰。 |
grant_type | 必須,固定為 authorization_code/refresh_token。 |
code | 必須,上一步獲取到的授權碼。 |
redirect_uri | 必須,完成授權後的回撥地址,與註冊時一致。 |
返回值:
{
"access_token":"a14afef0f66fcffce3e0fcd2e34f6ff4",
"token_type":"bearer",
"expires_in":3920,
"refresh_token":"5d633d136b6d56a41829b73a424803ec"
}
欄位 | 描述 |
---|---|
access_token | 這個就是最終獲取到的令牌。 |
token_type | 令牌型別,常見有 bearer/mac/token(可自定義)。 |
expires_in | 失效時間。 |
refresh_token | 重新整理令牌,用來重新整理 access_token。 |
3、獲取資源伺服器資源,拿著 access_token 就可以獲取賬號的相關資訊了:
curl -H "Authorization: token a14afef0f66fcffce3e0fcd2e34f6ff4" https://api.github.com/user
4、POST 請求
重新整理令牌
我們的 access_token 是有時效性的,當在獲取 github 使用者資訊時,如果返回 token 過期:
https://github.com/login/oauth/access_token?
client_id=a5ce5a6c7e8c39567ca0&
client_secret=xxxx&
redirect_uri=https://coding.net/api/oauth/github/callback&
grant_type=refresh_token&
refresh_token=5d633d136b6d56a41829b73a424803ec
欄位 | 描述 |
---|---|
client_id | 必須 |
client_secret | 必須 |
redirect_uri | 必須 |
grant_type | 必須,固定為 refresh_token |
refresh_token | 必須,上面獲取到的 refresh_token |
返回值:
{
"access_token":"a14afef0f66fcffce3e0fcd2e34f6ee4",
"token_type":"bearer",
"expires_in":3920,
"refresh_token":"4a633d136b6d56a41829b73a424803vd"
}
refresh_token 只有在 access_token 過期時才能使用,並且只能使用一次。當換取到的 access_token 再次過期時,使用新的 refresh_token 來換取 access_token
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
Figure 2: Refreshing an Expired Access Token
隱式模式
該方式一般用於移動客戶端或網頁客戶端。隱式授權類似於授權碼授權,但 token 被返回給使用者代理再轉發到客戶端(APP),因此它可能會暴露給使用者和使用者裝置上的其它客戶端(APP)。此外,此流程不會對客戶端(APP)的身份進行身份驗證,並且依賴重定向 URI(已在服務商中註冊)來實現此目的。
基本原理:要求使用者授權應用程式,然後授權伺服器將訪問令牌傳回給使用者代理,使用者代理將其傳遞給客戶端。
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+
Note: The lines illustrating steps (A) and (B) are broken into two
parts as they pass through the user-agent.
Figure 4: Implicit Grant Flow
1、同樣以 coding 和 github 為例:
https://github.com/login/oauth/authorize?
response_type=token&
client_id=a5ce5a6c7e8c39567ca0&
redirect_uri=https://coding.net/api/oauth/github/callback&
scope=user:email
欄位 | 描述 |
---|---|
response_type | 必須,固定為 token。 |
client_id | 必須。當客戶端被註冊時,有授權伺服器分配的客戶端標識。 |
redirect_uri | 可選。由客戶端註冊的重定向URI。 |
scope | 可選。請求可能的作用域。 |
state | 可選(推薦)。任何需要被傳遞到客戶端請求的URI客戶端的狀態。 |
返回值:
https://coding.net/api/oauth/github/callback#
access_token=a14afef0f66fcffce3e0fcd2e34f6ff4&
token_type=token&
expires_in=3600
欄位 | 描述 |
---|---|
access_token | 必須。授權伺服器分配的訪問令牌。 |
token_type | 必須。令牌型別。 |
expires_in | 推薦,訪問令牌過期的秒數。 |
scope | 可選,訪問令牌的作用域。 |
state | 必須,如果出現在授權請求期間,和請求中的 state 引數一樣。 |
授權錯誤和上面一樣
2、使用者代理提取令牌 token 並提交給 coding
3、coding 拿到 token 就可以獲取使用者資訊了
curl -H "Authorization: token a14afef0f66fcffce3e0fcd2e34f6ff4" https://api.github.com/user
資源所有者密碼模式
使用者將其服務憑證(使用者名稱和密碼)直接提供給客戶端,該客戶端使用憑據從服務獲取訪問令牌。如果其它方式不可行,則只應在授權伺服器上啟用該授權型別。此外,只有在客戶端受到使用者信任時才能使用它(例如,它由服務商自有,或使用者的桌面作業系統)。
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
Figure 5: Resource Owner Password Credentials Flow
POST 請求
密碼憑證流程
https://oauth.example.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
欄位 | 描述 |
---|---|
grant_type | 必須,固定為 password。 |
username | 必須,UTF-8 編碼的資源擁有者使用者名稱。 |
password | 必須,UTF-8 編碼的資源擁有者密碼。 |
scope | 可選,授權範圍。 |
返回值:
{
"access_token" : "...",
"token_type" : "...",
"expires_in" : "...",
"refresh_token" : "...",
}
如果授權伺服器驗證成功,那麼將直接返回令牌 token,改客戶端已被授權。
客戶端模式
這種模式只需要提供 client_id
和 client_secret
即可獲取授權。一般用於後端 API 的相關操作。
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
Figure 6: Client Credentials Flow
POST 請求
客戶端憑證流程:
https://oauth.example.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
欄位 | 描述 |
---|---|
grant_type | 必須。必須設定到客戶端證書中。 |
scope | 可選。授權的作用域。 |
返回值
{
"access_token" : "...",
"token_type" : "...",
"expires_in" : "...",
}
如果授權伺服器驗證成功,那麼將直接返回令牌 token,改客戶端已被授權。