首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 网站开发 > Web前端 >

面临 Java Web 应用程序的 OpenID

2012-10-09 
面向 Java Web 应用程序的 OpenID第 1 部分 在 Java Web 应用程序中使用 OpenID 身份验证(转载自)简介:?Op

面向 Java Web 应用程序的 OpenID

第 1 部分 在 Java Web 应用程序中使用 OpenID 身份验证(转载自)

简介:?OpenID 是一个分散式身份识别协议,能使用户更易于访问 Java? Web 应用程序中的资源。 在由两部分组成的文章的第 1 部分中,您将了解 OpenID 验证规范 以及在 Java 示例应用程序中加入该规范的步骤。作者 J. Steven Perry 没有手动实现 OpenID 验证规范,而是使用 openid4java 库和一个流行的 OpenID 提供者 myOpenID,为 Wicket 中编写的 Java 应用程序创建安全可靠的注册过程。

?

--------------===============================---------

?

内容

在本例中,用户提供的标识符是 redneckyogi.myopenid.com

UI 代码负责两项工作:确保用户在 Your OpenID 文本框中输入了文本,且在用户单击 Confirm OpenID 按钮时提交窗体。在确认之后,应用程序开始调用序列。清单 1 显示了 OpenIdRegistrationPage 中提交窗格和执行调用序列所用的代码。


清单 1. 使用 RegistrationService.java 执行 OpenID 身份验证调用序列的 Wicket UI 代码

清单 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
面临 Java Web 应用程序的 OpenID

此时,您需要确保有代码能处理运行于 URL 上的请求,该 URL 被指定为 “return-to” URL(参见 清单 4)。示例应用程序的 return-to URL 在 RegistrationService.getReturnToUrl() 中被硬编码。OpenIdRegistrationSavePage 的构造函数破解 Web 请求以查明它是否从 OP 返回。如果该请求确实是从 OP 返回,它必须得到验证。

描述名字大小下载方法

当用户单击这个链接时,执行清单 1 中的代码:


清单 1. 包含受保护资源的应用程序主页面

package com.makotogroup.sample.wicket;. . .public class OleMainPage extends WebPage {  public OleMainPage() {    add(new OleMainForm("form"));  }  public class OleMainForm extends Form {    public OleMainForm(String id) {      super(id);      add(new PageLink("openIdRegistrationPage", new IPageLink() {        public Page getPage() {          return new OpenIdRegistrationPage();        }        public Class<? extends WebPage> getPageIdentity() {          return OpenIdRegistrationPage.class;        }      }));    }  }} 

请注意清单 1 中的粗体代码。当用户单击图 1 所示的链接时,Wicket 把用户带到 OpenIdRegistrationPage(资源)。这时,调用链接的目的地,这会运行 OpenIdRegistrationPage 类的构造器。这个类有两个作用:

作为初始调用的入口点。 作为身份验证成功之后从 OP “回调” 的目标。

在发出初始调用以访问这个页面时,没有传递 Wicket PageParameters,RP 知道需要请求 OP 验证用户的身份。

RP 执行发现

为了在 RP 和 OP 之间通信,RP 必须对 OP 执行发现。从编程的角度来看,这很简单(同样是由于 openid4java 简化了编程),但这是一个重要的步骤,所以我把代码分解出来讨论一下。

RP 使用下面的代码(取自 OpenIdRegistrationPage 的构造器)发送发现请求:

  DiscoveryInformation discoveryInformation =    RegistrationService.performDiscoveryOnUserSuppliedIdentifier(          OpenIdProviderService.getOpEndpointUrl());

在这段代码中,RP 做两件事:

    对 OP 的端点 URL 执行发现。 把本身与 OP 关联起来。(对 Diffie-Hellman 密钥交换和关联期间发生的其他活动的详细解释参见 第 1 部分。)

接下来,由 OP 处理 RP 的发现请求。

OP 响应发现请求

请记住,在示例应用程序的 RP 和 OP 端都运行 openid4java。因此,在发现 OP 的过程中,openid4java 的 RP 端向 OP 的端点 URL 发送一个空的请求。端点 URL 是联系 OP 的位置,OP 在这里接收所有来自 RP 的请求。OP 必须处理这个请求。看一下 OpenIdProviderService.getOpEndpointUrl(),会注意到端点 URL 是 http://localhost:8080/openid-provider-sample-app/sample/OpenIdLoginPage。

当 RP 向 OP 发送空的请求时,Wicket 构造 OpenIdLoginPage 并运行它的构造器,见清单 2:


清单 2. OP 入口点

  public OpenIdLoginPage(PageParameters parameters) throws IOException {    super(parameters);    if (parameters.isEmpty()) {      // Empty request. Assume discovery request...      OpenIdProviderService.sendDiscoveryResponse (getResponse());  . . .

注意,如果 OP 接收到空的请求,它会假设这是发现请求。然后,它创建一个 XRDS 文档并发送回请求者。

清单 3 给出 sendDiscoveryRequest() 的代码:


清单 3. 发送对发现请求的响应

  public static void sendDiscoveryResponse (Response response) throws IOException {    //    response.setContentType("application/xrds+xml");    OutputStream outputStream = response.getOutputStream();    String xrdsResponse = OpenIdProviderService.createXrdsResponse();    //    outputStream.write(xrdsResponse.getBytes());    outputStream.close();  }

这个 XRDS 文档对于 openid4java 的 RP 端的正确运行很重要。为了简短,本文不讨论这个文档的细节;请通过下载 示例应用程序源代码 了解细节。

当 RP 收到 OP 发送的 XRDS 文档时,它知道它已经联系到了这个用户的 OP。然后,RP 创建身份验证请求并发送给 OP。

RP 请求验证用户的身份

RP 请求 OP 确认是否可以验证用户的身份。它执行的一系列调用见清单 4(取自构造器):


清单 4. RP 代码把身份验证委托给 OP

  DiscoveryInformation discoveryInformation =    RegistrationService.performDiscoveryOnUserSuppliedIdentifier(          OpenIdProviderService.getOpEndpointUrl());  MakotoOpenIdAwareSession session =    (MakotoOpenIdAwareSession)getSession();  session.setDiscoveryInformation(discoveryInformation, true);  AuthRequest authRequest =    RegistrationService.createOpenIdAuthRequest(          discoveryInformation,           RegistrationService.getReturnToUrl());  getRequestCycle().setRedirect(false);  getResponse().redirect(authRequest.getDestinationUrl(true)); 

首先,RP 通过端点 URL 联系 OP。这个调用可能看起来有点儿奇怪,但是请记住,在这个场景中应用程序集群使用一个可信的伙伴作为 OP。从 RP 的角度来看,验证用户提供的身份只需发现 OP 的位置,让 openid4java 构造后续交互所需的对象。OP 负责处理身份验证机制。

接下来,获取当前的 Wicket Session,把从 openid4java 获取的 DiscoveryInformation 存储起来供以后使用。我编写了一个特殊的 Session 子类 MakotoOpenIdAwareSession,这样便于在 Session 中存储 openid4java 对象。

然后,用从 openid4java 获取的 DiscoveryInformation 对象创建身份验证请求。这个对象告诉 Wicket 重定向到哪里以执行身份验证调用。

这些步骤与第 1 部分中的步骤相同。我在这里重复解释它们是因为本文使用的示例应用程序架构与第 1 部分的代码不太一样。我还希望您查看 OP 端的 API 调用,能够把它们联系在一起。

现在,RP 等待 OP 发送身份验证响应。在讨论下一个步骤之前,我们先看一下 Attribute Exchange 在用户身份验证中的作用。

OpenID Attribute Exchange 扩展

在第 1 部分中,我们简要讨论了 Simple Registration (SReg) 扩展,可以用 SReg 在 RP 和 OP 之间交换特定的信息集(由 SReg 规范定义)。看一下本文示例应用程序中的 createOpenIdAuthRequest() 方法,会注意到 RP 使用另一个扩展 OpenID Attribute Exchange (AX) 向 OP 请求信息。

与 SReg 扩展一样,OpenID Attribute Exchange (AX) 用于在 RP 和 OP 之间以一致的标准的方式交换信息。但是与 SReg 不同,AX 允许 OpenID 依赖方和提供者交换不受限制的信息,只要 RP 和 OP 都支持 AX 扩展。

简单地说,RP 通过消息请求 OP 提供特定的信息,OP 在消息中发送回这些信息。这些消息编码在浏览器重定向到的 URL 中,但是 openid4java 使用对象让代码可以使用这些信息。

RP 使用 FetchRequest 类发出 AX 请求。得到消息对象的引用之后,添加它希望从 OP 返回的属性,见清单 5:


清单 5. 包含属性的 RP FetchRequest

AuthRequest ret = obtainSomehow();// Create AX request to get favorite colorFetchRequest fetchRequest = FetchRequest.createFetchRequest();fetchRequest.addAttribute("favoriteColor",       "http://makotogroup.com/schema/1.0/favoriteColor",         false); ret.addExtension(fetchRequest);

当 OP 把信息发送回 RP 时,使用相同的构造,见清单 6:


清单 6. OP 发送回请求的属性

if (authRequest.hasExtension(AxMessage.OPENID_NS_AX)) {  MessageExtension extensionRequestObject =     authRequest.getExtension(AxMessage.OPENID_NS_AX);  FetchResponse fetchResponse = null;  Map<String, String> axData = new HashMap<String, String>();  if (extensionRequestObject instanceof FetchRequest) {   FetchRequest axRequest = (FetchRequest)extensionRequestObject;    ParameterList parameters = axRequest.getParameters();    fetchResponse = FetchResponse.createFetchResponse(        axRequest, axData);    if (parameters.hasParameter("type.favoriteColor")) {       axData.put("favoriteColor", registrationModel.getFavoriteColor());      fetchResponse.addAttribute("favoriteColor",          "http://makotogroup.com/schema/1.0/favoriteColor",          registrationModel.getFavoriteColor());    }      authResponse.addExtension(fetchResponse);  } else {    // ERROR  }}

定义的每个属性有一个简单的名称和相关联的 URI。在这里,属性的简单名称是 FavoriteColor,它的 URI 是 http://makotogroup.com/schema/1.0/favoriteColor。

另外,属性必须能够转换为字符串 (以这种方式发送数据字段的示例见示例应用程序)。在定义要在 RP 和 OP 之间交换的属性时,两端对于属性的定义必须一致;除此之外,没有任何限制!

现在,讨论下一个应用程序交互步骤。

OP 验证用户的身份

在上一步中,身份验证请求已经到达了 OP 的端点 URL。接下来,OP 分解请求以决定后续操作。OP 打开请求,获取它的模式,模式可能是关联或身份验证。


清单 7. OP 处理关联请求

//From (OpenIdLoginPage's constructor):public OpenIdLoginPage(PageParameters parameters) throws IOException {    super(parameters);    . . .    if ("associate".equals(mode)) {        OpenIdProviderService.processAssociationRequest(getResponse(), requestParameters);      }    . . .}//From (OpenIdProviderService):  public static void processAssociationRequest(Response response, ParameterList request)        throws IOException {    Message message = getServerManager().associationResponse(request);    sendPlainTextResponse(response, message);  }  private static void sendPlainTextResponse(Response response, Message message)        throws IOException {    response.setContentType("text/plain");    OutputStream os = response.getOutputStream();    os.write(message.keyValueFormEncoding().getBytes());    os.close();  }

在清单 7 中,OpenIdLoginPage 的构造器(示例应用程序中 OP 的入口点)首先分解请求。模式表明这是一个关联请求,所以它把关联机制委托给 openid4java,openid4java 的代码包装在 OpenIdProviderService.java 中。把关联响应发送回 RP。

RP 确认已经建立关联之后(实际上是 openid4java 确认之后),RP 向 OP 发送另一个调用。OP 再次分解并处理请求。大多数情况下,这是一个 checkid_authentication 请求。

清单 8 给出 OpenIdLoginPage 构造器中的代码:


清单 8. openid4java 分解 checkid_authentication 请求

  public OpenIdLoginPage(PageParameters parameters) throws IOException {    super(parameters);     . . .      else if ("checkid_immediate".equals(mode)      ||       "checkid_setup".equals(mode)      ||       "check_authentication".equals(mode)) {        if (((MakotoOpenIdAwareSession)getSession()).isLoggedIn()) {          // Create AuthResponse from session variables...         sendSuccessfulResponse();        }    add(new OpenIdLoginForm("form"));    . .   }
</table

热点排行