OAuth 簡介
OAuth 是由 Blaine Cook、Chris Messina、Larry Halff 及 David Recordon 共同發(fā)起的,目的在于為 API 訪問授權提供一個安全、開放的標準。
基于 OAuth 認證授權具有以下特點:
安全。OAuth 與別的授權方式不同之處在于:OAuth 的授權不會使消費方(Consumer)觸及到用戶的帳號信息(如用戶名與密碼),也是是說,消費方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權。
開放。任何消費方都可以使用 OAuth 認證服務,任何服務提供方 (Service Provider) 都可以實現(xiàn)自身的 OAuth 認證服務。
簡單。不管是消費方還是服務提供方,都很容易于理解與使用。
OAuth 的解決方案如下圖所示。
圖 1. OAuth Solution
如圖 1 所示 OAuth 解決方案中用戶、消費方及其服務提供方之間的三角關系:當用戶需要 Consumer 為其提供某種服務時,該服務涉及到需要從服務提供方那里獲取該用戶的保護資源。OAuth 保證:只有在用戶顯式授權的情況下(步驟 4),消費方才可以獲取該用戶的資源,并用來服務于該用戶。
從宏觀層次來看,OAuth 按以下方式工作:
消費方與不同的服務提供方建立了關系。
消費方共享一個密碼短語或者是公鑰給服務提供方,服務提供方使用該公鑰來確認消費方的身份。
消費方根據(jù)服務提供方將用戶重定向到登錄頁面。
該用戶登錄后告訴服務提供方該消費方訪問他的保護資源是沒問題的。
回頁首
OAuth 認證授權流程
在了解 OAuth 認證流程之前,我們先來了解一下 OAuth 協(xié)議的一些基本術語定義:
- Consumer Key:消費方對于服務提供方的身份唯一標識。
- Consumer Secret:用來確認消費方對于 Consumer Key 的擁有關系。
- Request Token:獲得用戶授權的請求令牌,用于交換 Access Token。
- Access Token:用于獲得用戶在服務提供方的受保護資源。
- Token Secret:用來確認消費方對于令牌(Request Token 和 Access Token)的擁有關系。
圖 2. OAuth 授權流程(摘自 OAuth 規(guī)范)
對于圖 2 具體每一執(zhí)行步驟,解釋如下:
消費方向 OAuth 服務提供方請求未授權的 Request Token。
OAuth 服務提供方在驗證了消費方的合法請求后,向其頒發(fā)未經(jīng)用戶授權的 Request Token 及其相對應的 Token Secret。
消費方使用得到的 Request Token,通過 URL 引導用戶到服務提供方那里,這一步應該是瀏覽器的行為。接下來,用戶可以通過輸入在服務提供方的用戶名 / 密碼信息,授權該請求。一旦授權成功,轉(zhuǎn)到下一步。
服務提供方通過 URL 引導用戶重新回到消費方那里,這一步也是瀏覽器的行為。
在獲得授權的 Request Token 后,消費方使用授權的 Request Token 從服務提供方那里換取 Access Token。
OAuth 服務提供方同意消費方的請求,并向其頒發(fā) Access Token 及其對應的 Token Secret。
消費方使用上一步返回的 Access Token 訪問用戶授權的資源。
總的來講,在 OAuth 的技術體系里,服務提供方需要提供如下基本的功能:
第 1、實現(xiàn)三個 Service endpoints,即:提供用于獲取未授權的 Request Token 服務地址,獲取用戶授權的 Request Token 服務地址,以及使用授權的 Request Token 換取 Access Token 的服務地址。
第 2、提供基于 Form 的用戶認證,以便于用戶可以登錄服務提供方做出授權。
第 3、授權的管理,比如用戶可以在任何時候撤銷已經(jīng)做出的授權。
而對于消費方而言,需要如下的基本功能:
第 1、從服務提供方獲取 Customer Key/Customer Secret。
第 2、提供與服務提供方之間基于 HTTP 的通信機制,以換取相關的令牌。
OAuth的授權流程
你所開發(fā)的應用需要流程如下:
- 向應用服務商(新浪、搜狐等微博)請求request_token。
- 得到request_token后重定向用戶到服務商的授權頁面。
- 如果用戶選擇授權你得應用,用request_token向服務商請求換取access_token。
- 得到access_token等信息訪問受限資源。
而服務商相應的響應如下:
- 創(chuàng)建request_token返回給應用。
- 詢問用戶是否授權此應用。如果用戶授權重定向用戶至應用頁面。
- 創(chuàng)建access_token并返回給應用。
- 響應受限資源請求并返回相關信息。
- 通俗點的說法就是“你拿著你得身份證明(request_token)向服務商申請進入用戶家的門鑰匙(access_token),服務商詢問用戶同不同意,如果用戶同意服務商就給你進入用戶家門的鑰匙(access_token),拿到鑰匙后你就可以進到用戶家里”。
OAuth授權的Java實現(xiàn)
作為一個開放協(xié)議目前有很多現(xiàn)成的Oauth庫可供開發(fā)者使用,可以點擊這里下載。不過有精力有時間的話還是自己去實現(xiàn)一下OAuth授權的流程,可以很好的體會OAuth認證協(xié)議的原理。以下就是我使用Java實現(xiàn)Oauth的具體步驟,代碼很簡單,如果有畫蛇添足的地方還望高手一笑而過。
一、獲取Request_token
首先得準備一下參數(shù)及其來源:
- oauth_consumer_key —— 注冊應用后由應用服務商提供
- consumer_secret —— 注冊應用后由應用服務商提供
- oauth_callback —— 用戶授權后的返回地址
- oauth_nonce —— 隨機字符串,須保證每次都不同
- oauth_timestamp —— 時間戳
- oauth_signature_method —— 簽名base string 的方法,目前支持 HMAC-SHA1
- oauth_version —— Oauth協(xié)議版本
還需要下面三個請求地址(這些地址任何一個提供OAuth的服務商都會提供給你,看下API文檔就會找到):
- requst_token_url —— 上面第1步中的請求地址
- authorize_url —— 上面第2步的請求地址
- access_token_url —— 上面第3步的請求地址
至于如何注冊應用,新浪微博、騰訊微博等等的網(wǎng)站上都有,這里就不再詳細說明了。注冊成功后就會獲得oauth_consumer_key 和 consumer_secret 兩個參數(shù)。
oauth_callback 起的作用是當用戶授權成功后服務商會把用戶重定向到這個網(wǎng)址。
oauth_nonce 是一個隨機字符串下面是我的生成代碼:
1
2
3
4
5
6
7
8
9
10
|
public String set_nonce() { String base = "abcdefghijklmnopqrstuvwxyz0123456789" ; Random random = new Random(); StringBuffer sb = new StringBuffer(); for ( int i = 0 ; i < 18 ; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } |
oauth_timestamp 是請求的時間戳,我的代碼如下:
1
2
3
4
5
6
|
public String set_timestamp() { Date date = new Date(); long time = date.getTime(); return (time + "" ).substring( 0 , 10 ); } |
需要說明一下的是這里的時間戳為10位而不是13位,因此截取0-10位置。
其他參數(shù)直接指定就行了。
接下來,有了這些參數(shù)就可以組裝base string了。準備base string的目的就是為了得到 oauth_signature 這個參數(shù),這個參數(shù)向服務商發(fā)送請求的時候需要用到。
組裝的方法是用下面8部分
POST(也可以是GET,取決于你應用服務商支持哪個)。
- Urlencode之后的requst_token_url 。
- oauth_callback=Urlencode之后你的oauth_callback(Urlencode的參數(shù)為“utf-8”)。
- oauth_consumer_key = 你的oauth_consumer_key
- oauth_nonce = 你的oauth_nonce
- oauth_signature_method = 你的 oauth_signature_method
- oauth_timestamp = 你的oauth_timestamp
- oauth_version = “1.0”——目前大多數(shù)OAuth都采用的是1.0或1.0a版本。
需要注意的是上面除了1跟2外其他參數(shù)的格數(shù)形如: abc=“abc” ,然后先將上面1和2部分用&號相連得到串A、3-8部分用&相連得到串B,下面需要將串B再進行一次Urlencode得到串C,最后將A跟C以&號相連就得到了base string。這個過程中 oauth_callback 實質(zhì)上經(jīng)過了兩次 Urlencode ,組裝base string是非常容易出錯的,一不小心丟一個引號或者格式稍有不對就會出錯。
下面是我的Java實現(xiàn)代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public String set_basestring() throws UnsupportedEncodingException { String bss; bss = oauth_request_method + "&" + URLEncoder.encode(requst_token_url, "utf-8" ) + "&" ; String bsss = "oauth_callback=" + URLEncoder.encode(oauth_callback, "utf-8" ) + "&oauth_consumer_key=" + oauth_consumer_key + "&oauth_nonce=" + oauth_nonce + "&oauth_signature_method=" + oauth_signature_method + "&oauth_timestamp=" + oauth_timestamp + "&oauth_version=" + oauth_version; bsss = URLEncoder.encode(bsss, "utf-8" ); return bss + bsss; } |
有了base string就可以簽名生成oauth_signature這個參數(shù),oauth_signature會在請求request_token的時候用到。簽名算法是HMAC-SHA1,簽名的key就是最開始的consumer_secret后加一個&號,簽名算法代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public String hmacsha1(String data, String key) { byte [] byteHMAC = null ; try { Mac mac = Mac.getInstance( "HmacSHA1" ); SecretKeySpec spec = new SecretKeySpec(key.getBytes(), "HmacSHA1" ); mac.init(spec); byteHMAC = mac.doFinal(data.getBytes()); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException ignore) { } String oauth = new BASE64Encoder().encode(byteHMAC); return oauth; } |
里面用的的BASE64Encoder這個類可以Google一個。
得到oauth_signature后就要開始向 requst_token_url 發(fā)送請求了,OAuth規(guī)范定義了三種傳遞OAuth參數(shù)方式:
- httpheader中
- url中
- post form中
國內(nèi)各大微博的支持情況是:新浪Httpheader可用,網(wǎng)易Httpheader可用,騰訊只支持在url,搜狐由于沒有appkey所以還沒去嘗試。
如果使用Httpheader傳遞參數(shù)頭名為“Authorization”,值為下面的格式,將值改為自己應用的。
1
|
OAuth oauth_nonce="9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1272323047", oauth_consumer_key="GDdmIQH6jhtmLUypg82g", oauth_token="8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc", oauth_verifier="pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY", oauth_signature="PUw%2FdHA4fnlJYM6RhXk5IU%2F0fCc%3D", oauth_version="1.0" |
url和post form兩種方式的參數(shù)名和參數(shù)值也即上面的,完全一樣。
請求發(fā)送成功后就會得到的響應如下:
1
|
oauth_token=8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc&oauth_token_secret=x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA&oauth_callback_confirmed=true |
可以看到響應里面已經(jīng)包含oauth_token和oauth_token_secret了,存貯之以備后面使用。
二、用戶認證
拿到了oauth_token之后就需要用戶對此oauth_token授權,也即對你的應用授權,具體做法就是發(fā)送oauth_token到服務商并請求用戶對此oauth_token授權:實現(xiàn)方法為以oauth_token和oauth_callback為參數(shù)請求oauthorize_url,Servlet中的代碼如下:
resp.sendRedirect(oauthorize_url+"?oauth_token="+oauth_token+"&oauth_callback="+oauth_callback);
這是用戶就被帶到了應用授權頁面,并可以選擇是否對該應用授權。如果用戶授權之后就會被帶到oauth_callback 地址。同時如果需要服務商會給oauth_callback返會一個名為oauth_verifier的參數(shù)(此參數(shù)用于無法跳轉(zhuǎn)的桌面應用,不一定每個微博平臺都會返回),這時候我們的oauth_token已經(jīng)獲得用戶的授權了。
三、用oauth_token換取access_token
這一步跟第一步“獲取Request_token”基本相同,也是需要準備 base string 對其簽名,然后發(fā)送請求,可以參考第一步的代碼實現(xiàn):但是相應的參數(shù)有所不用,具體來講就是第一步組裝base string 時候8個部分中第二個部分中的url換為access_token_url 并去掉oauth_callback加上oauth_token(如果有oauth_verifier的話也需要一并加上),組裝好之后需要簽名以得到oauth_signature,本次簽名的方法跟上次一樣,但是key變?yōu)閏onsumer_secret和oauth_token_secret以&連接的串。
下來需要向access_token_url發(fā)送請求,請求參數(shù)包括base string 里的除了請求方法(POST或GET)和請求地址外的所有參數(shù)及其值和簽名后生成的oauth_signature。例子如下:
1
|
OAuth oauth_nonce="9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1272323047", oauth_consumer_key="GDdmIQH6jhtmLUypg82g", oauth_token="8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc", oauth_verifier="pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY", oauth_signature="PUw%2FdHA4fnlJYM6RhXk5IU%2F0fCc%3D", oauth_version="1.0"" |
請求成功會服務商就會返回oauth_token和oaut_token_secret,這里的oauth_token和oaut_token_secret就是真正訪問資源要用的access_token。
還需要說明的是以上過程只需要經(jīng)行一次,就是說你拿到的access_token是不會過期的,除非用戶手動將授權收回,因此作為access_token的oauth_token和oaut_token_secret要保存起來,以后訪問受限資源的時候可以直接使用。至于如何訪問受限資源,等以后有時間了再補上。