OAuth 協議致力於使網站和應用程序(統稱為消費方 Consumer)能夠在無須用戶透露其認證信息的情況下,通過 API 訪問該用戶在服務提供方(Service Provider)那裡的受保護資源。更一般地說,OAuth 為 API 認證提供了一個可自由實現且通用的方法。目前互聯網很多服務如 Open API 等都提供了 OAuth 認證服務,OAuth 標准也逐漸成為開放資源授權的標准。本文討論如何使用 Google Code 上提供的 OAuth Java 庫來實現基於 OAuth 認證的 Java 應用,並結合 Google 的 Data Service,給出使用 OAuth 方式訪問 Google Data 的例子。
OAuth 簡介
OAuth 是由 Blaine Cook、Chris Messina、Larry Halff 及 David Recordon 共同發起的,目的在於為 API 訪問授權提供一個安全、開放的標准。
基於 OAuth 認證授權具有以下特點:
安全。OAuth 與別的授權方式不同之處在於:OAuth 的授權不會使消費方(Consumer)觸及到用戶的帳號信息(如用戶名與密碼),也是是說,消費方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權。
開放。任何消費方都可以使用 OAuth 認證服務,任何服務提供方 (Service Provider) 都可以實現自身的 OAuth 認證服務。
簡單。不管是消費方還是服務提供方,都很容易於理解與使用。
OAuth 的解決方案如下圖所示。
圖 1. OAuth Solution
如圖 1 所示 OAuth 解決方案中用戶、消費方及其服務提供方之間的三角關系:當用戶需要 Consumer 為其提供某種服務時,該服務涉及到需要從服務提供方那裡獲取該用戶的保護資源。OAuth 保證:只有在用戶顯式授權的情況下(步驟 4),消費方才可以獲取該用戶的資源,並用來服務於該用戶。
從宏觀層次來看,OAuth 按以下方式工作:
消費方與不同的服務提供方建立了關系。
消費方共享一個密碼短語或者是公鑰給服務提供方,服務提供方使用該公鑰來確認消費方的身份。
消費方根據服務提供方將用戶重定向到登錄頁面。
該用戶登錄後告訴服務提供方該消費方訪問他的保護資源是沒問題的。
OAuth 認證授權流程
在了解 OAuth 認證流程之前,我們先來了解一下 OAuth 協議的一些基本術語定義:
Consumer Key:消費方對於服務提供方的身份唯一標識。
Consumer Secret:用來確認消費方對於 Consumer Key 的擁有關系。
Request Token:獲得用戶授權的請求令牌,用於交換 Access Token。
Access Token:用於獲得用戶在服務提供方的受保護資源。
Token Secret:用來確認消費方對於令牌(Request Token 和 Access Token)的擁有關系。
圖 2. OAuth 授權流程(摘自 OAuth 規范)
對於圖 2 具體每一執行步驟,解釋如下:
消費方向 OAuth 服務提供方請求未授權的 Request Token。
OAuth 服務提供方在驗證了消費方的合法請求後,向其頒發未經用戶授權的 Request Token 及其相對應的 Token Secret。
消費方使用得到的 Request Token,通過 URL 引導用戶到服務提供方那裡,這一步應該是浏覽器的行為。接下來,用戶可以通過輸入在服務提供方的用戶名 / 密碼信息,授權該請求。一旦授權成功,轉到下一步。
服務提供方通過 URL 引導用戶重新回到消費方那裡,這一步也是浏覽器的行為。
在獲得授權的 Request Token 後,消費方使用授權的 Request Token 從服務提供方那裡換取 Access Token。
OAuth 服務提供方同意消費方的請求,並向其頒發 Access Token 及其對應的 Token Secret。
消費方使用上一步返回的 Access Token 訪問用戶授權的資源。
總的來講,在 OAuth 的技術體系裡,服務提供方需要提供如下基本的功能:
第 1、實現三個 Service endpoints,即:提供用於獲取未授權的 Request Token 服務地址,獲取用戶授權的 Request Token 服務地址,以及使用授權的 Request Token 換取 Access Token 的服務地址。
第 2、提供基於 Form 的用戶認證,以便於用戶可以登錄服務提供方做出授權。
第 3、授權的管理,比如用戶可以在任何時候撤銷已經做出的授權。
而對於消費方而言,需要如下的基本功能:
第 1、從服務提供方獲取 Customer Key/Customer Secret。
第 2、提供與服務提供方之間基於 HTTP 的通信機制,以換取相關的令牌。
我們具體來看一個使用 OAuth 認證的例子。
在傳統的網站應用中,如果您想在網站 A 導入網站 B 的聯系人列表,需要在網站 A 輸入您網站 B 的用戶名、密碼信息。例如,您登陸 Plaxo (https://www.plaxo.com ),一個聯系人管理網站,當您想把 GMail 的聯系人列表導入到 Plaxo,您需要輸入您的 GMail 用戶名 / 密碼,如圖 3 所示:
圖 3. 在 Plaxo 獲得 GMail 聯系人
在這裡,Plaxo 承諾不會保存您在 Gmail 的密碼。
如果使用 OAuth 認證,情況是不同的,您不需要向網站 A(扮演 Consumer 角色)暴露您網站 B(扮演 Service Provider 角色)的用戶名、密碼信息。例如,您登錄 http://lab.madgex.com/oauth-net/googlecontacts/default.aspx 網站, 如圖 4 所示:
圖 4. 在 lab.madgex.com 獲得 GMail 聯系人
點擊“Get my Google Contacts”,浏覽器將會重定向到 Google,引導您登錄 Google,如圖 5 所示:
圖 5. 登錄 Google
登錄成功後,將會看到圖 6 的信息:
圖 6. Google 對 lab.madgex.com 網站授權
在您登錄 Google,點擊“Grant access”,授權 lab.madgex.com 後,lab.madgex.com 就能獲得您在 Google 的聯系人列表。
在上面的的例子中,網站 lab.madgex.com 扮演著 Consumer 的角色,而 Google 是 Service Provider,lab.madgex.com 使用基於 OAuth 的認證方式從 Google 獲得聯系人列表。
下一節,本文會給出一個消費方實現的例子,通過 OAuth 機制請求 Google Service Provider 的 OAuth Access Token,並使用該 Access Token 訪問用戶的在 Google 上的日歷信息 (Calendar)。
示例
准備工作
作為消費方,首先需要訪問 https://www.google.com/accounts/ManageDomains,從 Google 那裡獲得標志我們身份的 Customer Key 及其 Customer Secret。另外,您可以生成自己的自簽名 X509 數字證書,並且把證書上傳給 Google,Google 將會使用證書的公鑰來驗證任何來自您的請求。
具體的操作步驟,請讀者參考 Google 的說明文檔:http://code.google.com/apis/gdata/articles/oauth.html。
在您完成這些工作,您將會得到 OAuth Consumer Key 及其 OAuth Consumer Secret,用於我們下面的開發工作。
如何獲得 OAuth Access Token
以下的代碼是基於 Google Code 上提供的 OAuth Java 庫進行開發的,讀者可以從 http://oauth.googlecode.com/svn/code/java/core/ 下載獲得。
指定 Request Token URL,User Authorization URL,以及 Access Token URL,構造 OAuthServiceProvider 對象:
OAuthServiceProvider serviceProvider = new OAuthServiceProvider(
"https://www.google.com/accounts/OAuthGetRequestToken",
"https://www.google.com/accounts/OAuthAuthorizeToken",
"https://www.google.com/accounts/OAuthGetAccessToken");
指定 Customer Key,Customer Secret 以及 OAuthServiceProvider,構造 OAuthConsumer 對象:
OAuthConsumer oauthConsumer = new OAuthConsumer(null
, "www.example.com"
, "hIsGkM+T4+90fKNesTtJq8Gs"
, serviceProvider);
為 OAuthConsumer 指定簽名方法,以及提供您自簽名 X509 數字證書的 private key。
oauthConsumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1);
oauthConsumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey);
由 OAuthConsumer 對象生成相應的 OAuthAccessor 對象:
accessor = new OAuthAccessor(consumer);
指定您想要訪問的 Google 服務,在這裡我們使用的是 Calendar 服務:
Collection<? extends Map.Entry> parameters
= OAuth.newList("scope","http://www.google.com/calendar/feeds/");
通過 OAuthClient 獲得 Request Token:
OAuthMessage response = getOAuthClient().getRequestTokenResponse(
accessor, null, parameters);
使用 Request Token, 將用戶重定向到授權頁面,如圖 7 所示:
圖 7. OAuth User Authorization
當用戶點擊“Grant access”按鈕,完成授權後,再次通過 OAuthClient 獲得 Access Token:
oauthClient.getAccessToken(accessor, null, null);
在上述步驟成功完成後,Access Token 將保存在 accessor 對象的 accessToken 成員變量裡。查看您的 Google Account 安全管理頁面,可以看到您授權的所有消費方,如圖 8 所示。
圖 8. Authorized OAuth Access to your Google Account
使用 OAuth Access Token 訪問 Google 服務
接下來,我們使用上一節獲得的 Access Token 設置 Google Service 的 OAuth 認證參數,然後從 Google Service 獲取該用戶的 Calendar 信息:
OAuthParameters para = new OAuthParameters();
para.setOAuthConsumerKey("www.example.com");
para.setOAuthToken(accessToken);
googleService.setOAuthCredentials(para, signer);
清單 1 是完整的示例代碼,供讀者參考。
清單 1. 基於 OAuth 認證的 Google Service 消費方實現
import java.util.Collection;
import java.util.Map;
import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.client.OAuthClient;
public class DesktopClient {
private final OAuthAccessor accessor;
private OAuthClient oauthClient = null;
public DesktopClient(OAuthConsumer consumer) {
accessor = new OAuthAccessor(consumer);
}
public OAuthClient getOAuthClient() {
return oauthClient;
}
public void setOAuthClient(OAuthClient client) {
this.oauthClient = client;
}
//get the OAuth access token.
public String getAccessToken(String httpMethod,
Collection<? extends Map.Entry> parameters) throws Exception {
getOAuthClient().getRequestTokenResponse(accessor, null,parameters);
String authorizationURL = OAuth.addParameters(
accessor.consumer.serviceProvider.userAuthorizationURL,
OAuth.OAUTH_TOKEN, accessor.requestToken);
//Launch the browser and redirects user to authorization URL
Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler "
+ authorizationURL);
//wait for user's authorization
System.out.println("Please authorize your OAuth request token. "
+ "Once that is complete, press any key to continue...");
System.in.read();
oauthClient.getAccessToken(accessor, null, null);
return accessor.accessToken;
}
}
import java.net.URL;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Collection;
import java.util.Map;
import com.google.gdata.client.GoogleService;
import com.google.gdata.client.authn.oauth.OAuthParameters;
import com.google.gdata.client.authn.oauth.OAuthRsaSha1Signer;
import com.google.gdata.client.authn.oauth.OAuthSigner;
import com.google.gdata.data.BaseEntry;
import com.google.gdata.data.BaseFeed;
import com.google.gdata.data.Feed;
import net.oauth.OAuth;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.OAuthServiceProvider;
import net.oauth.client.OAuthClient;
import net.oauth.client.httpclient4.HttpClient4;
import net.oauth.example.desktop.MyGoogleService;
import net.oauth.signature.OAuthSignatureMethod;
import net.oauth.signature.RSA_SHA1;
public class GoogleOAuthExample {
//Note, use the private key of your self-signed X509 certificate.
private static final String PRIVATE_KEY = "XXXXXXXX";
public static void main(String[] args) throws Exception {
KeyFactory fac = KeyFactory.getInstance("RSA");
//PRIVATE_KEY is the private key of your self-signed X509 certificate.
EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(
OAuthSignatureMethod.decodeBase64(PRIVATE_KEY));
fac = KeyFactory.getInstance("RSA");
PrivateKey privateKey = fac.generatePrivate(privKeySpec);
OAuthServiceProvider serviceProvider = new OAuthServiceProvider(
//used for obtaining a request token
//"https://www.google.com/accounts/OAuthGetRequestToken",
//used for authorizing the request token
"https://www.google.com/accounts/OAuthAuthorizeToken",
//used for upgrading to an access token
"https://www.google.com/accounts/OAuthGetAccessToken");
OAuthConsumer oauthConsumer = new OAuthConsumer(null
, "lszhy.weebly.com" //consumer key
, "hIsGnM+T4+86fKNesUtJq7Gs" //consumer secret
, serviceProvider);
oauthConsumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1);
oauthConsumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey);
DesktopClient client = new DesktopClient(oauthConsumer);
client.setOAuthClient(new OAuthClient(new HttpClient4()));
Collection<? extends Map.Entry> parameters =
OAuth.newList("scope","http://www.google.com/calendar/feeds/");
String accessToken = client.getAccessToken(OAuthMessage.GET,parameters);
//Make an OAuth authorized request to Google
// Initialize the variables needed to make the request
URL feedUrl = new URL(
"http://www.google.com/calendar/feeds/default/allcalendars/full");
System.out.println("Sending request to " + feedUrl.toString());
System.out.println();
GoogleService googleService = new GoogleService("cl", "oauth-sample-app");
OAuthSigner signer = new OAuthRsaSha1Signer(MyGoogleService.PRIVATE_KEY);
// Set the OAuth credentials which were obtained from the step above.
OAuthParameters para = new OAuthParameters();
para.setOAuthConsumerKey("lszhy.weebly.com");
para.setOAuthToken(accessToken);
googleService.setOAuthCredentials(para, signer);
// Make the request to Google
BaseFeed resultFeed = googleService.getFeed(feedUrl, Feed.class);
System.out.println("Response Data:");
System.out.println("==========================================");
System.out.println("|TITLE: " + resultFeed.getTitle().getPlainText());
if (resultFeed.getEntries().size() == 0) {
System.out.println("|\tNo entries found.");
} else {
for (int i = 0; i < resultFeed.getEntries().size(); i++) {
BaseEntry entry = (BaseEntry) resultFeed.getEntries().get(i);
System.out.println("|\t" + (i + 1) + ": "
+ entry.getTitle().getPlainText());
}
}
System.out.println("==========================================");
}
}
小結
OAuth 協議作為一種開放的,基於用戶登錄的授權認證方式,目前互聯網很多 Open API 都對 OAuth 提供了支持,這包括 Google, Yahoo,Twitter 等。本文以 Google 為例子,介紹了 Java 桌面程序如何開發 OAuth 認證應用。在開發桌面應用訪問 Web 資源這樣一類程序時,一般通行的步驟是:使用 OAuth 做認證,然後使用獲得的 OAuth Access Token,通過 REST API 訪問用戶在服務提供方的資源。
事實上,目前 OAuth 正通過許多實現(包括針對 Java、C#、Objective-C、Perl、PHP 及 Ruby 語言的實現)獲得巨大的動力。大部分實現都由 OAuth 項目維護並放在 Google 代碼庫 (http://oauth.googlecode.com/svn/) 上。開發者可以利用這些 OAuth 類庫編寫自己需要的 OAuth 應用。