基于 OAuth 安全協議的 Java 應用編程

jopen 12年前發布 | 64K 次閱讀 OAuth Java OpenID/單點登錄SSO

OAuth 簡介

OAuth 是由 Blaine Cook、Chris Messina、Larry Halff 及 David Recordon 共同發起的,目的在于為 API 訪問授權提供一個安全、開放的標準。

基于 OAuth 認證授權具有以下特點:

  • 安全。OAuth 與別的授權方式不同之處在于:OAuth 的授權不會使消費方(Consumer)觸及到用戶的帳號信息(如用戶名與密碼),也是是說,消費方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權。
  • 開放。任何消費方都可以使用 OAuth 認證服務,任何服務提供方 (Service Provider) 都可以實現自身的 OAuth 認證服務。
  • 簡單。不管是消費方還是服務提供方,都很容易于理解與使用。

OAuth 的解決方案如下圖所示。


圖 1. OAuth Solution
基于 OAuth 安全協議的 Java 應用編程  

如圖 1 所示 OAuth 解決方案中用戶、消費方及其服務提供方之間的三角關系:當用戶需要 Consumer 為其提供某種服務時,該服務涉及到需要從服務提供方那里獲取該用戶的保護資源。OAuth 保證:只有在用戶顯式授權的情況下(步驟 4),消費方才可以獲取該用戶的資源,并用來服務于該用戶。

從宏觀層次來看,OAuth 按以下方式工作:

  1. 消費方與不同的服務提供方建立了關系。
  2. 消費方共享一個密碼短語或者是公鑰給服務提供方,服務提供方使用該公鑰來確認消費方的身份。
  3. 消費方根據服務提供方將用戶重定向到登錄頁面。
  4. 該用戶登錄后告訴服務提供方該消費方訪問他的保護資源是沒問題的。

OAuth 認證授權流程

在了解 OAuth 認證流程之前,我們先來了解一下 OAuth 協議的一些基本術語定義:

  • Consumer Key:消費方對于服務提供方的身份唯一標識。
  • Consumer Secret:用來確認消費方對于 Consumer Key 的擁有關系。
  • Request Token:獲得用戶授權的請求令牌,用于交換 Access Token。
  • Access Token:用于獲得用戶在服務提供方的受保護資源。
  • Token Secret:用來確認消費方對于令牌(Request Token 和 Access Token)的擁有關系。

圖 2. OAuth 授權流程(摘自 OAuth 規范)
基于 OAuth 安全協議的 Java 應用編程  

對于圖 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 聯系人
基于 OAuth 安全協議的 Java 應用編程  

在這里,Plaxo 承諾不會保存您在 Gmail 的密碼。

如果使用 OAuth 認證,情況是不同的,您不需要向網站 A(扮演 Consumer 角色)暴露您網站 B(扮演 Service Provider 角色)的用戶名、密碼信息。例如,您登錄 http://lab.madgex.com/oauth-net/googlecontacts/default.aspx 網站, 如圖 4 所示:


圖 4. 在 lab.madgex.com 獲得 GMail 聯系人
基于 OAuth 安全協議的 Java 應用編程  

點擊“Get my Google Contacts”,瀏覽器將會重定向到 Google,引導您登錄 Google,如圖 5 所示:


圖 5. 登錄 Google
基于 OAuth 安全協議的 Java 應用編程  

登錄成功后,將會看到圖 6 的信息:


圖 6. Google 對 lab.madgex.com 網站授權
基于 OAuth 安全協議的 Java 應用編程  

在您登錄 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 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
    基于 OAuth 安全協議的 Java 應用編程  

  • 當用戶點擊“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 安全協議的 Java 應用編程  

使用 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 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 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,推ter 等。本文以 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 應用。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!