为什么使用urn:ietf:wg:spring cloud oauth2:2.0:oob

阿里巴巴开放平台
应用程序可通过调用开放平台提供的API获取到阿里巴巴的会员、交易等数据,因为涉及数据隐私,所以在使用前必须获得阿里巴巴会员的授权,才可以调用API(公开数据除外)。
目前开放平台提供了三种授权方式:单用户授权,简单多用户授权,OAuth授权,具体内容如下:
单用户授权意味着您的应用程序只能被一个阿里巴巴用户授权,适用于企业对接场景,企业只需要在1688上使用一个账号进行商品采购或者商品售卖,授权流程可以简单归纳为: (1)对接应用审核通过后开发者可以在添加授权用户,默认为开发者自己,如果授权用户有登录邮箱,系统会自动发送邀请邮件,开发者账号除外;
(2)授权用户拿到邮件中或者开发者提供的授权确认链接,然后访问此链接确认授权;(3)开发者可以参考了解如何调用API,注意不需要传access_token,因为这样调用api操作的就是唯一授权用户的数据。
多用户授权适用于企业对接场景,并且企业在1688有多个店铺或者买家账号,授权流程可以简单归纳为: (1)开发者在添加授权用户,默认为开发者自己,如果授权用户有登录邮箱,系统会自动发送邀请邮件,开发者账号除外; (2)授权用户拿到邮件中或者开发者提供的授权确认链接,然后访问此链接确认授权;(3)开发者在拿到持久的访问令牌(accessToken); (4)开发者可以参考了解如何调用API,注意调用API时传入此accessToken,这样操作的是此token对应授权用户的数据。
阿里巴巴开放平台采用OAuth 2.0作为授权协议,授权流程可以简单归纳为: (1)获取临时令牌; (2)用临时令牌换取长时令牌以及访问令牌; (3)访问令牌过期后用长时令牌刷新访问令牌。 关于临时令牌、访问令牌以及长时令牌的介绍请参考下一节
1、appKey和appSecret
appKey是app的唯一标识,appSecret是app的密钥,它们相当于用户名和密码。注册app之后就会生成一个appKey和appSecret,请妥善保存。另外appSecret和密码一样也可以重置,在编辑或者查看app的页面都可以重置密钥。
即临时令牌。当用户在开放平台访问app前,需对app进行授权,用户授权后,app方可访问用户的隐私数据。用户在完成授权之后,会跳转到redirect_uri,并带上临时令牌code,app再用code从开放平台后台接口换取access_token。code为一次性令牌,有效期为2分钟。
3、access_token
即用户授权令牌,为用户一次会话的授权标识,有效期10小时。在获得code后,通过调用开放平台后台接口getToken来获取access_token。
App在访问用户隐私数据时,需要带上access_token,也只有accessToken才能作为访问的凭证,其他token如code和refreshToken都不能直接作为访问凭证,需要转换为accessToken之后才能访问用户隐私数据。
4、refresh_token
即长时令牌,有效期半年。当access_token过期后,可以使用refresh_token换取新的access_token访问用户数据。
5、redirect_uri
即回调地址,指的是App发起请求时,所传的回调地址参数,必填。请参考授权流程中的说明。
请选择授权方式:
托管式授权
客户端授权
注意:请根据自己APP的情况选择对应的授权方式,如果使用了错误的授权方式可能给您APP的使用带来不便
1、托管式授权
第三方开发者提供的app会托管在应用市场进行售卖,用户要通过应用市场访问app,那么这样的app就要采用托管式授权方式。
2、WEB端授权
第三方开发者提供的app属于网页应用,但是不在应用市场售卖,可以自用或者自行售卖,用户直接在浏览器中输入app入口地址即可访问,那么这样的app就要采用WEB授权方式。
3、客户端授权
第三方开发者提供的app属于客户端应用,用户需要下载应用到本地客户端才能使用,那么这样的app就要采用客户端授权方式。
授权及使用授权时序图
托管式授权是指将授权托管给应用市场,由应用市场发出授权请求的方式。
1) 用户通过应用市场使用app
2) 应用市场向1688开放平台发起授权请求
3) 如果用户没有授权或者授权过期,需要输入用户名密码,并确认授权;否则跳到下一步
4) 应用市场获得临时令牌code,并302跳转到app入口地址,同时带入参数code
5) app使用code换取access_token
https://gw.open.1688.com/openapi/http/1/system.oauth2/getToken/YOUR_APPKEY?grant_type=authorization_code&need_refresh_token=true&client_id= YOUR_APPKEY&client_secret= YOUR_APPSECRET&redirect_uri=YOUR_REDIRECT_URI&code=CODE
注:此接口必须使用POST方法提交;必须使用https
getToken接口参数说明:
a) grant_type为授权类型,使用authorization_code即可
b) need_refresh_token为是否需要返回refresh_token,如果返回了refresh_token,原来获取的refresh_token也不会失效,除非超过半年有效期
c) client_id为app唯一标识,即appKey
d) client_secret为app密钥
e) redirect_uri为app入口地址
f) code为授权完成后返回的一次性令牌
g) 调用getToken接口不需要签名
注:如果超过code有效期(2分钟)或者已经使用code获取了一次令牌,code都将失效,需要返回第二步重新获取code
:{"aliId":"","resource_owner":"xxx","memberId":"xxxxxxx","expires_in":"36000","refresh_token":"479f6e-ab62-29d3e82277d9","access_token":"f14da3b8-b0b1-4f73-a5de-9bed637e0188","refresh_token_timeout":"22+0800"}
说明:resource_owner为登录id,memberId为会员接口id,aliId为阿里巴巴集团统一的id,refresh_token_timeout表示refreshToken的过期时间
使用令牌访问用户隐私数据
例如访问以下api
: http://gw.open.1688.com/openapi/param2/1/cn.alibaba.open/member.get/1?memberId=xxx&access_token=ACCESS_TOKEN&_aop_signature=SIGENATURE
签名示例见
换取accessToken流程
如果refreshToken有效并且accessToken已经过期(超过10小时),那么可以使用refresh_token换取access_token,不用重新进行授权,然后访问用户隐私数据。
注意:如果refreshToken已经超过了半年的有效期,用户修改密码,用户订购到期或者用户通过取消了对app的授权,那么都需要用户重新授权获取refreshToken
换取accessToken的url示例如下:
https://gw.open.1688.com/openapi/param2/1/system.oauth2/getToken/YOUR_APPKEY
请求参数如下:
grant_type=refresh_token&client_id=YOUR_APPKEY&client_secret=YOUR_APPSECRET&refresh_token=REFRESH_TOKEN
注:此接口必须使用POST方法提交;必须使用https
a) 此处grant_type参数必须为refresh_token,表示通过refreshToken换取accessToken,而不是通过临时code换取
b) 调用getToken接口时不需要签名
换取refreshToken流程
如果当前时间离refreshToken过期时间在30天以内,那么可以调用postponeToken接口换取新的refreshToken;否则会报错。
注意:有自动功能的应用(用户不进入应用主页也能使用第三方应用,因为在应用后台可以自动调用api处理用户数据,如自动重发类的应用)才需要调用此接口。如果没有自动功能,那么不需要调用,因为用户必须在应用主页操作才能获取以及修改用户数据,所以即使refreshToken过期了,用户再次通过应用市场进入应用主页时授权一次即可
换取refreshToken的url示例如下:
https://gw.open.1688.com/openapi/param2/1/system.oauth2/postponeToken/YOUR_APPKEY
请求参数如下:
client_id=YOUR_APPKEY&client_secret=YOUR_APPSECRET&refresh_token=REFRESH_TOKEN&access_token=ACCESS_TOKEN
返回结果类似getToken的返回结果:
{"aliId":"","resource_owner":"xxx","memberId":"xxxxxxx","expires_in":"36000","refresh_token":"479f6e-ab62-29d3e82277d9","access_token":"f14da3b8-b0b1-4f73-a5de-9bed637e0188","refresh_token_timeout":"22+0800"}
注:此接口必须使用https,get和post都行
a) 调用postponeToken接口需要授权,所以必须带上access_token参数
b) 调用postponeToken接口时不需要签名
c) refresh_token参数表示当前使用的refreshToken,如果refreshToken的有效期和当前时间的差值小于30天,那么调用此接口后当前使用的refreshToken失效,返回一个新的refreshToken
子账号授权
大企业采购Error 404 (Not Found)!!1
404. That’s an error.
The requested URL /611004/ was not found on this server.
That’s all we know.OAuth 2.0 教程: Grape API 整合 Doorkeeper
· Ruby China
本帖已被设为精华帖!
最近我要实作使用 OAuth 2 认证的 API ,我先是看了 Spec (RFC 6740 、 RFC 6750),然后研究了既有的 Rails solution ,但因为 API 是用 Grape 盖的,又 Doorkeeper / Rack::OAuth2 / Grape 内建的 OAuth 2 认证全都无法直接拿来用,所以只好自己实现 API 认证这部份。
我把实现的过程写成了教程:
简单来说是这样:
用 Devise 造 User (Resource Owner) 系统
用 Grape 造 API (Resource Server)
用 Doorkeeper 造 OAuth 2 Provider (Authorization Server)
自己用 Rack::OAuth2 接 Grape 来造出 API 上的 Guard
其中第四项我搞最久,希望可以帮后来的同学省到时间 :)
应要求全文转贴 :) 不过很抱歉是繁体的,原本想用 OpenCC 转换却不会用它的命令列工具…
這篇文章示範如何使用 OAuth 2 保護 API ,其中 API 是用 Grape 造出來的,掛在 Rails 底下。
整個實作流程會造出這些東西:
Resource Owner - 可以授權給第三方 App 的角色,也就是 User。
Authorization Server - 用來處理與 OAuth 2 授權有關的事務,像是:
Clients - 需要有 Clients (Apps) 的 CRUD 。
Access Token (Model) - 需要有個 Model 來儲存 Access Token 。
Authorization Endpoint - 這裡來處理 Auth Code Grant 和 Implicit Grant 。
Token Endpoint - 這裡來真正核發 Token 。
Resource Server - 給 App 存取的地方,也就是 API ,一部份需要 Access Token 才能存取的叫做 Protected Resource 。
Resource Server 上面的 Guard - 用途是「保護某些 API ,必須要帶 Access Token 才能存取」,俗稱保全。
本文使用這些套件來實作:
Resource Owner (User) -
Authorization Server (OAuth 2 Provider) -
Resource Server (API) -
Guard - 用
來整合 Grape
因為 Doorkeeper 的 doorkeeper_for 只能用在 Rails ,而 Guard 只是一個 Rack Middleware ,所以這裡要自己拼湊。詳情請見先前的文章 。
所有過程我都會放在
這個 repository ,各 step 有對應的 step-x tag ,例如 Step 1 完成的結果可以在 step-1 這個 tag 看到。
Step 1: 造 Resource Owner 邏輯 (User)
用 Devise 做。這個應該是 Rails Developer 的基本功,所以不解釋了,請見 。
可以試著打開 /pages/secret ,會要求登入。
Step 2: 造 Resource Server (API)
用 Grape 是因為不想要讓 API 經過太多 Rails 的 stack。
這個不難,而且不是本文的重點,所以直接看官方文件就好了。成品可以看
Step 3: 造 Authorization Server (Provider)
既然底是 Rails ,那麼就直接上 Doorkeeper 就好了。可以看
,若有買 Pro 不妨去看看。不過其實照官方文件做也不難:
安裝 Doorkeeper Gem
gem 'doorkeeper'
別忘了跑 bundle install 。
然後跑這些來安裝:
$ rails generate doorkeeper:install
$ rails generate doorkeeper:migration
$ rake db:migrate
再照他文件說的去接 Devise 來認證 Resource Owner:
# 認證 Resource Owner 的方法,直接接 Devise
resource_owner_authenticator do
current_user || warden.authenticate!(:scope =& :user)
這樣就好了。
Doorkeeper 會建這些 model:
OauthApplication - Clients 的註冊資料庫
OauthAccessGrant - Auth Code 流程第一步產生的 Auth Grants 的資料庫
OauthAccessToken - 真正核發出去的 Access Tokens,包含對應的 Refresh Token (預設關閉)
Doorkeeper 開的 Routes 有這些:
| Method (REST) | Path
|---------------|------------------------------------|------------------------------------|
| /oauth/authorize
| Authorization Endpoint
| /oauth/authorize
| User 許可 Authorization 時的 action |
| /oauth/authorize
| User 拒絕 Authorization 時的 action |
| /oauth/authorize/:code
| (應該是用來 Local 測試的)
| /oauth/authorize
| (不明的 update grant)
| /oauth/token
| Token Endpoint
| /oauth/token/info
| Token Debug Endpoint
| resources
| /oauth/applications
| Clients 管理界面
| /oauth/authorized_applications
| Resource Owner 管理授權過的 Clients |
| /oauth/authorized_applications/:id | Resource Owner 管理授權過的 Clients |
其中 Authorization Endpoint 的 show 只會顯示 grant code ,可能是 Local Testing 要用的;而 update 則是沒有任何 action 去接它,不確定是不是 dead feature 。
可以發現到:
幫你蓋好了 Authorization Endpoint 和 Token Endpoint
還附加 Token Debug Endpoint ,在 Implicit Flow 可以驗證 Token 的真實性。
還有附 Clients 管理界面
還可以讓 User 管理授權過的 Clients
所以一個 Authorization Server 該有的東西它都提供了。
Step 3.1: 開測試用的 Client
蓋完 Authorization Server 之後,要去開一個 Client 。可以打開 /oauth/applications ,其中 Client 的 redierct URI 填入 http://localhost:12345/auth/demo/callback ,實際上沒有跑 Web server 在 localhost:12345 也沒關係,最終目的是拿到 code 或 token。
Step 3.2: 拿取 Access Token
現在可以來試著拿 Access Token 了,我們要用人腦模擬 Client ,來跑 。
步驟如下:
首先打開剛剛生的 Client 的 show 頁面,會看到有 Application ID 、 Secret 等資訊的頁面。最下面有一個 Authorize 的連結,點下去會打開到這個網址(假設這個 Rails App 開在 localhost:9999 ,下同)(中間斷行比較好讀):
?client_id=4a407c6a8d3c75e17ae940db6df882c86aaeac2c788d6
&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2Fauth%2Fdemo%2Fcallback
&response_type=code
就像之前在流程文裡介紹過的,它用 GET 去 Authorization Endpoint 求 Grant Code ,附上自己的 Client ID 和 Redirection URI 。
接著會問你要 Authorize 還是 Deny ,當然選 Authorize 。
接著會跑到一個瀏覽器打不開的網址,是先前設定的 Redirection URI,不過沒關係,我們已經得到 Grant Code 了(中間斷行比較好讀):
?code=21e1c81db4e619a23d4ed25d4189baab358be8b591a
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Grant Code
如果在 Step 3.1 照網頁上的指示填入 urn:ietf:wg:oauth:2.0:oob ,最後會出現 grant code 的 show 頁面,直接把 grant code 曬給你,這是 local 用來測試用的,類似 。
到這裡,Client 就拿到 Grant Code 了,依照流程,接下來是 Client 要另外從後台偷偷去 Authorization Server 把這個 Grant Code 換成 Access Token。
因為要填的資料太多了,我抓一下
的畫面,填完最後按下「Send」就會拿到 Access Token 了!
Step 4: 造 Resource Server 上的 Guard
造 Guard 這件事比較難,就像我說過的,在 API 是 Grape 的情況下, 沒有一個 Guard 是可以直接拿來用的 ;即使你用 Rails 做 API 好了,doorkeeper_for 現在也還只是半成品。我目前的做法是把 Rack::OAuth2 的 Bearer Token middleware 接到 Grape 上面,邏輯參考了 doorkeeper_for 的實作方式。
這裡會寫得比較仔細。可以看
我寫成一個 module ,用
去簡化 module 化的程式,放在 api/concerns/api_guard.rb。
Step 4.1: 安裝 Rack Middleware 來抓取 Access Token (String)
Rack::OAuth2 這個 Rack Middleware 在安裝 (use) 的時候要傳一個 block ,它會去 call ,但 call 的條件是「Request 有帶 OAuth 2 Token」這樣才會 call ,意思是說:
如果 Request 有帶 Authorization: Bearer XXX 或 ?access_token=xxx 才會 call
如果 Request 不帶上述的參數,就不會 call ,直接 pass 到下一個 middleware stack (!)
而且這個 Middleware 在 call 之後其實會把 return value 直接存進 request.env["某個 key"] 裡面,意思就是說 「它只是給你 fetch access token 用的」 ,不能拿來「確認 Access Token 有效並放行 API access」,這件事要在 API 層做。
那麼就來安裝這個 Middleware 吧,但只拿來 fetch access token string :
included do
# OAuth2 Resource Server Authentication
use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
# The authenticator only fetches the raw token string
# Must yield access token to store it in the env
request.access_token
Step 4.2: 做一個 private method 來取出先前拿到的 Access Token (String)
前文提到 Middleware 會把 Token 存在 request.env 裡面,具體就是 request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN] ,所以就把它拿出來吧。
helpers do
def get_token_string
# The token was stored after the authenticator was invoked.
# It could be nil. The authenticator does not check its existence.
request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
Step 4.3: 做一個 private method 來把 Token String 變成 Instance
Token String 只是單純的字串,還需要去 Database 裡面撈才能換成 Instance 。參考了 doorkeeper_for 的做法,我直接呼叫它的 AccessToken.authenticate ,撈不到會直接回 nil:
helpers do
def find_access_token(token_string)
Doorkeeper::AccessToken.authenticate(token_string)
Step 4.4: 做一個 service 來驗證 Access Token 是否合法
OAuth2::AccessTokenValidationService 我放在 app/service 裡面,其中會驗證是否過期 (expired) 、是否被撤銷 (revoked) ,這兩個都是 Doorkeeper::AccessToken 內建的 methods。但此外還要驗證所需的 scopes 是否包含在 Access Token 的 scopes 裡面。回傳的驗證結果會是 VALID 、 EXPIRED 、 REVOKED 、 INSUFFICIENT_SCOPE 四個常數的其中一個(定義在該 module 裡面)。
在 Grape Endpoint 放一個 validate_access_token helper 來方便處理這件事,它會直接回傳結果,也就是上述四個之中的一個, caller 就可以根據驗證結果決定要怎麼回 response 。
helpers do
def validate_access_token(access_token, scopes)
OAuth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
在 Service 裡面要驗證 scopes ,我的演算法其實很簡單,就是集合比較而已;有的網站會有「A scope 包含 B scope」的設計,如果要做成這樣的話,就不能用單純的集合比較了。純集合比較的演算法是這樣:
如果沒有要求任何 scopes ,那其實任何 Access Token 都符合,就回 true。
如果有要求任何 scopes ,那麼「授權過的 scopes」就得是「所需的 scopes」的宇集,剛好 Ruby 有內建 Set 這個資料結構,把兩個 Array 都轉成 Set 就能方便比較了。
def sufficent_scope?(token, scopes)
if scopes.blank?
# if no any scopes required, the scopes of token is sufficient.
return true
# If there are scopes required, then check whether
# the set of authorized scopes is a superset of the set of required scopes
required_scopes = Set.new(scopes)
authorized_scopes = Set.new(token.scopes)
return authorized_scopes &= required_scopes
Step 4.5: 製作 Guard 來擋住沒有合法 Access Token 的 Requests
現在要真正寫 guard! method 來擋 API use 了。
為了讓程式流程看起來更簡潔,根據不同的錯誤情況,定義了不同的 Exception ,各 Exception 要怎麼處理,則可以交由 Grape 的 rescue_from 處理(我是這樣做的),或 Exception 裡面直接 raise Rack::OAuth2 內建的 exception。
邏輯是這樣:
先去抓出 Token String
如果沒給 Token ,表示 Client 不知道要認證,丟 MissingTokenError
照 spec 是要回 401 但是不給任何錯誤訊息
如果有給 Token 但是資料庫裡面找不到,丟 TokenNotFound
照 spec 是要回 401 加上 Invalid Token Error
如果找得到 Token 則進一步驗證是否可以用來存取該 API (根據有否過期、被撤銷,如果有要求 scope 的話則再檢查 scope)
若驗證結果是 VALID ,則把 @current_user 指定給該 Access Token 綁定的 Resource Owner (User)
若驗證結果不是 VALID ,則丟出相對應的 Exceptions
照 spec ,如果是因為 scope 不足,則是回 403 加上 Insufficient Scope Error ,其他情況則是要回 401 加上 Invalid Token Error
helpers do
def guard!(scopes: [])
token_string = get_token_string()
if token_string.blank?
raise MissingTokenError
elsif (access_token = find_access_token(token_string)).nil?
raise TokenNotFoundError
case validate_access_token(access_token, scopes)
when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
raise InsufficientScopeError.new(scopes)
when Oauth2::AccessTokenValidationService::EXPIRED
raise ExpiredError
when Oauth2::AccessTokenValidationService::REVOKED
raise RevokedError
when Oauth2::AccessTokenValidationService::VALID
@current_user = User.find(access_token.resource_owner_id)
Step 4.6: 把 Exception 轉送到 Rack::OAuth2 內建的錯誤回應方式
我的做法是用 Grape 的 rescue_from 去接 exceptions ,當然要直接 raise 也可以就是了。
要注意的是:
Bearer::ErrorMethods 有內建一組 error_description 的預設值,根據不同的 error code 去對應
但只有在 Rack 的 authenticator 裡面使用相對應的 helper method (如 insufficiet_scope!) 才會填入
直接 call 這個 middleware 則不會自動填入錯誤訊息
所以必須手動填入
沒給 Token 要視為「Client 不知道要 Authenticate」
所以 error code 不屬於 Spec 裡面定義的任何一個
error_description 也不需要給。
使用 Bearer::Unauthorized 回 401
Token 找不到、過期 (Expired) 、被撤銷 (Revoked) 的 error code 都是 invalid_token
其實可以用同一個 error_description
我的寫法會把三種情況分別丟不同的 Exception,並填入不同的 error_description
你實作的時候可以用同一個,這並不會違反 spec
使用 Bearer::Unauthorized 回 401
Token 的 scope 不足會使用 insufficient_scope 的 error
使用 Bearer::Forbidden 回 403
可是 Rack::OAuth2 的實作並沒有填入 WWW-Authenticate header (只有 401 強制要求要填)
所有的 error message (包括 scope)會出現在 JSON response body 裡面
我有 ,會一併填入 WWW-Authenticate 裡面
這個實作沒有填入 error_uri 和 realm ,其 realm 會使用 Rack::OAuth2 內建的。
included do |base|
install_error_responders(base)
module ClassMethods
def install_error_responders(base)
error_classes = [ MissingTokenError, TokenNotFoundError,
ExpiredError, RevokedError, InsufficientScopeError]
base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
def oauth2_bearer_token_error_handler
Proc.new {|e|
response = case e
when MissingTokenError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
when TokenNotFoundError
Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
:invalid_token,
"Bad Access Token.")
# etc. etc.
response.finish
Step 4.7: 製作用來擋全 API 的 Guard
這是仿 doorkeeper_for :all 的,用途是「這個 API 底下的所有 Endpoints 都要擋」。在 Grape 的世界裡面,它要是放在 Grape::API 裡面的 class method ,所以我寫在 ClassMethods module 裡面,一 call 就是塞 before filter 進去,它底下每個 endpoint 都會過這個 filter。
module ClassMethods
def guard_all!(scopes: [])
guard! scopes: scopes
Step 4.8: 現在可以用 OAuth 2 來擋 API 了
單獨擋一個 Endpoint:
class SampleAPI & Base
get "secret" do
guard! # Requires a valid OAuth 2 Access Token to use this Endpoint
{ :secret =& "only smar)" }
擋一個 API 底下所有 Endpoints:
class SecretAPI & Base
guard_all!
# Requires a valid OAuth 2 Access Token to use all Endpoints
get "secret1" do
{ :secret1 =& "Hi, #{current_user.email}" }
get "secret2" do
{ :secret2 =& "only smar)" }
不帶 Token 就去打 API 會被拒絕:
$ curl -i http://localhost:9999/api/v1/secret/secret1.json
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="The API"
Content-Type: application/json
Cache-Control: no-cache
{"error":"unauthorized"}
附 Token 再去打 API 就沒問題了,並且會告訴我這個 User 是誰:
$ curl -i http://localhost:9999/api/v1/secret/secret1.json \
& -H "Authorization: Bearer a14bbfbb6a3bad6cba25f32a28acc931a74ead06ca904c05281b4c"
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=0, private, must-revalidate
{"secret1":"Hi, "}
Step 5: 使用 Scope
到目前為止,實作出來的 OAuth 2 的 Guard 雖然支援「scopes」,但 Authorization Server 不支援。要怎麼限制某些 API 必須使用「授權了某些 scopes」的 Access Token 才能存取呢?以下是範例,詳細可以參考 Doorkeeper 的文件
。程式碼見
首先在 config/initializers/doorkeeper.rb 裡面增加 scopes 的定義,例如
# Define access token scopes for your provider
# For more information go to https://github.com/applicake/doorkeeper/wiki/Using-Scopes
default_scopes
# 如果 Client 不索取任何 scopes 則預設使用這組 scopes
optional_scopes :top_secret,
# 其他可以額外申請的 scopes
:el, :psy, :congroo
要重開 Rails server 生效。
接著打開 Authorize 的頁面,點下去的網址不會帶有「想要索取的 scopes」,所以先把網址複製下來,後面手動附上 scope=top_secret 參數:(中間斷行比較好讀)
?client_id=4a407c6a8d3c75e17ae940db6df882c86aaeac2c788d6
&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2Fauth%2Fdemo%2Fcallback
&response_type=code
&scope=top_secret
到這裡會再問你要不要 Authorize ,所以你知道了 Authorization Server 會區別帶有不同 scopes 的 grants。Authorize 之後會得到一組 grant code ,再照標準流程拿 Token 。我這次拿到的 Token 是 5d840a4e467bcbf16b53f853f3cd4f001e51a5c95abfd.
現在我在 SampleAPI 裡面新增兩個 endpoint ,需要 scopes 才能存取:
get "top_secret" do
guard! scopes: [:top_secret]
{ :top_secret =& "T0P S3CR37 :p" }
get "choice_of_sg" do
guard! scopes: [:el, :psy, :congroo]
{ :says =& "El. Psy. Congroo." }
用之前申請過的 Access Token 來打 top_secret 這個 API 會被拒絕(中間斷行比較好讀):
$ curl -i http://localhost:9999/api/v1/sample/top_secret.json \
& -H "Authorization: Bearer a14bbfbb6a3bad6cba25f32a28acc931a74ead06ca904c05281b4c"
HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-cache
"error":"insufficient_scope",
"error_description":"The request requires higher privileges than provided by the access token.",
"scope":"top_secret"
若用新拿到的 Token 就會過了:
$ curl -i http://localhost:9999/api/v1/sample/top_secret.json \
& -H "Authorization: Bearer 5d840a4e467bcbf16b53f853f3cd4f001e51a5c95abfd"
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=0, private, must-revalidate
{"top_secret":"T0P S3CR37 :p"}
然而如果拿這個 Token 去打 choice_of_sg 就會被拒絕(中間斷行比較好讀):
$ curl -i http://localhost:9999/api/v1/sample/choice_of_sg.json \
& -H "Authorization: Bearer 5d840a4e467bcbf16b53f853f3cd4f001e51a5c95abfd"
HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-cache
"error":"insufficient_scope",
"error_description":"The request requires higher privileges than provided by the access token.",
"scope":"el psy congroo"
當然,因為 scope 不符啊。這時候就要再重新申請一個 Token ,照前面說過的流程,要先有一個 Authorize 的 URL:
?client_id=4a407c6a8d3c75e17ae940db6df882c86aaeac2c788d6
&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2Fauth%2Fdemo%2Fcallback
&response_type=code
&scope=el%20psy%20congroo
多重 scope 的情況下,各 scope 之間用空格 %20 分開。
最後我拿到的新 Token 是 0bd8f80ced97ecf1e55 。再用它去打 choice_of_sg API 就會回我了:
curl -i http://localhost:9999/api/v1/sample/choice_of_sg.json \
-H "Authorization: Bearer 0bd8f80ced97ecf1e55"
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=0, private, must-revalidate
{"says":"El. Psy. Congroo."}
以上的 Tutorial 只簡單示範了如何從零建造一個 OAuth 2 Authorization Server 並且用 OAuth 2 Bearer Token 來保護 Grape API 。以下這些問題沒有處理:
要可以限制「誰才可以開 Clients」 (例如站長,或是有登入的使用者),我想應該是 Doorkeeper 的 admin_authenticator 和 enable_application_owner options 。
沒有示範 Refresh Token
Doorkeeper 不能設定只要開啟哪些 Grant Flows
這我有開一個 fork 出來實作,也貼了
,等他 merge 。
因為 Guard 是自己寫的,Doorkeeper 的一些功能完全不使用,例如 access_token_methods (設定 Client 可以用哪些方式向 API 出示 Token)
承上,也沒有使用到 Doorkeeper 內建的錯誤訊息 i18n 機制
Guard 的 Error Response 的 "realm" 不同步
Guard 的 insufficient_scope Error 沒有把參數放在 WWW-Authenticate header
有實作這個,還沒開 PR 給原版。
雖然在 403 放這個 header doesn't make sense ,但總覺得還是統一放在這裡比較好啊…
Scope 的 matching 只用了簡單的集合比較,不適用於某些 A scope 吃 B scope 情況
又,寫完的 Guard 並沒有包成 Gem 還是什麼的……之後應該會包一下吧。
有任何問題(包括指教本文的謬誤)歡迎在下面的留言板提出 :)
我已经看了,非常好的技术文章。
如果能全文转载过来,就给你加精,如何?
没问题,我贴过来了,抱歉是繁体的,我本来想用 OpenCC 转换但不会用它的工具程式…
我喜欢繁体,打算以后也试着用繁体写文章
4楼 已删除
5楼 已删除
我读过了喔!也是因为读过才知道怎么做的,此前我看了任何 library 都不知道它们在做什么
繁体也无妨,必须起立鼓掌。。。
8楼 已删除
给楼主点个赞!
楼主熟读领悟 OAuth 的 spec 才能搞懂各家的 client plugins 以及 OAuth provider 的实现,并且总结出一套 Grape API 整合 Doorkeeper 的具体方案的,可以看楼主近期的Blog,关于 OAuth 相关的研究,从协议到实现,研究的非常鞭辟入里,这种钻研精神,着实让人汗颜。
感谢! 学到了很多. 如果是单纯的API, 是不是直接使用rack-oauth2会更加灵活呢?
12楼 已删除
你好像很cool的样子 ;)
14楼 已删除
完全看不懂,只能说作者太牛逼了
16楼 已删除
17楼 已删除
已全部删除。
咋感觉我俩在歪楼啊。。。
能直接用的话我也很想用啊 :p 本文 step 4 就是在搞定 rack-oauth2 整合的,基本上还是要把 Access Token 的业务逻辑搞定
好文,最近刚想研究下 oauth 先点个赞,再马克,最后慢慢看~
多谢楼主!
我想知道做手机api接口,用OAuth 2.0是不是要比用OAuth 1.0要方便很多,我只用过1,就感觉非常烦琐。
纯新手膜拜,大赞楼主。
如此详细,马克一下,细细学习。
好棒啊!我最近也要实现一个认证和授权系统,如果遇到问题可以咨询你么?
这两天刚好再看相关内容,一定好好学习。
您好,能帮我解决下这个问题么?
比如说在doorkeeper中,access token是产生算法是什么呢?授权服务器又是如何验证access token的合法性的呢?
刚刚回应了 :)
果断收藏点赞,最近好好读一下,准备要实现类似的需求,感谢
在想是不是真的有必要用grape,如果不用会简单很多。(我是新手)
不會,因為 doorkeeper_for 是半成品,雖然可以動,但要做到像 RFC6750 那樣的標準,還是得自己刻一份
你好:我有一个疑问:问什么OAuth2.0的在授权的时候,要先请求获得授权码,再用授权码换取Access Token呢。为什么不在 客户端将浏览器引导至授权服务器让用户授权之后,直接返回访问Access Token呢?
我认为是这样:
索取 Token 这个操作需要真正可以信赖的 client 來执行
在 Authorization Code Grant Flow 里面,是由放在服务器上面的 client 来做(也就是网站的后端程序)
可以通过 Client Authorization 来确认 Client 是真的
在 Implicit Flow 里面,完全依赖浏览器,Client 无法信赖,虽然可以一步得到 Token ,但由于 Client 不可信,以及 Client 里面的数据很容易就能取出(包括 Access Token),所以规格书里面规定,这个流程取得的 Token 时效要很短,并且无法自动 Refresh 。
回完才看到你在
也有贴文 :p 应该就是一楼的回答这样子的原因了
最近也在做OAuth2.0 Server端的开发,看了LZ的帖子和Blog,受益匪浅,向大神致谢。
相对于用Rack::OAuth2 做一个全新的guard,这种对doorkeeper的扩展方案是不是会更好一些?
膜拜一下,最近正要写个OAUTH2 SERVER
学习了,看繁体字实在速度不快,我找到了一个可以繁体转简体的工具,用Ubuntu下包管理器apt-get install opencc(0.4.3版的)。老实说,这个命令行工具用起来挺麻烦的,默认简转繁,繁转简的命令如下: opencc -i test.md -o test.md -c zht2zhs.ini,如果是编译安装的话,配置文件应该是 t2s.json 。
学习了,想问一下,活跃的token怎么让他不過期 (expired) 呢
后方可回复, 如果你还没有账号请点击这里 。
共收到 34 条回复

我要回帖

更多关于 oauth2 的文章

 

随机推荐