程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 面向Java Web應用程序的OpenID,第1部分

面向Java Web應用程序的OpenID,第1部分

編輯:關於JAVA

在Java Web應用程序中使用OpenID身份驗證

OpenID 是一套分散式身份驗證系統。通過 OpenID 我可以證明自己擁有類似 http://openid.jstevenperry.com/steve 這樣的 URL,而且可以使用經驗證的身份登錄任何支持 OpenID 的站點 — 比如 Google、Slashdot 或 Wordpress。OpenID 對終端用戶來說無疑是個不錯的工具。但是對 OpenID 的使用引發我產生這樣的想法:“如果使用 OpenID 為我給客戶編寫的基於 Java 的 Web 應用程序創建標准可靠的身份識別系統,會怎麼樣呢?”

在這個由兩部分組成的文章中,我將向您展示如何使用 openid4java 庫和知名的 OpenID 提供者 myOpenID 為基於 Java 的 Web 應用程序創建身份驗證系統。還將向您展示如何使用一個 OpenID 簡單注冊擴展(Simple Registration Extension)(SReg)接收用戶信息。

首先我將解釋什麼是 OpenID 並說明如何獲得自己的 OpenID。接下來,簡短地介紹 OpenID 身份驗證的運作方式。最後,概述使用 openid4java 執行 OpenID 身份驗證所需的步驟。在本文第 2 部分,您將了解如何創建自己的 OpenID 提供者。

我將通篇使用基於 Wicket 的 Java Web 應用程序,這是我專門為本文編寫的。您可以隨時下載應用程序 源代碼。另外,您可能希望看一下 openid4java 庫。

注意:本文重點介紹面向 Java Web 應用程序的 OpenID,不過 OpenID 在任何軟件架構模式中都有效。

OpenID 簡介

OpenID 是證明用戶擁有標識符的一種規范。現在,僅將標識符 看作惟一標識用戶的 String。如果您像我一樣,會擁有很多標識符或用戶名。我在 Facebook、Twitter 和因特網上的大量其他站點上都有用戶名。我經常嘗試使用同一個用戶名,但是這在我要注冊的每個新站點上都不可行。因此,我需要記住所有的用戶名及其對應的 Web 站點。這是一件很痛苦的事;我常常會用到 “忘記密碼?” 這一提示信息。如果有一種方法可以在所有站點使用同一個標識符,該有多好!

OpenID 恰恰可以解決這個問題。通過 OpenID,我可以聲明一個標識符,然後在采用 OpenID 協議的任意 Web 站點上使用它。最新統計(來自 OpenID Web 站點)顯示有 50,000 多個網站支持 OpenID,包括 Facebook、Yahoo!、Google 和 Twitter。

OpenID 身份驗證

OpenID 身份驗證是 OpenID 的核心,它包括三個主要概念:

OpenID 標識符:一個惟一標識用戶的文本字符串。

OpenID 依賴方(RP):一種在線資源(可能是一個 Web 站點,也可以是文件、圖像或想要進行訪問控制的任何資源),使用 OpenID 識別可以訪問它的對象。

OpenID 提供者(OP):一個站點,用戶可在該站點聲明 OpenID,隨後登錄並為任意 RP 驗證身份。

OpenID 基金會 是一個社團,該社團成員關注通過 OpenID 規范推進開源身份管理。

OpenID 如何運作?

假設有用戶嘗試訪問屬於 RP Web 站點的資源,且 RP 使用 OpenID。要訪問該資源,用戶必須以一種能被識別(規范化)為 OpenID 的形式呈現其 OpenID。OpenID 由 OP 的位置編碼。然後 RP 采用用戶標識符並將用戶重定向到 OP,此時 OP 會要求用戶證明其 ID 請求。

接下來簡要介紹一下 OpenID 規范的每個組成部分及其作用。

OpenID 標識符

OpenID 的核心部分當然是 OpenID 標識符。OpenID 標識符(或簡稱 “標識符”)是惟一標識用戶的可讀字符串。沒有兩個用戶擁有相同的 OpenID,這正是 OpenID 發揮作用的關鍵之處。通過遵循 OpenID 驗證規范 2.0 版 的規定,OpenID 依賴方能夠解碼(或 “規范化”)標識符以弄清如何驗證用戶身份。在 OpenID 的運作過程中,作為編寫代碼的開發人員,我們感興趣的是下面兩個標識符:

用戶提供的標識符

聲明的標識符

顧名思義,用戶提供的標識符是由用戶提供給 RP 的標識符。用戶提供的標識符必須被規范化 為聲明的標識符,這只是將用戶提供的標識符轉化為標准形式的一種別出心裁的說法。然後可使用聲明的標識符通過一個名為 discovery 的進程定位 OP,之後 OP 驗證該用戶身份。

OpenID 依賴方(RP)

RP 通常由用戶提供的標識符呈現,該標識符被規范化為聲明的標識符。用戶的浏覽器(“用戶代理”)將被重定向到 OP,這樣用戶便可以提供其密碼並得到身份驗證。

RP 不知道也不關心聲明的標識符是如何獲得驗證的;它只想知道 OP 是否成功地驗證了用戶身份。如果驗證成功,用戶代理(也可能是用戶的浏覽器)會被轉發到用戶正試圖訪問的安全資源中。如果用戶得不到驗證,RP 會拒絕任何訪問。

Open ID 提供者(OP)

OP(OpenID 提供者)負責發出標識符並執行用戶身份驗證。OP 還提供基於 Web 的 OpenID 管理。OP 收集並保留每個用戶的以下基本信息:

電子郵箱

全名

出生日期

郵編

國家

第一語言

當要求 OP 驗證聲明的標識符時,用戶的浏覽器直接轉到登錄頁面,用戶在該頁面輸入其密碼。此時的控制權在於 OP。如果用戶成功得到身份驗證,OP 會將浏覽器轉到 RP 指定的位置(在一個特殊的 “return-to” URL 中)。如果用戶不能進行身份驗證,他可能會收到來自 OP 的消息,指出身份驗證失敗(至少對於兩個流行的 OpenID 提供者 ClaimID 和 myOpenID 來說是這樣的)。

成為 OpenID 依賴方

現在我們了解了 OpenID 的主要組成部分,以及它們之間的協作方式。文章的其余部分將重點介紹如何使用開源 openid4java 庫編寫 OpenID 依賴方(RP)。

使用 OpenID 的第一步就是獲取一個標識符。這很簡單:只需轉到 myOpenID 並單擊 SIGN UP FOR AN OPENID 按鈕即可。選擇一個 OpenID,比如 redneckyogi 或 jstevenperry(順便提一下,兩個都是我的用戶名)。登錄窗體會告訴您所選用戶名是否已存在。如果不存在,系統將指導您輸入密碼、電子郵箱,並在 JChaptcha 格式的文本框中輸入一些文本(您不是一個機器人程序,對吧?)。

稍後,您會收到一封電子郵件,其中含有一個鏈接。單擊鏈接確認電子郵箱,然後 — 恭喜您!— 您現在擁有自己的 OpenID 了!

當然,隨著技術的不斷發展,會有更多的 OPenID 提供者可供選擇。

為表明獲取一個 OpenID 有多麼簡單快捷,我在大約 30 分鐘內用 myOpenID、Verisign 和 ClaimID 的帳戶進行了登錄。這個時間段也包括輸入詳細信息和上傳圖片所花費的時間。

您可能已經擁有 OpenID

據 OpenId.net統計,Google,Wordpress 和其他流行站點均支持 OpenID。如果您已經在這些站點上注冊,那麼您可能已經擁有一個 OpenID 了。

例如,如果您有一個 Yahoo! 帳戶,但是還希望有一個 OpenID(我就是這樣,我之前甚至不知道OpenID 是什麼)。登錄時您只需使用 Yahoo! ID 即可,Yahoo 是您的 OpenID 提供者。您使用 [email protected] 提供基於 Yahoo 的 OpenID,然後 RP 會要求 Yahoo 對您進行身份驗證(如果您運行本文附帶的示例應用程序,您實際上可以看到這個過程)。

關於示例應用程序

正如我在文章開始所講的,我使用 openid4java 編寫了 Java Web 應用程序來創建簡單的 OpenID 依賴方(RP)。這是個簡單的應用程序,您可以構建該應用程序(WAR 形式),將其放入 Tomcat,然後從本地機器上運行。示例應用程序集中關注以下幾步:

用戶在注冊頁面輸入其 OpenID。

應用程序驗證標識符(將用戶定向到其 OP 以進行登錄)

身份驗證成功之後,應用程序從 OP 獲取用戶的個人資料,然後將用戶定向到 Save 頁面,用戶可在此頁面審查並保存其個人信息。

Save 頁面上顯示的信息來自 OP。

我使用 Wicket 編寫了應用程序,是因為我真的很喜歡 Wicket。我試著盡量減少 Wicket 的 “footprint”,這樣在學習編寫 OpenID 依賴方時才不易受到擾亂。

示例應用程序的架構分為兩個職責范圍:

在 Wicket 中編寫的用戶界面

OpenID 身份驗證 — 使用 openid4java 庫

當然這兩個方面彼此交互,不過我再次嘗試減少重復部分使其更易於遵循 OpenID 規范,而不是因 Wicket 的細小部分而受到擾亂。

關於 openid4java 和示例應用程序代碼

OpenID 驗證規范 很復雜。如果您一直實現規范,您可能在編寫自己的實現時覺得很容易。不過我很懶。我不想做工作要求以外的工作以解決手頭的問題,這正是 openid4java 發揮作用的地方。openid4java 是 OpenID 驗證 規范的一個實現,它使得在編程中使用 OpenID 更簡單。

接下來的代碼顯示 openid4java API 如何調用 RP 以使用 OpenID。您可能會注意到,示例應用程序實際上需要很少的代碼來實現這個調用。openid4java 確實簡化了您的生活。

為減少示例應用程序中的 Wicket footprint,我分離出一段代碼,這段代碼將 openid4java 調用到自己的 Java 類內,這個 Java 類稱作 RegistrationService(位於 com.makotogroup.sample.model)。針對 openid4java API 的使用,該類包括 5 種方法:

getReturnToUrl() 在身份驗證成功之後返回浏覽器指向的 URL。

getConsumerManager() 用於獲取主 openid4java API 類的實例。該類處理示例 RP 應用程序執行身份驗證所需的所有代碼。

performDiscoveryOnUserSuppliedIdentifier() 顧名思義,它處理 discovery 進程中出現的潛在問題。

createOpenIdAuthRequest() 創建身份驗證所需的 AuthRequest 構造。

processReturn() 用於處理身份驗證請求的結果。

編寫 RP

身份驗證的目的是要用戶證明其身份。這樣做可以保護 Web 資源,使其免受惡意訪問者的攻擊。用戶證明了其身份之後,您決定是否要授予其訪問資源的權利(不過身份驗證不是本文的介紹范圍)。

本文的示例應用程序執行一個許多 Web 站點都常用的功能:用戶注冊。它假定用戶能證明其身份從而可以進行注冊。這是個簡單的前提,不過它表明了與 OP 的典型 “對話” 是如何進行的,且如何使用 openid4java 實現該對話。下面是一些基本步驟:

獲取用戶提供的標識符:RP 獲得用戶的 OpenID。

發現:RP 規范化用戶提供的標識符,以決定聯系哪個 OP 進行身份驗證,如何與其聯系。

關聯:並非必要步驟,不過是我強烈推薦的一步,在該步中,RP 和 OP 建立一個安全通信渠道。

身份驗證請求:RP 要求 OP 對用戶進行身份驗證。

驗證:RP 向 OP 請求用戶名驗證,並確保通信沒有受到干擾。

轉到應用程序:身份驗證之後,RP 為用戶指向其先前請求的資源。

接下來,我們將詳細分析這些步驟中的每一步,包括代碼例子。在我們逐步查看下面內容時,我將從頭到尾使用一個例子來闡述 OpenID 身份驗證過程。

獲取用戶提供的標識符

這是 RP 應用程序的任務。在工作示例中,用戶名是在應用程序的 OpenIdRegistrationPage 上獲取的。我輸入我的 OpenID 並單擊 Confirm OpenID 按鈕。示例應用程序(充當 RP)現在知道我的用戶提供標識符了。圖 1 顯示了運行中的示例應用程序的一幅截圖。

圖 1. 獲取用戶提供的標識符

在本例中,用戶提供的標識符是 redneckyogi.myopenid.com。

UI 代碼負責兩項工作:確保用戶在 Your OpenID 文本框中輸入了文本,且在用戶單擊 Confirm OpenID 按鈕時提交窗體。在確認之後,應用程序開始調用序列。清單 1 顯示了 OpenIdRegistrationPage 中提交窗格和執行調用序列所用的代碼。

清單 1. 使用 RegistrationService.java 執行 OpenID 身份驗證調用序列的 Wicket UI 代碼

Button confirmOpenIdButton = new Button("confirmOpenIdButton") {
 public void onSubmit() {
  String userSuppliedIdentifier = formModel.getOpenId();
  DiscoveryInformation discoveryInformation =
  RegistrationService.
   performDiscoveryOnUserSuppliedIdentifier(
    userSuppliedIdentifier);
  MakotoOpenIdAwareSession session =
   (MakotoOpenIdAwareSession)owningPage.getSession();
  session.setDiscoveryInformation(discoveryInformation, true);
  AuthRequest authRequest =
   RegistrationService.createOpenIdAuthRequest(
    discoveryInformation, returnToUrl);
  getRequestCycle().setRedirect(false);
  getResponse().redirect(authRequest.getDestinationUrl(true));
  }
};

試著不要受示例及其使用 Wicket UI 代碼的方式困擾(不過如果您很好奇,完全可以查看 OpenIdRegistrationPage.java,也就是清單 1 的來源)。這裡的重點是,當用戶單擊按鈕時,UI 代碼委托 RegistrationService 的各種方法來調用 openid4java 的 API,主要做三項工作(每一項都在清單 1 中用粗體表示):

在用戶提供的標識符上執行發現

創建用於生成身份驗證請求的 openid4java AuthRequest 對象

重定向浏覽器到 OpenID 提供者

重定向浏覽器之後,UI 代碼完成任務,現在控制權在 OP 手中。注意,myopenid.com 是標識符的一部分,且用戶提供的標識符不是結構良好的 URL。在標識符中仍然需要編碼足夠的信息,以允許 openid4java 規范化並執行發現。這將在下一部分介紹。

發現(discovery)

RP 采用用戶提供的標識符,並將其轉化為一種格式,可用於確定兩個內容:OpenID 提供者(OP)是誰,如何聯系 OP。

RP 使用發現過程來確定如何向 OP 發出請求,而關鍵便是用戶提供的標識符。但是,在將用戶提供的標識符用於發現之前,首先必須將其規范化。 openid4java 實際上已經承擔了規范化用戶提供標識符的工作,所以這裡無需再作詳細討論。

兩種不同的形式是:

XRI:可擴展資源標識符

URL:統一資源定位符

本文中我們將看一些 URL 示例。圖 1 中的用戶提供標識符是一個缺少模式的 URL,因此,作為規范化工作的一部分,openid4java 向其附加 “http://”,從而構成聲明的標識符 http://redneckyogi.myopenid.com。

聲明的標識符中的編碼信息包含 OP 的名稱,在本例中是 myOpenID。由於聲明的標識符是一個 URL,openid4java 知道如何聯系 OP — 在 http://myopenid.com上 — 這正是它所要做的。

清單 2(來自示例應用程序的 RegistrationService 類)顯示 RP 如何使用 openid4java 執行發現。

清單 2. 使用 openid4java 執行發現

public static
  DiscoveryInformation performDiscoveryOnUserSuppliedIdentifier(
   String userSuppliedIdentifier) {

 DiscoveryInformation ret = null;
 ConsumerManager consumerManager = getConsumerManager();
 try {
  // Perform discover on the User-Supplied Identifier
  List<DiscoveryInformation> discoveries =
   consumerManager.discover(userSuppliedIdentifier);
  // Pass the discoveries to the associate() method...
  ret = consumerManager.associate(discoveries);
 } catch (DiscoveryException e) {
  String message = "Error occurred during discovery!";
  log.error(message, e);
  throw new RuntimeException(message, e);
 }
 return ret;
}

openid4java 進行 OpenID 身份驗證所用的核心類是 ConsumerManager。openid4java 對於該類的使用有嚴格的准則。它將該類作為靜態類成員存儲並通過 getConsumerManager() 方法予以訪問(參見示例應用程序中的 RegistrationService.java 了解更多信息)。

openid4java 允許使用一行代碼(清單 2 中粗體部分)規范化用戶提供的標識符並執行發現。返回的是 DiscoveryInformation 對象的 java.util.List。可將這些對象看作不透明對象。一定要保留這些對象,因為當您的 RP 實現選擇構建與 OP 的關聯時,要用到它們(如示例應用程序)。

關聯

關聯是 RP 和 OP 建立共享密鑰(通過 Diffie-Hellman 密鑰交換)的一種方式,能使它們之間的交互更安全可信。關聯不是 OpenID 規范所必需的。關聯是從 RP 代碼中執行的,僅需調用 ConsumerManager 上的 associate() 方法即可,如清單 3 所示。

清單 3. 使用 openid4java 建立關聯

public static
  DiscoveryInformation performDiscoveryOnUserSuppliedIdentifier(
   String userSuppliedIdentifier) {

 DiscoveryInformation ret = null;
 ConsumerManager consumerManager = getConsumerManager();
 try {
  // Perform discover on the User-Supplied Identifier
  List<DiscoveryInformation> discoveries =
   consumerManager.discover(userSuppliedIdentifier);
  // Pass the discoveries to the associate() method...
  ret = consumerManager.associate(discoveries);
 } catch (DiscoveryException e) {
  String message = "Error occurred during discovery!";
  log.error(message, e);
  throw new RuntimeException(message, e);
 }
 return ret;
}

這種方法返回 DiscoveryInformation 對象,它用來描述發現的結果(您可將該對象看作不透明對象)。示例應用程序存儲一個 session 中的 DiscoveryInformation 對象,因為稍後會用到該對象。要發出身份驗證請求,就需要該對象,接下來我們將對此進行討論。

身份驗證

RP 在用戶提供的標識符上成功執行發現後,該到驗證用戶身份的時候了。ConsumerManager 需要建立一個稱作 AuthRequest 的特殊對象,OP 會使用該對象處理身份驗證請求。

在此次交互中,需要利用名為 SimpleRegistration(簡稱 SReg)的一個 OpenID 擴展;該擴展允許 RP 提出以下請求:在響應中返回 OP 用戶資料中的某些屬性。清單 4 顯示了建立 AuthRequest 對象和使用 SReg 請求屬性的代碼。

清單 4. 建立 AuthRequest 並使用 SReg 擴展

public static AuthRequest
createOpenIdAuthRequest(DiscoveryInformation
discoveryInformation, String returnToUrl) {
 AuthRequest ret = null;
 //
 try {
  // Create the AuthRequest object
  ret =
  getConsumerManager().authenticate(discoveryInformation,
    returnToUrl);
  // Create the Simple Registration Request
  SRegRequest sRegRequest =
SRegRequest.createFetchRequest();
  sRegRequest.addAttribute("email", false);
  sRegRequest.addAttribute("fullname", false);
  sRegRequest.addAttribute("dob", false);
  sRegRequest.addAttribute("postcode", false);
  ret.addExtension(sRegRequest);
 } catch (Exception e) {
  String message = "Exception occurred while building " +
           "AuthRequest object!";
  log.error(message, e);
  throw new RuntimeException(message, e);
 }
 return ret;
}

清單 4 中第一行粗體代碼顯示了對 ConsumerManager.authenticate() 的調用,它其實不執行身份驗證調用。它僅接受成功完成與 OP 的發現交互之後返回的 DiscoveryInformation 對象(參見 清單 3),以及身份驗證成功之後用戶代理(浏覽器)指向的 URL。

第二行粗體代碼顯示了如何通過對 SRegRequest.createFetchRequest() 的靜態方法調用創建 SReg 請求。然後通過對 SRegRequest 對象上 addAttribute() 的調用, 您需要的屬性作為簡單注冊擴展(Simple Registration Extension)的一部分從 OP 返回。最後,通過調用 addExtension() 將擴展添加到 AuthRequest 。

openid4java 使所有這些動作都很直觀。此時,浏覽器指向負責驗證用戶身份的 OpenID 提供者,用戶將在此頁面輸入其密碼。參見 OpenIdRegistrationPage.java 查看執行重定向的 Wicket UI 代碼。 圖 2 顯示了處理身份驗證請求的 myOpenID 服務器截圖。

圖 2. 處理身份驗證請求的 myOpenID

此時,您需要確保有代碼能處理運行於 URL 上的請求,該 URL 被指定為 “return-to” URL(參見 清單 4)。示例應用程序的 return-to URL 在 RegistrationService.getReturnToUrl() 中被硬編碼。OpenIdRegistrationSavePage 的構造函數破解 Web 請求以查明它是否從 OP 返回。如果該請求確實是從 OP 返回,它必須得到驗證。

驗證

清單 5 顯示的代碼用於查明一個請求是否來自 OP。如果是,將會有一個參數 is_return,該參數的值為 true。 如果情況是這樣的,那麼 openid4java 用於驗證請求(實際上是來自 OP 的響應)並取出 清單 4 中請求的屬性。

清單 5. 處理 return-to URL

public OpenIdRegistrationSavePage(PageParameters pageParameters) {
 RegistrationModel registrationModel = new RegistrationModel();
 if (!pageParameters.isEmpty()) {
  String isReturn = pageParameters.getString("is_return");
  if (isReturn.equals("true")) {
   MakotoOpenIdAwareSession session =
    MakotoOpenIdAwareSession)getSession();
   DiscoveryInformation discoveryInformation =
    session.getDiscoveryInformation();
   registrationModel =
    RegistrationService.processReturn(discoveryInformation,
     pageParameters,
     RegistrationService.getReturnToUrl());
   if (registrationModel == null) {
     error("Open ID Confirmation Failed.");
    }
   }
  }
  add(new OpenIdRegistrationInformationDisplayForm("form",
    registrationModel));
 }

在這段代碼中,Wicket 頁面的構造函數首先確定請求來自於 OP,是對先前身份驗證請求的響應。它使用一種定制的 Session 類(MakotoOpenIdAwareSession)抓取 DiscoveryInformation 對象,在成功完成與 OP 的發現交互之後,該對象被存儲。請求由 RegistrationService.processReturn() 方法使用 DiscoveryInformation 對象、請求參數和 return-to URL 得到驗證。如果請求驗證成功,會返回一個完全填充的 RegistrationModel 對象。這可以充當 OpenIdRegistrationSavePage 的 Wicket 模型,應用程序可在此繼續其預定作用。

轉到應用程序

如果對身份驗證的響應得到成功檢驗,用戶就有權通過 OpenID 訪問由 RP 保護的任何資源。在示例應用程序中,這是注冊過程。如果身份驗證成功,會跳出一個頁面,用戶可在此頁面審查來自 OP 的信息,並按需更改和保存信息。示例應用程序不包含真正保存注冊信息的代碼,不過有 hook。圖 3 顯示了我運行示例應用程序驗證我的 OpenID 時來自 OP 的信息。

Figure 3. 顯示來自 OP 的個人資料信息的示例應用程序

結束語

OpenID 用於解決大量的在線身份驗證問題,已經作為一種可靠的身份管理解決方案而被廣為接受。OpenID 的獲取很簡單,目前注冊的 OpenID 已經達到數百萬個。與任何其他規范一樣,OpenID 身份驗證 很復雜,不過 openid4java 極大地簡化了它。在本文中,您已經看到了 OpenID 身份驗證的運作方式。您也了解了使用 openid4java 將 OpenID 加入 Java Web 應用程序中有多麼簡單。

在本文第 2 部分,我們將著重介紹 OpenID 謎題的另外半部分:編寫 OpenID 提供者。這一部分的討論也是圍繞示例代碼展開的,使用專門為本文編寫的示例 Java Web 應用程序。同時,為在 Java Web 應用程序中實現 OpenID 身份驗證,請隨意使用 RegistrationService.java 上的代碼。

下載

描述 名字 大小 下載方法 OpenID 示例 openid4java-sample-app.zip 4.3 MB HTTP
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved