OpenID使用手册

摘要:

OpenID是一种开放、离散式的用于用户数字标识的开源框架。在网络应用日益充斥的今天,作为终端用户的我们不得不在每个网站上设置帐号,并管理众多的帐号。而采用OpenID技术的话,你就无须再管理这些相互独立的帐号,而是通过认证服务器管理自己唯一的身份标识。本文将详细解释OpenID技术框架以及如何使用Java进行开发。

作者:cleverpig
什么是OpenID?

OpenID是一种开放、离散式的用于用户数字标识的开源框架。

请让我们思考自己所拥有的在线帐号种类:博客、wiki、to-do list、个人相册。在网络应用日益充斥的今天,这些个人在线帐号可谓不胜枚举,而对帐号的需要也同样无处不在,乃至当我们想在好友博客上进行评论时都需要注册成为该博客系统的用户。于是作为终端用户,我们不得不在每个网站上设置帐号,并管理众多的帐号。而采用OpenID技术的话,你就无须再管理这些相互独立的帐号,而是通过认证服务器管理自己唯一的身份标识。

OpenID常见的应用场景:某用户试图登录一个外部网站。与提交用户名和密码的方式不同,他只提交了属于自己的一个URL,例如:http://johnsmith.example.com/

这个URL即指向了用户的OpenID认证服务器,同时又是用户的身份标识。因此外部网站使用此URL便可以找到用户的认证服务器,并询问认证服务器:“该用户声称他拥有此URL。而这个URL说明了你负责认证工作,那么请告诉我,该用户能否访问我的站点?”。认证服务器将提示用户登入认证系统,并询问用户是否允许和外部网站进行认证。如果用户同意的话,那么认证服务器将通知外部网站——用户已经通过认证。在上面,我们通过拟人化的表达方式来形象生动地诠释整个认证请求/回应过程。

用户可以使用同一个URL用作在任何支持OpenID认证的外部网站中使用的标识。这正是OpenID与其它传统认证方式的最大不同。通过使用URL,可以使外部站点非常容易地获取到负责认证工作的服务器位置。而只有认证服务器才需要输入密码来验证用户身份。其它希望验证用户身份的站点都将询问用户所注册的认证服务器。如果你正在使用支持OpenID的门户站点(比如AOL),那么你就可以使用现成的AOL即时消息登录帐号来登录AOL站点,而无需另外注册。因此,我们可以猜想Google和Yahoo也许已经开始着手建造他们的OpenID服务。

你一定想知道OpenID是如何实现分散化服务的?由于用户具有选择OpenID服务提供者的权利,因此你会在最初选择AOL作为OpenID提供者,而过一段时间后,可能觉得希望更换到另外一个OpenID提供者,此时你所需要做的就是修改以下的HTML标签:

<link rel="openid.server" href="http://openid.example.com/">

保存这些link元数据的最常见位置就是个人站点(比如博客)的根页面。

如何使用OpenID?

OpenID绝妙地解决了多个帐号同步问题,但并不仅仅如此。例如,你可以利用它建立跨应用、跨域的单点登录(Single sign-on)。如果你使用同一个OpenID登入了博客和个人相册,那么你只需要在登录过程中进行一次认证。对于在此之后的每个需要登录的应用(在同一个session周期)只需提供OpenID,而不是传统的用户名和密码。

大多数OpenID提供者也提供了支持多个配置的功能。这样你就可以使用“Bob Smith”登录博客,而使用“Robert J Smith”登录企业wiki。随着OpenID提供者的日益成熟和OpenID功能上的提升,我们不久就会使用对来自伙伴公司OpenID认证服务器主机名的用户进行认证的服务。

哪些网站支持OpenID?

OpenID技术出现不久,便获得了在众多公共消费站点的热捧:DiggSix ApartZoomrAOL。其中AOL为老用户提供了OpenID支持,使得六千五百万的登录用户在一日之内就全部能够使用OpenID。目前已经具有超过九千五百万的用户能够使用OpenID登录系统,并且每天都有25至50个站点加入到支持OpenID规范的队伍中。另外,OpenID增加了对Firefox3和微软Windows Vista的支持。

下面是实现了OpenID代码库的语言列表:
        • C#
        • C++
        • Java
        • Perl
        • Python
        • Ruby
        • PHP
        • Coldfusion

OpenID社区维护了这些代码库的清单:http://openid.net/wiki/index.php/Libraries。在本文的后面,我们将讨论到OpenID的Java实现:OpenID4Java(http://www.openid4java.org)。

OpenID协议综述

OpenID协议非常易于扩展,下面的图表展示了OpenID2.0草案的基本工作流程。它展示了在终端用户、Relying Party站点(一个示例站点)和OpenID服务提供者之间的交互过程(最常见的认证流程)。
image

用户登入外部站点的过程主要分为以下七个步骤:

1. Relying Party站点请求用户标识

此步骤非常简单:用户提供一个字符串(以URL或者XRI格式)给外部站点,使后者能够识别用户。

        1.外部站点请求用户发送标识。通常使用带有Open图标的文本输入框、用于提交信息的按钮组成的form来完成此功能。为了方便起见,文本输入框的name属性应为openid_identifier,这样以便浏览器自动将其识别为一个OpenID登录form。
        2.用户输入标识,标识可能采用下面的形式:
        • URI/URL (通过http或者https)
        • XRI。XRI是一种广义的、分散式的URI。它能用于任何使用URI的地方。XRI主要采用以下形式:xri://authority/path?query#fragment。了解更多关于XRI的信息请看:XRI语法规范

用户标识类似这个样子:http://myname.myhost.com/。外部站点经常将OpenID logo放置到其登录form上,这样可以使你很快地辨别出是否使用OpenID。
image
用户在点击位于外部站点登录页面上的“Login”按钮后便启动了认证过程。

2.“标准化”: Relying Party站点整理用户标识

用户输入了标识后,此标识便由外部站点进行整理(标准化)。由于标识可能使用XRI或者URI格式,因此整理的过程非常复杂:
        1.如果标识以xri://、xri://$ip或者xri://$dns*开头,那么我们要去掉这些头部标记。
        2.如果余下字符串中的第一个字符是XRI的全局上下文符号(=、@、+、 $、!),那么此字符串为标准的XRI标识。否则,将被视为HTTP URL(如果http/https前缀没有定义,我们需要为其添加上http://)。当然,URL必须遵守URL命名规范。最终获得的URL就是一个标准的URL标识。

下面是一些示例:
image

下面的流程图描绘了标准化处理过程:
image

3.“发现”: Relying Party站点查询与OpenID服务器进行通讯的方式

外部站点使用标准化的标识来查询用于发起请求所必须的信息。对于“发现”阶段来讲,其使用的解析协议和获取的结果文档都取决于在标准化阶段决定的标识类型。这正是OpenID2.0规范的特殊之处。
image

快速参考:

        • XRI 解析:类似通过UDP将主机名解析为IP的DNS协议;它将XRI转换为XRDS。其目的是提供一种将厚重、通用的XRI格式转换为真实可用的描述符。

        • YADIS协议:将URL连接到XRDS上。这是一种非常简单的协议,它将当前的HTTP或者HTTPS URL直接指向XRDS。

        • XRDS:一种基于XML的XRI资源描述符。它被设计用来提供关于XRI的可用的、描述性信息。在OpenID应用场合中,XRDS用来描述OpenID服务器,并且使用“priority”参数标识了用户对服务器的优选顺序。在下面的示例中,http://www.livejournal.com/users/frank具有最高的优先权(最低的数值):

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)"
xmlns:openid="http://openid.net/xmlns/1.0">
  <XRD>
    <Service priority="50">
      <Type>http://openid.net/signon/1.0</Type>
      <URI>http://www.myopenid.com/server</URI>
      <openid:Delegate>http://smoker.myopenid.com/</openid:Delegate>
    </Service>
    <Service priority="10">
      <Type>http://openid.net/signon/1.0</Type>
      <URI>http://www.livejournal.com/openid/server.bml</URI>      <openid:Delegate>http://www.livejournal.com/users/frank/</openid:Delegate>
    </Service>
    <Service priority="20">
      <Type>http://lid.netmesh.org/sso/2.0</Type>
      <URI>http://mylid.net/liddemouser</URI>
    </Service>
  </XRD>
</xrds:XRDS>

        • 使用HTML代码。在HTML的<head>部分必须提供以下标签:

<link rel="openid2.provider" href="http://openid.com/server/endpoint/url"/>

可选的,如果用户使用委派(delegation)(就是用户宣称拥有一个不存在于该OpenID服务器上的本地标识),则需要使用下面的标签:

<link rel="openid2.local_id" href="http://openid.com/server/endpoint/url"/>

例如,某人正在使用openidprovider.com这个OpenID服务器来验证他在另一个OpenID服务器usersite.com上的身份,那么其本地标识将使用类似user.openidprovider.com的形式。

这个“发现”的过程允许外部站点知道两件事,其中一件是外部站点如何与OpenID服务器进行通讯:
        1.OpenID提供者端点URL:OpenID提供者的最终URL(服务器URL)。
        2.认证协议版本: OpenID认证使用的协议版本。

作为可选的,如果用户使用委派,那么外部站点将需要知道:
        1.用户宣称的标识:此标识为用户宣称属于自己的。它是在登录过程中用户提供过的标识。
        2.本地标识(OP-Local Identifier):用户在其OpenID服务器上拥有的本地标识。

例如,用户使用http://www.example.com/作为其标识,但外部网站实际上通过https://www.exampleprovider.com/endpoint/这个OpenID服务器来验证用户标识https://exampleuser.exampleprovider.com/。那么在对http://www.example.com/执行发现的过程中,我们需要在XRDS文档的最后一个XRD成员中提供下面的XML片段

<Service xmlns="xri://$xrd*($v*2.0)">
  <Type>http://specs.openid.net/auth/2.0/signon</Type>
  <URI>https://www.exampleprovider.com/endpoint/</URI>
  <LocalID>https://exampleuser.exampleprovider.com/</LocalID>
</Service>

4. Relying Party站点建立与OpenID服务器之间的关联(可选)

通过在外部站点和OpenID服务器之间的关联(association),我们可以建立一种在两者之间共享的加密通道,它可以用来验证后续的协议信息并降低通讯回合数。在OpenID规范中对于实际创建关联的过程进行了详尽的描述。简单来讲就是使用了一种Diffie-Hellman密钥交换算法来生成共享密钥。此密钥用于对信息进行签名。

这样使得外部站点和OpenID服务器之间能够安全地通讯。这里指的“安全性”是通过传输层(使用SSL)或者通过应用层的HMAC SHA1或者HMAC SHA256算法实现的。关联请求的成果就是assoc_handle(关联权柄),外部站点和OpenID服务器将在本次关联的后继活动中将它作为对消息进行加密的密钥。

关联阶段被标为“可选的”,这是因为OpenID协议还允许外部站点直接请求认证(不作关联)、并且接着请求对认证信息进行验证。这样外部站点可以不保有关联权柄信息,以实现无状态通讯。而这种方式被推荐用于执行与OpenID服务器之间的相关通讯,但如果不能使用此方式的话,就必须创建上面讲的关联方式。

5. Relying Party站点请求认证

我们通过使用重定向页面可以建立认证请求。请查看下表中的重要参数值,详细信息请参考OpenID相关信息格式文档:
image
请注意:外部站点并没有直接发送HTTP请求到OpenID服务器,而是重定向到OpenID服务器页面。由于这样使得OpenID服务器能够从用户浏览器中读取cookie而没有将任何认证细节泄露给外部站点,因此这个过程是安全的。

6. OpenID服务器回应认证请求

在接收到OpenID认证请求后,OpenID服务器必须决定允许还是拒绝此用户的认证。这将由用户从前是否在OpenID服务器上认证过决定。

请注意:OpenID服务器在接收认证请求信息时是具有控制权的。这意味着它不但能够异步地回应认证请求信息,并在它回应认证请求之前与用户进行一系列的交互。大多数认证服务器都提供给用户一个页面使其能够选择允许或者拒绝来自外部站点的认证请求。

一旦OpenID服务器已经回应了认证请求,那么它将创建一个如下描述的认证回应信息,低层信息细节请阅读OpenID协议文档:
image

此回应通过将用户重定向到外部站点的方式发送,以确保外部站点和OpenID服务器之间在认证请求/回应过程中没有直接通讯。

7. 验证间接回应

协议的最后一步是外部站点验证这个发自OpenID服务器的间接认证回应信息。

当外部站点接收到回应时,它必须在接受其内容之前进行下面的验证:
        • “openid.return_to”的参数值是否匹配当前请求的URL。这确保OpenID服务器重定向用户、发送回应信息到正确的URL。
        • 被发现的信息是否与回应信息相匹配。
        • 具有相同参数值的“openid.response_nonce”表示OpenID服务器遭到了重放攻击(reply attacks)。
        • 在回应信息中的签名是否有效、要求的签名域是否都被签名。这保证认证信息没有被篡改过。

协议扩展

OpenID协议提供了一个基本的认证机制。目前还有基于OpenID的其它可用协议:
        • Attribute Exchange:OpenID属性交换是一种用于在端点之间交换标识信息OpenID服务扩展。其提供了对标识信息的接收和存储。
        • Simple Registration:这是OpenID认证协议的扩展,它允许非常轻量级的配置交换。主要用于在终端用户使用web服务注册新帐号时传送八种常用的请求信息。

使用OpenID4Java实现OpenID协议
image
OpenID4Java是对OpenID1.1和2.0规范的实现,目前它通过code.google.com系统进行维护。此项目初始代码是由Sxip捐献出来的,而后Atlassian等公司参与进来,并为实现支持2.0规范(属性交换规范)的API贡献了大量的工作。

在使用OpenID4Java之前,你需要完成以下工作:
        • 下载OpenID4Java代码库,并将其安装到你的项目中。
        • 修改你的认证提示,使其询问用户的OpenID标识,而不是从前的用户名和密码。
        • 创建针对用户标识的认证请求,并将用户重定向到他们的OpenID服务器。
        • 在返回URL中接收OpenID提供者的认证回应并进行验证。

这样,你的web应用就会像在上面协议综述中的流程图所展示的“Relying Party”那样工作。

第一步是创建消费者对象,它将向认证服务器发出认证请求。这里我们将消费者对象视为一个贯穿应用的个体,以使相关的关联密钥能够保存在同一位置。因为当面临多个认证请求时,在不同的请求之间保存密钥将在两个端点(请求和回应端点)间引起下幅度的性能下降。

Sample Consumer代码片段:

/**
 * Sample Consumer (Relying Party) implementation.
 */
public class SampleConsumer
{
    public ConsumerManager manager;

    public SampleConsumer() throws ConsumerException
    {
        // instantiate a ConsumerManager object
        manager = new ConsumerManager();
    }

    ...
}

一旦用户提供了OpenID URL,你就需要获取OpenID认证服务器的端点URL,发送请求到此URL。而OpenID认证服务器的端点被确定后,你还要创建一个和服务器关联的共享密钥。

// discover the OpenID authentication server's endpoint URL
List discoveries = manager.discover(userSuppliedOpenID);

// attempt to associate with the OpenID provider
// and retrieve one service endpoint for authentication
DiscoveryInformation discovered = manager.associate(discoveries);

// store the discovery information in the user's session for later use
session.setAttribute("discovered", discovered);

以上的调用将完成:
        • 下载OpenID提供者列表(一般只有一个提供者)。返回结果将按照用户指定的优选顺序排列。
        • 通过关联获取和OpenID提供者之间的共享密钥。
        • 将关联(发现信息)保存,以备之后的使用。

我们现在需要将用户重定向到他们的OpenID提供者页面,并告诉OpenID提供者外部站点的地址(返回URL,这里就是你的站点),以使OpenID提供者知道在执行完认证后将用户发送到哪里。

// define the return path
String returnURL = "http://company.com/openidresponse.jsp";

// generate an AuthRequest message to be sent to the OpenID provider
AuthRequest authReq = manager.authenticate(discovered, returnURL);

// redirect the user to their provider for authentication
httpResp.sendRedirect(authReq.getDestinationUrl(true));

上面的代码将用户重定向到他们的OpenID提供者,在那里用户将被询问是否同意和你的站点进行认证。(请注意:无论用户同意“临时”授权给你的web应用、还是“总是”或者“不”授权,OpenID提供者都将保存此首选标识)。当用户再次访问你的web应用时,如果用户已经被OpenID提供者认证过并且在首次认证时选择了“总是”,那么此用户将可以访问你的web应用,而无需再次认证。

在认证用户之后,OpenID提供者将用户重定向到外部站点(由返回URL定义的web应用),并发送认证回应信息给你的web应用,而你的web应用将需要接收此回应。你可以显示错误信息或者将用户发送到“成功”页面,这完全取决于你的工作流。

这是处理来自OpenID提供者认证信息的最简单过程:

// extract the parameters from the authentication response
// (which comes in as a HTTP request from the OpenID provider)
ParameterList openidResp = new ParameterList(request.getParameterMap());

// retrieve the previously stored discovery information
DiscoveryInformation discovered = (DiscoveryInformation) session.getAttribute("discovered");

// extract the receiving URL from the HTTP request
StringBuffer receivingURL = request.getRequestURL();
String queryString = request.getQueryString();

if (queryString != null && queryString.length() > 0)
   receivingURL.append("?").append(request.getQueryString());

// verify the response
VerificationResult verification = manager.verify(receivingURL.toString(), openidResp, discovered);

// examine the verification result and extract the verified identifier
Identifier verified = verification.getVerifiedId();

if (verified != null)
    // success, use the verified identifier to identify the user
else
// OpenID authentication failed

查看完整的Sample Consumer代码:http://code.google.com/p/openid4java/wiki/SampleConsumer

结论

OpenID是通过标准化认证方式由互联网社区催生出来的综合效应。它完成了和SAML这些现存协议同样的事情,但它却易于安装、部署和维护。任何具备基本编程技能的人都能够在其现有或者新建的网站上部署OpenID技术。

OpenID已经获得愈加广泛的使用。我们有理由相信,在不久之后的公司之间、像Google和Yahoo这样的门户站点之间都将支持此技术,OpenID技术将随着互联网社区的发展而成为网站之间通用的主流认证方法。
关于OpenID技术的更多信息,请访问:http://openid.net/

原文(《Using OpenID》)作者简介

Justen StepkaAtlassian的Crowd单点登录安全系统的项目管理者,同时也是OpenID4Java项目的提交者(committer)。Crowd团队目前正在开发实现OpenID2.0规范的服务站点。Justen从前是Authentisoft的CEO,该公司于2006年被Atlassian收购。

Shihab Hamid是在Atlassian工作的工程师,他主要负责用Crowd产品在OpenID集成方面的设计和开发。同时也是OpenID4Java项目的提交者。

相关资料

        • OpenID官方网站:http://openid.net/
        • OpenID工作过程:http://openid.net/about.bml
        • OpenID4Java项目主页:http://code.google.com/p/openid4java/wiki/ProjectHome_zh_CN
        • 下载OpenID4Java:http://code.sxip.com/openid4java/
        • 支持OpenID的各种代码库:http://www.openidenabled.com/
        • 目前支持OpenID的主要站点:http://iwantmyopenid.org/bounty/sponsors


Warning: Attempt to read property "ID" on bool in /afang/www/liuhuafang.com/wp-content/themes/blocksy/inc/components/single/single-helpers.php on line 52

Warning: Attempt to read property "ID" on bool in /afang/www/liuhuafang.com/wp-content/themes/blocksy/inc/components/single/single-helpers.php on line 52

Warning: Attempt to read property "ID" on bool in /afang/www/liuhuafang.com/wp-content/themes/blocksy/inc/components/single/single-helpers.php on line 52

Warning: Attempt to read property "ID" on bool in /afang/www/liuhuafang.com/wp-content/themes/blocksy/inc/components/single/single-helpers.php on line 52
文章: 1325
订阅评论
提醒
guest

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x