编写 OAuth 小工具

本文档介绍了如何编写使用 OAuth 功能进行验证的小工具。您可以将 Oauth 与 makeRequest() 函数配合使用。有关使用 makeRequest() 函数提取远程内容的一般论述,请参阅提取远程内容

本文档主要集中介绍了最简单的使用实例:编写在 iGoogle 上运行并从支持 OAuth 协议的任何网站访问用户私有数据的小工具。不过,这只是 OAuth 的一个用途。最终,在任何容器上运行的任何 OpenSocial 小工具都将能够使用本文档中介绍的技术访问受 OAuth 保护的数据。作为一种容器,iGoogle 可提供最简单的模型,因为用户在 iGoogle 上添加的小工具只会显示给该用户。如果是社交网络容器,则情况会比较复杂:查看小工具的用户与添加该小工具的数据拥有者可能不是同一个人。有关服务提供商如何支持社交网络容器的 OAuth 的说明,请参阅文档在启用 OpenSocial 的社交网络上启用了小工具的 OAuth

什么是 OAuth 代理?

许多互联网服务正开始提供 REST API,这种 API 使用户可以访问这些互联网服务代表用户存储的私有数据。例如,某种互联网服务可以允许用户访问其照片、邮件、地址簿、健康记录等等。OAuth 是一项标准,使私有数据的帐户所有者可以告知托管服务允许另一网站(或小工具)访问该数据。OAuth 代理旨在使小工具可更为轻松地使用 OAuth 标准。

请注意:只有在 OpenSocial 容器中运行的小工具的 gadgets.* API 中支持 OAuth 代理。传统小工具 API 不支持 OAuth 代理。目前,iGoogle 是支持此功能的小工具容器。然而,还存在一个被称作 Shindig 的开源项目,该项目是对小工具规范和 OAuth 代理功能的应用。其他许多互联网网站正在评估部署此技术。

受众

本文档主要面向想要使用 OAuth 协议与其用户的服务进行通讯的小工具开发人员。 我们假设您已对正在访问的服务非常熟悉,并了解涉及的任何访问/验证问题。使用小工具验证代理服务时,您需要了解某些服务特定的详细信息。

目录

  1. 什么是 OAuth 代理?
  2. 什么类型的互联网服务具有支持 OAuth 的 REST API?
  3. 关键概念
  4. 选择服务提供商
    1. Google 服务提供商
    2. 非 Google 服务提供商
  5. 选择端点
  6. 小工具练习示例
  7. 执行 OAuth 小工具
    1. 示例小工具
    2. OAuth 部分
    3. 执行批准流程
    4. 有关 makeRequest() 的详细信息
  8. Google 数据 API:一种备选方式
  9. 跳过弹出窗口
    1. 执行预先批准的网址
    2. 添加用户使用偏好
  10. 其他示例
  11. 高级 OAuth 技巧

什么类型的互联网服务具有支持 OAuth 的 REST API?

可从 Google 上获得的所有 REST API 和 MySpace 的数据可用性 API 都支持 OAuth(请参阅网络应用程序的 OAuth 验证Google 数据 API 文档)。OAuth 社区 wiki 列出了提供启用了 OAuth 的 API 的其他服务提供商

关键概念

下表提供了对基本 OAuth 术语的定义。有关详细信息,请参阅 OAuth 规范网络应用程序的 OAuth 验证

术语 定义
服务提供商 允许通过 OAuth 访问的网络应用程序。例如,Google 或 MySpace。
用户 具有服务提供商帐户的用户,该服务提供商可能会为该用户提供可通过 OAuth 访问的数据。 例如,小工具可能会通过 OAuth 访问用户的 Google 日历数据。
使用者 代表用户使用 OAuth 访问服务提供商的网站或应用程序。在此环境中,使用 OAuth 访问用户数据的小工具即为使用者。
受保护的资源 受服务提供商控制的数据,使用者(小工具)可通过验证访问该数据。
容器 容器是嵌入小工具的 OpenSocial 环境。例如,iGoogle 即为一容器。容器负责管理小工具的布局和控件,并代表小工具支持各种功能。OAuth 小工具只能在支持 OAuth 的容器中运行。如果某小工具使用 OAuth,那么它实际上是通过处理 OAuth 协议所要求的所有数字签名代表该小工具执行该协议的容器。
使用者密钥 小工具用以向服务提供商识别其自身的值。该值与 oauth_consumer_key 参数相对应。有关详细信息,请参阅 OAuth 规范
使用者机密 小工具用以确定使用者密钥的所有权的机密。
请求令牌 小工具用以获取来自用户的授权的值,可交换获得访问令牌。
访问令牌 小工具用以代表用户获取受保护资源的获取权限的值,而非使用该用户的服务提供商凭证。
令牌机密 小工具用以确定对给定令牌的所有权的机密。

例如,假设您拥有可通过 Google 数据 API 访问用户的私有日历数据的小工具。在这种情况下,Google 是服务提供商,而小工具则为使用者。用户是使用该小工具访问个人日历数据的人员。

用户首次运行该小工具时,小工具会将该用户发送到服务提供商的站点,并会提示该用户授予小工具访问其日历数据的权限。 此验证由 OAuth 进行处理。用户登录到自己的 Google 帐户并授予小工具权限后,小工具从此刻起(或至少在用户明确撤消权限之前)就可以访问该用户的日历数据。用户无需再次登录并授予权限即可通过小工具访问自己的数据。

选择服务提供商

创建 OAuth 小工具的第一步就是选择服务提供商。以下是两个 OAuth 使用实例:

  • 从 Google 数据或其他 Google 服务提供商请求数据。
  • 从非 Google 服务提供商请求数据。

下面将详细论述这些选项。

Google 服务提供商

如果要访问 Google 数据 API,则只需指定您要访问的 Google 数据 REST API 端点。您可在 <OAuth> 部分(位于 <ModulePrefs> 部分下)以及在使用端点请求数据的小工具代码中指定该端点。

非 Google 服务提供商

访问非 Google 端点通常需要您使用非 Google 服务提供商注册您的应用程序(例如,请参阅 MySpace 注册)。如果您的公司想要展示自己的 OAuth 服务提供商端点,那么,即使仅适用于公司创建的小工具,参考 Google 维护的高级 OAuth 技巧站点也是非常有帮助的。

在您使用非 Google 服务提供商注册应用程序后,该提供商将为您提供 OAuth 使用者机密,您可使用该机密对从应用程序发送到该服务提供商的所有请求进行数字签名。小工具可公开访问,因此,小工具并不适合存储这些类型的机密。可以改为使用能提供您的小工具将从中运行的容器的网站注册 OAuth 使用者机密(假设该网站可提供 OAuth 代理)。

如果是 iGoogle,可将以下信息以邮件形式发送到 oauthproxyreg@google.com 以注册您的 OAuth 使用者机密:

  • 小工具的网址。
  • 服务提供商分配给您的 OAuth 使用者密钥。OAuth 使用者密钥是识别第三方网络应用程序的域。使用服务提供商注册应用程序时使用该域,以便服务提供商可识别来自您应用程序的请求。
  • 服务提供商分配给您的 OAuth 使用者机密。使用该机密对从应用程序发送到服务提供商的所有请求进行数字签名。
  • 使用服务提供商对称签名还是不对称签名(或者您对此一无所知)。

注册您的 OAuth 使用者机密之前,您的小工具不会工作。如果要更改小工具的网址,需要重新注册该小工具的机密。

许多 OAuth 服务提供商要求您在请求 OAuth 使用者密钥和使用者机密时提供 OAuth 回调网址。您可以为小工具指定一个回调网址 http://oauth.gmodules.com/gadgets/oauthcallback

OAuth 服务提供商注意事项

如果要帮助您的开发社区避免执行此手动注册步骤,可以提高 OAuth 配置以直接接受来自 iGoogle 的电子签名。iGoogle 目前使用 RSA_SHA1 签名方法(如 OAuth 标准中所定义)和以下公共密钥,该公共密钥此处以自签名证书的形式出现,应当可以轻松导入到以各种编程语言执行的 OAuth 库中:

-----BEGIN CERTIFICATE-----
MIIDBDCCAm2gAwIBAgIJAK8dGINfkSTHMA0GCSqGSIb3DQEBBQUAMGAxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEG
A1UEChMKR29vZ2xlIEluYzEXMBUGA1UEAxMOd3d3Lmdvb2dsZS5jb20wHhcNMDgx
MDA4MDEwODMyWhcNMDkxMDA4MDEwODMyWjBgMQswCQYDVQQGEwJVUzELMAkGA1UE
CBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBJ
bmMxFzAVBgNVBAMTDnd3dy5nb29nbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDQUV7ukIfIixbokHONGMW9+ed0E9X4m99I8upPQp3iAtqIvWs7XCbA
bGqzQH1qX9Y00hrQ5RRQj8OI3tRiQs/KfzGWOdvLpIk5oXpdT58tg4FlYh5fbhIo
VoVn4GvtSjKmJFsoM8NRtEJHL1aWd++dXzkQjEsNcBXwQvfDb0YnbQIDAQABo4HF
MIHCMB0GA1UdDgQWBBSm/h1pNY91bNfW08ac9riYzs3cxzCBkgYDVR0jBIGKMIGH
gBSm/h1pNY91bNfW08ac9riYzs3cx6FkpGIwYDELMAkGA1UEBhMCVVMxCzAJBgNV
BAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUg
SW5jMRcwFQYDVQQDEw53d3cuZ29vZ2xlLmNvbYIJAK8dGINfkSTHMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAYpHTr3vQNsHHHUm4MkYcDB20a5KvcFoX
gCcYtmdyd8rh/FKeZm2me7eQCXgBfJqQ4dvVLJ4LgIQiU3R5ZDe0WbW7rJ3M9ADQ
FyQoRJP8OIMYW3BoMi0Z4E730KSLRh6kfLq4rK6vw7lkH9oynaHHWZSJLDAp17cP
j+6znWkN9/g=
-----END CERTIFICATE-----

iGoogle 使用草稿 OAuth 小工具扩展提供要代表它向服务提供商发出请求的小工具的网址。

选择端点

端点是小工具用以通过服务提供商访问私有数据的网址。请参阅您的服务提供商的文档,找出可用的端点。如果您的小工具要通过支持 Google 数据 API 的 Google 服务请求数据,请参阅 Google 数据 API 文档了解每项服务所支持的端点。有些端点需要验证(可通过 OAuth 实现),而有些端点则不需要。以下是 Google 数据端点的一些示例:

  • http://www.google.com/m8/feeds/contacts/default/full/ -- 获得包含当前已登录用户的联系人的供稿。
  • http://gdata.youtube.com/feeds/api/users/default/uploads?alt=json -- 获得包含当前已登录用户的上传的 YouTube 视频的供稿(JSON 格式)。
  • http://gdata.youtube.com/feeds/api/users/username/uploads -- 获得包含用户名指定的用户的 YouTube 视频的供稿。请注意,此示例无需进行验证。

小工具只能访问一定范围内的数据。如果小工具请求访问 scope=http://www.google.com/m8/feeds/,该小工具只能使用 Contacts API 的端点(供稿)。单个小工具可以请求多个范围。

小工具演示示例

本文档使用一个简单的示例小工具来说明 OAuth 小工具的工作原理。该示例小工具会提取并显示当前已登录用户的联系人。要体验一下 OAuth 小工具的工作原理,请完成以下演示步骤。

OAuth 小工具需要基于 gadgets.* API 并在支持 OAuth 的 OpenSocial 容器中运行。您可以在 iGoogle 中部署 OAuth 小工具。

要将小工具添加到 iGoogle,请执行以下步骤:

  1. 请转至 http://www.google.com/ig
  2. 点击“个性化此页面”按钮。这会使您转到 iGoogle 目录。
  3. 点击左侧导航栏底部的“添加供稿或小工具”链接。
  4. 将该示例小工具的网址键入到文本字段 (http://gadget-doc-examples.googlecode.com/svn/trunk/opensocial-gadgets/oauth-contacts.xml),然后点击“添加”
  5. 点击“返回 iGoogle 主页”链接(左上角)以返回到 iGoogle 并查看该示例小工具。

新添加的示例小工具会显示链接“个性化此小工具”。该链接包含一个请求令牌。要授予您数据的访问权限,请执行以下步骤:

  1. 点击“个性化此小工具”链接。
  2. 如果您拥有多个帐户,可能显示一个 Google 帐户页面。该页面上显示“第三方服务正请求您的 Google 帐户的访问权限”。选择您要访问其中数据的帐户。
  3. 然后,您会看到“Google 帐户访问请求”页面。该页面包含“站点 www.google.com 正请求以下所列产品的 Google 帐户的访问权限”等文本。文本中列出了该站点(容器)要访问其数据的产品。点击“授予访问权限”。这样,请求令牌交换为访问令牌,这可使小工具从指定的服务提取您的数据。
  4. 返回到 iGoogle。此时小工具应当显示您的数据。有关详细信息,请参阅执行批准流程

从此刻起,除非您删除小工具或转至 Google 帐户并撤消访问权限,否则,小工具将可继续访问您的数据。您只需要授予一次访问权限。

该示例小工具介绍了一种可指导用户完成批准流程的方法。不管您采取哪种特定的实施方案,小工具都应当执行以下操作:

  • 尝试提取用户的数据。
  • 如果成功提取了用户的数据,会显示该数据。
  • 如果提取用户数据失败,则会提示该用户通过弹出窗口授予批准权限。
  • 弹出窗口关闭后,就会开始再次提取该用户的数据。如果小工具拥有相关的访问令牌,那么,不管该小工具何时运行,都将自动提取数据。

Shindig 项目提供了 JavaScript 库,您可以使用这些库创建弹出窗口并检测弹出窗口何时关闭。有关该主题的更多讨论内容,请参阅执行批准流程

执行 OAuth 小工具

一旦您选定服务提供商以及要用来提取所需数据的端点,就可以开始执行您的小工具。

除了常规的小工具功能以外,OAuth 小工具必须包含以下内容:

  • 一个指定的服务提供商
  • 一个端点
  • 在您小工具的 <ModulePrefs> 部分中要有 <OAuth...> 部分,该部分包含了有关该小工具所使用的所有服务和端点的详细信息。
  • 一种用于检测用户是否批准数据的访问的机制。如果用户尚未授予访问权限,则小工具必须为该用户提供联系服务提供商的方式,例如,通过为用户提供指向该服务提供商的 OAuth 授权网址的链接。然后,服务提供商将会指导用户完成验证和批准流程。用户批准其数据的访问权限后,小工具即可访问该用户的数据。
  • 调用具有适当的 OAuth 参数的 gadgets.* makeRequest() 函数以提取经过验证的数据。
  • 用于处理返回的数据的代码,不管该代码是否适用于此特定小工具

在本部分中,我们将通过一个示例小工具一部分一部分地了解 OAuth 小工具的工作原理。

示例小工具

该示例小工具通过 Google Contacts 数据 API 提取当前已登录用户的联系人。它使用端点 http://www.google.com/m8/feeds/contacts/default/full,该端点会告知服务器返回其凭证符合该请求的用户的联系人。联系人作为 ATOM XML 结果以 JSON 格式 (http://www.google.com/m8/feeds/contacts/default/full?alt=json) 返回。有关详细信息,请参阅与 Google 数据 API 一起使用 JSON

以下是完整的小工具。下面将对其进行详细论述。

<?xml version="1.0" encoding="UTF-8" ?> 
<Module>
  <ModulePrefs title="OAuth Contacts" scrolling="true">
    <Require feature="opensocial-0.8" />
    <Require feature="locked-domain"/> 
    <OAuth>
      <Service name="google">
        <Access url="https://www.google.com/accounts/OAuthGetAccessToken" method="GET" /> 
        <Request url="https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/" method="GET" /> 
        <Authorization url="https://www.google.com/accounts/OAuthAuthorizeToken?oauth_callback=http://oauth.gmodules.com/gadgets/oauthcallback" /> 
      </Service>
    </OAuth>
  </ModulePrefs>
  <Content type="html">
  <![CDATA[ 
 
  <!-- shindig oauth popup handling code -->
  <script src="http://gadget-doc-examples.googlecode.com/svn/trunk/opensocial-gadgets/popup.js"></script>

  <style>
  #main {
    margin: 0px;
    padding: 0px;
    font-size: small;
  }
  </style>

  <div id="main" style="display: none">
  </div>

  <div id="approval" style="display: none">
    <img src="http://gadget-doc-examples.googlecode.com/svn/trunk/images/new.gif">
    <a href="#" id="personalize">Personalize this gadget</a>
  </div>

  <div id="waiting" style="display: none">
    Please click
    <a href="#" id="approvaldone">I've approved access</a>
    once you've approved access to your data.
  </div>

  <script type="text/javascript">
    // Display UI depending on OAuth access state of the gadget (see <divs> above).
    // If user hasn't approved access to data, provide a "Personalize this gadget" link
    // that contains the oauthApprovalUrl returned from makeRequest.
    //
    // If the user has opened the popup window but hasn't yet approved access, display
    // text prompting the user to confirm that s/he approved access to data.  The user
    // may not ever need to click this link, if the gadget is able to automatically
    // detect when the user has approved access, but showing the link gives users
    // an option to fetch their data even if the automatic detection fails.
    //
    // When the user confirms access, the fetchData() function is invoked again to
    // obtain and display the user's data.
    function showOneSection(toshow) {
      var sections = [ 'main', 'approval', 'waiting' ];
      for (var i=0; i < sections.length; ++i) {
        var s = sections[i];
        var el = document.getElementById(s);
        if (s === toshow) {
          el.style.display = "block";
        } else {
          el.style.display = "none";
        }
      }
    }
      
    // Process returned JSON feed to display data.
    function showResults(result) {
      showOneSection('main');

      var titleElement = document.createElement('div');
      var nameNode = document.createTextNode(result.feed.title.$t);
      titleElement.appendChild(nameNode);
      document.getElementById("main").appendChild(titleElement);
      document.getElementById("main").appendChild(document.createElement("br"));

      list = result.feed.entry;

      for(var i = 0; i < list.length; i++) {
        entry = list[i];
        var divElement = document.createElement('div');
        divElement.setAttribute('class', 'name');
        var valueNode = document.createTextNode(entry.gd$email[0].address);
        divElement.appendChild(nameNode);
        divElement.appendChild(valueNode);
        document.getElementById("main").appendChild(divElement);
      }
    }

    // Invoke makeRequest() to fetch data from the service provider endpoint.
    // Depending on the results of makeRequest, decide which version of the UI
    // to ask showOneSection() to display. If user has approved access to his
    // or her data, display data.
    // If the user hasn't approved access yet, response.oauthApprovalUrl contains a
    // URL that includes a Google-supplied request token. This is presented in the 
    // gadget as a link that the user clicks to begin the approval process.     
    function fetchData() {
      var params = {};
      url = "http://www.google.com/m8/feeds/contacts/default/base?alt=json";
      params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
      params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
      params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
      params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
      params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;

      gadgets.io.makeRequest(url, function (response) { 
        if (response.oauthApprovalUrl) {
          // Create the popup handler. The onOpen function is called when the user
          // opens the popup window. The onClose function is called when the popup
          // window is closed.
          var popup = shindig.oauth.popup({
            destination: response.oauthApprovalUrl,
            windowOptions: null,
            onOpen: function() { showOneSection('waiting'); },
            onClose: function() { fetchData(); }
          });
          // Use the popup handler to attach onclick handlers to UI elements.  The
          // createOpenerOnClick() function returns an onclick handler to open the
          // popup window.  The createApprovedOnClick function returns an onclick 
          // handler that will close the popup window and attempt to fetch the user's
          // data again.
          var personalize = document.getElementById('personalize');
          personalize.onclick = popup.createOpenerOnClick();
          var approvaldone = document.getElementById('approvaldone');
          approvaldone.onclick = popup.createApprovedOnClick();
          showOneSection('approval');
        } else if (response.data) {
          showOneSection('main');
          showResults(response.data);
        } else {
          // The response.oauthError and response.oauthErrorText values may help debug
          // problems with your gadget.
          var main = document.getElementById('main');
          var err = document.createTextNode('OAuth error: ' +
            response.oauthError + ': ' + response.oauthErrorText);
          main.appendChild(err);
          showOneSection('main');
        }
      }, params);
    }
    // Call fetchData() when gadget loads.
    gadgets.util.registerOnLoadHandler(fetchData);
  </script>
  ]]> 
  </Content>
</Module>

请注意:我们建议您通过将 <Require feature="locked-domain"/> 行添加到您的小工具中选择已锁定的域功能。我们还建议您托管自己的 popup.js 副本。

<OAuth> 部分

您的小工具首先需要添加的是位于 <ModulePrefs> 部分中的 <OAuth> 部分:

<?xml version="1.0" encoding="UTF-8" ?> 
<Module>
  <ModulePrefs title="OAuth Contacts" scrolling="true">
    <Require feature="opensocial-0.8" /> 
    <Require feature="locked-domain"/> 
    <OAuth>
      <Service name="google">
        <Access url="https://www.google.com/accounts/OAuthGetAccessToken" method="GET" /> 
        <Request url="https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/" method="GET" /> 
        <Authorization url="https://www.google.com/accounts/OAuthAuthorizeToken?oauth_callback=http://oauth.gmodules.com/gadgets/oauthcallback" /> 
      </Service>
    </OAuth>
  </ModulePrefs>

OAuth 部分为容器提供小工具的 OAuth 服务配置,如下所示:

元素 说明
/ModulePrefs/OAuth/Service 此元素是一个 OAuth 服务配置。

属性:
  • name -- 服务的名称,用于在运行时引用 OAuth 服务。该参数是可选的。如果未指定,则默认为“”。小工具开发人员通过将服务名称作为参数传递到 makeRequest() 以指定他们想要使用的 OAuth 服务。
/ModulePrefs/OAuth/Service/Request
/ModulePrefs/OAuth/Service/Access
这些元素表示 OAuth 请求令牌和访问令牌网址。有关详细信息,请参阅 OAuth 规范网络应用程序的 OAuth 验证

属性:
  • url -- 端点的网址。
  • method -- 用于发出请求的 HTTP 动词。该参数是可选的。如果未指定,则默认为 POST。
/ModulePrefs/OAuth/Service/Authorization OAuth 授权网址。当您的小工具需要请求用户批准以访问用户的数据时,小工具会打开一个指向该网址 的弹出窗口。

此授权网址包含一个 oauth_callback 查询参数。OAuth 服务提供商在用户批准了其数据的访问权限后会重定向至此网址。您应当将 http://oauth.gmodules.com/gadgets/oauthcallback 指定为您的 oauth_callback 网址。oauthcallback 页面包含自动关闭该弹出窗口的 JavaScript 代码段。您的小工具可以检测该弹出窗口何时关闭并可尝试提取用户的数据。

执行批准流程

OAuth 小工具可提取已验证用户的数据。要进行此操作,用户必须授予服务(本例中为 Google 数据 Contacts)权限以与小工具共享该用户数据。

例如,请考虑联系人示例小工具。以下是活动的先后顺序:

  1. 小工具在运行时将调用 gadgets.util.registerOnLoadHandler(fetchData)(调用 fetchData() 函数)。
  2. fetchData() 函数调用 makeRequest()
  3. makeRequest() 函数指定一个回调参数。除了由 makeRequest() 返回的正常值外,该回调参数还传递具有若干特定于 OAuth 的字段的 JavaScript 对象。

以下是 fetchData() 函数:

function fetchData() {
  var params = {};
  url = "http://www.google.com/m8/feeds/contacts/default/base?alt=json";
  params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON;
  params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
  params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
  params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
  params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;

  gadgets.io.makeRequest(url, function (response) { 
    if (response.oauthApprovalUrl) {
      // Create the popup handler. The onOpen function is called when the user
      // opens the popup window. The onClose function is called when the popup
      // window is closed.
      var popup = shindig.oauth.popup({
        destination: response.oauthApprovalUrl,
        windowOptions: null,
        onOpen: function() { showOneSection('waiting'); },
        onClose: function() { fetchData(); }
      });
      // Use the popup handler to attach onclick handlers to UI elements.  The
      // createOpenerOnClick() function returns an onclick handler to open the
      // popup window.  The createApprovedOnClick function returns an onclick 
      // handler that will close the popup window and attempt to fetch the user's
      // data again.
      var personalize = document.getElementById('personalize');
      personalize.onclick = popup.createOpenerOnClick();
      var approvaldone = document.getElementById('approvaldone');
      approvaldone.onclick = popup.createApprovedOnClick();
      showOneSection('approval');
    } else if (response.data) {
      showOneSection('main');
      showResults(response.data);
    } else {
      // The response.oauthError and response.oauthErrorText values may help debug
      // problems with your gadget.
      var main = document.getElementById('main');
      var err = document.createTextNode('OAuth error: ' +
        response.oauthError + ': ' + response.oauthErrorText);
      main.appendChild(err);
      showOneSection('main');
    }
  }, params);
}

小工具处理回调时,会先将检查 response.oauthApprovalUrl 是否有非空值。如果用户还未授予其数据的访问权限,response.oauthApprovalUrl 将会包含用户为小工具授予数据的访问权限所需访问的网址。该网址的一部分是服务提供商(本例中为 Google)所发布的请求令牌。

小工具会先创建一个 shindig.oauth.popup 对象以管理弹出窗口。该 shindig.oauth.popup 对象会使用若干参数:

var popup = shindig.oauth.popup({
  destination: response.oauthApprovalUrl,
  windowOptions: null,
  onOpen: function() { showOneSection('waiting'); },
  onClose: function() { fetchData(); }
});

destination 参数指定弹出窗口将会打开的网址。

windowOptions 参数则指定要传递到浏览器提供的 window.open 函数的选项。您可以控制弹出窗口的大小、放置位置及其所使用的色度。不同的浏览器支持不同的 window.open 参数。有关示例,请参阅 Internet ExplorerFirefox 的文档。

用户点击链接打开弹出窗口时,会调用 onOpen 函数。此小工具在等待用户批准访问权限的同时会调用 showOneSection('waiting') 以显示相应的消息。

弹出窗口关闭时,将调用 onClose 函数。弹出窗口关闭后,小工具会注册对 fetchData() 的调用以检索用户的数据。

创建弹出对象后,onclick 处理程序必须与 DOM 元素关联以便打开该弹出窗口。小工具为位于 approval <div> 中的 personalize HREF 设置 onclick 处理程序以打开该弹出窗口:

var personalize = document.getElementById('personalize');
personalize.onclick = popup.createOpenerOnClick();

小工具还会将 waiting <div> 中的 approvaldone HREF 配置为 popup.createApprovedOnClick() 处理程序。当点击 approvaldone HREF 时,小工具会关闭弹出窗口并尝试提取用户数据。顺利的话,用户将永远无需点击“我已批准”链接,但是只是为了以防万一,您应当附加 onclick 处理程序:

var approvaldone = document.getElementById('approvaldone');
approvaldone.onclick = popup.createApprovedOnClick();

小工具将显示 approval <div> 以要求用户点击一个链接打开弹出窗口。许多浏览器使用弹出窗口拦截器以阻止不想显示的弹出窗口。要避免被弹出窗口拦截器拦截,您的小工具应在用户点击了某按钮或链接后再打开弹出窗口。

showOneSection('approval');

小工具是否使用 <div> 的函数和 showOneSection() 函数显示相应的 UI 由该小工具的批准状态决定。存在三种 <div> 的函数,每一种函数对应小工具的每一种可能的状态:

  • approval -- 如果用户尚未授予访问权限,小工具将使用 approval <div> 以显示带有“个性化此小工具”链接(其中包含请求令牌)的 UI。用户点击此链接即可开始批准流程。
  • waiting -- 如果用户已打开弹出窗口但尚未批准访问权限,小工具将显示此 <div>。小工具将显示文本以提示用户确认是否已批准数据的访问权限。如果小工具能够自动检测用户何时批准了访问权限,那么,该用户可能无需点击此链接,但是,显示该链接可为用户提供提取其数据的一个选项(即使在自动检测失败的情况下)。如果自动检测失败,小工具将显示消息“一旦您批准了数据的访问权限,请点击 我已批准访问权限”。用户点击后,小工具将调用 fetchData() 以提取用户的数据。
  • main -- 一旦访问令牌已就位,那么,不管小工具何时运行,它都将使用 main <div> 显示用户的数据。此 <div> 还用于显示任何错误。
<div id="main" style="display: none">
  </div>

  <div id="approval" style="display: none">
    <img src="http://gadget-doc-examples.googlecode.com/svn/trunk/images/new.gif">
    <a href="#" id="personalize">Personalize this gadget</a>
  </div>

  <div id="waiting" style="display: none">
    Please click
    <a href="#" id="approvaldone">I've approved access</a>
    once you've approved access to your data.
  </div>

  <script type="text/javascript">
    function showOneSection(toshow) {
      var sections = [ 'main', 'approval', 'waiting' ];
      for (var i=0; i < sections.length; ++i) {
        var s = sections[i];
        var el = document.getElementById(s);
        if (s === toshow) {
          el.style.display = "block";
        } else {
          el.style.display = "none";
        }
      }
    }

有关 makeRequest() 的详细信息

提取远程内容中对 makeRequest() 函数进行了详细介绍。您可以使用该函数检索远程网络内容以及对远程网络内容进行操作。该函数将使用以下参数:

  • String url - 存放内容的网址
  • Function callback - 提取网址后,与来自该网址的数据一起调用的函数
  • Map.<gadgets.io.RequestParameters, Object> opt_params - 传送给请求的其他参数。

如果将 opt_params[gadgets.io.RequestParameters.AUTHORIZATION] 设置为 gadgets.io.AuthorizationType.OAUTH,则容器需要使用 OAuth 来获取访问请求中指定的资源的权限。通常,这需要小工具通过指引用户从服务提供商处获取访问权限来获取用户的内容。

可选参数

您可以在 opt_params 中指定以下附加 OAuth 参数:

参数 说明
gadgets.io.RequestParameters.OAUTH_SERVICE_NAME 小工具用来指代其 XML 规范中 OAuth <Service> 元素的昵称。服务名称还可以在 <ModulePrefs> 下的 /ModulePrefs/OAuth/Service XML 部分指定。如果上述两个位置中都未指定,则默认为“”。
gadgets.io.RequestParameters.OAUTH_TOKEN_NAME 小工具用来指代可授予对特殊资源的访问权限的 OAuth 令牌昵称。如果未指定,则默认为“”。如果小工具有权访问来自同一个服务提供商的多个资源,则可以使用多个令牌名称。例如,有权访问联系人列表和日历的小工具可以使用“联系人”令牌名称来使用联系人列表令牌,并可以使用“日历”令牌名称来使用日历令牌。
gadgets.io.RequestParameters.OAUTH_REQUEST_TOKEN 服务提供商或许能够自动为已预先获得对资源的访问权限的小工具提供请求令牌。小工具可以将该令牌与 参数一起使用。该参数是可选的。
gadgets.io.RequestParameters.OAUTH_REQUEST_TOKEN_SECRET 与预先批准的请求令牌相对应的密钥。该参数是可选的。
gadgets.io.RequestParameters.OAUTH_USE_TOKEN

该参数可为“从不”、“如果可用”或“总是”。 有些服务提供商 API 不需要 OAuth 访问令牌。这类 API 通过 OAuth 使用者密钥验证调用应用程序,然后允许请求继续进行。您可将 OAUTH_USE_TOKEN 设置为“从不”以避免不必要地请求用户批准访问这样的 API。

如果用户尚未授予批准权限,有些 OAuth API 将提供有限的数据,然而,如果用户已授予调用应用程序访问令牌,则这些 OAuth API 可提供其他功能。如果您使用可接受 OAuth 访问令牌但却不需要该令牌的 API,则可将 OAUTH_USE_TOKEN 设置为“如果可用”。如果用户已批准访问权限,将发送访问令牌。如果用户尚未批准访问权限,请求将继续,但不发送访问令牌。

许多 OAuth API 只在发送了访问令牌的情况下才起作用。在使用这样的 API 前,您必须请求用户的批准。将 OAUTH_USE_TOKEN 设置为“总是”以要求访问令牌可用。如果无可用的访问令牌,批准网址将被返回至您的小工具。

如果您将 AUTHORIZATION 设置为 SIGNED,则 OAUTH_USE_TOKEN 的默认值为“从不”。如果将 AUTHORIZATION 设置为 OAUTH,则 OAUTH_USE_TOKEN 的默认值将为“总是”。

如果使用 OAuth,则容器将代表小工具执行 OAuth 协议。如 OAuth 服务提供商注意事项中所述,如果小工具尚未注册使用者密钥以同 iGoogle 一起使用,则 iGoogle 将使用其默认的 RSA 签名密钥。

回调参数

除了由 makeRequest() 返回的正常值外,makeRequest() 回调参数还传递具有若干特定于 OAuth 的字段的 JavaScript 对象。

OAuth 字段 说明
oauthApprovalUrl 如果指定了该值,该值则为包含由服务提供商发布的请求令牌的网址。非空值表示用户需要访问外部页面才能批准小工具访问数据的请求。建议使用弹出窗口引导用户转至外部页面。一旦用户批准访问,小工具便可重复进行 makeRequest() 调用以检索数据。
oauthError 如果指定了该值,则表示发生了与 OAuth 相关的错误。
oauthErrorText 如果指定了该值,则表示发生了与 OAuth 相关的错误。该值可用于为小工具开发人员提供调试信息。参数:
  • String url -- 存放内容的网址。
  • Function callback --- 提取网址后,与来自该网址的数据一起调用的函数。
  • Map.<gadgets.io.RequestParameters, Object> opt_params -- 附加的请求参数或代理请求参数。

OAuth 的 OpenSocial 扩展

OpenSocial 容器可通过在 OAuth 请求中传递签名的附加参数来提供有关对 OAuth 服务提供商站点请求的环境的其他信息。在 gadgets.io.makeRequest 文档中介绍了附加参数(例如 opensocial_owner_idopensocial_app_url)。了解 OpenSocial 的服务提供商可使用这些参数将附加数据返回至用户。例如,服务提供商不仅可返回有关小工具查看者的信息,而且可返回有关该查看者好友的信息。

Google 数据 API:一种备选方式

以上示例使用 gadgets.* makeRequest() 方法提取数据。但是,如果您使用 Google 数据 API,则可以利用与 OAuth 代理配合使用的 Google 数据 JavaScript 库。它将在后台调用 makeRequest(),因此,您小工具中的最终效应并无变化。

有关详细信息及示例,请参阅文章创建 Google 数据小工具

跳过弹出窗口

如果您是 OAuth 服务提供商,您可以通过避免批准弹出窗口来改善小工具的用户体验。当用户从您的站点添加小工具时,您会传递给 iGoogle 一个预先批准的 OAuth 请求令牌以用来获取用户数据的访问权限。

流程如下:

  1. 用户登录到服务提供商站点。
  2. 该服务提供商站点具有“添加至 Google”链接。服务提供商们应当使用该链接常用的“添加至 Google 徽标”
  3. 该链接的目标是服务提供商站点上的一个特殊网址,该网址可发布预先批准的 OAuth 请求令牌。出于安全性考虑,必须保护该网址以防受到 CSRF 攻击
  4. 该服务提供商的预先批准的网址将验证该用户并创建一个预先批准的 OAuth 请求令牌以授予该用户的数据的访问权限。然后,该预先批准的网址会将用户重定向至 iGoogle addmodule 网址。该重定向网址包含请求令牌和(可选)请求令牌机密。
  5. iGoogle 会要求用户确认是否想要添加小工具。
  6. 用户确认想要添加小工具后,该预先批准的请求令牌将被交换为 OAuth 访问令牌,并且,该用户的数据立即可用。

执行预先批准的网址

OAuth 预先批准的网址需要创建一个预先批准的 OAuth 请求令牌,然后将用户重定向至 addmodule 网址以使用该令牌。如何创建一个预先批准的 OAuth 令牌取决于 OAuth 服务提供商的详细信息,但是,预先批准的令牌应当具备以下属性:

  • 令牌应识别其数据正被访问的用户。
  • 令牌应当在短期内有效,仅在几分钟内可用。
  • 令牌应为一次性的,这样,这些令牌一旦被用,任何窃取令牌的人都无法获取用户数据的访问权限。

一旦服务提供商站点创建了预先批准的请求令牌,该站点应当将用户重定向至 iGoogle addmodule 网址,该网址包含请求中的预先批准的请求令牌(以及可选的令牌机密)。该重定向如下所示:

http://www.google.com/ig/add?moduleurl=<URL OF GADGET>&up_request_token=<REQUEST TOKEN>&up_request_token_secret=<REQUEST TOKEN SECRET>

像所有重定向网址一样,查询参数必须是已编码的网址。

up_ 为前缀的参数被视为小工具的用户使用偏好。up_rt 参数的值将变成小工具中 rt 用户使用偏好的值。 请注意,对于这些用户使用偏好,iGoogle 只允许使用字母数字字符、句点 (.) 字符和空格。如果您的请求令牌或请求令牌机密包含其他字符,您可能需要先对它们进行编码,然后 iGoogle 才会允许您自动将其添加到小工具。

添加用户使用偏好

小工具需要稍做更改才能使用预先批准的请求令牌。首先,应当更新小工具规范以使其具备 setprefs 功能,并声明请求令牌和请求令牌机密的两个新用户使用偏好

...
<ModulePrefs title="Preapproved OAuth Gadget" scrolling="true">
  <Require feature="setprefs" />
    <OAuth>
      <Service>
        ...
      </Service>
    </OAuth>
 </ModulePrefs>
 <UserPref name="request_token" required="false" datatype="hidden"></UserPref>
 <UserPref name="request_token_secret" required="false" datatype="hidden"></UserPref>
 ...

一旦向小工具规范中添加了这些参数,则小工具脚本需要将新用户使用偏好传递给 gadgets.io.makeRequest() 函数。例如:

params.AUTHORIZATION = "OAUTH";
var prefs = new gadgets.Prefs();
var requestToken = prefs.getString("request_token");
if (requestToken !== "") {
  // We have a preapproved request token
  params.OAUTH_REQUEST_TOKEN = requestToken;
  params.OAUTH_REQUEST_TOKEN_SECRET = prefs.getString("request_token_secret");
  // Preapproved request tokens are only good once
  prefs.set("request_token", "");
  prefs.set("request_token_secret", "");
}
gadgets.io.makeRequest(url, callback, params);
 

代码首先检查是否应当使用 request_token 用户使用偏好:

var prefs = new gadgets.Prefs();
var requestToken = prefs.getString("request_token");
if (requestToken !== "") {
  ...
}

如果 request_token 用户使用偏好不为空,小工具将尝试通过设置值 params.OAUTH_REQUEST_TOKENparams.OAUTH_REQUEST_TOKEN_SECRET 使用预先批准的请求令牌:

if (requestToken !== "") {
  // We have a preapproved request token
  params.OAUTH_REQUEST_TOKEN = requestToken;
  params.OAUTH_REQUEST_TOKEN_SECRET = prefs.getString("request_token_secret");
  // Preapproved request tokens are only good once
  prefs.set("request_token", "");
  prefs.set("request_token_secret", "");
}

预先批准的请求令牌只能使用一次。要防止小工具再次尝试使用该使用偏好,应当在将使用偏好复制到 makeRequest() 参数中后通过调用 prefs.set() 清除使用偏好:

prefs.set("request_token", "");
prefs.set("request_token_secret", "");

最后一步是使用已更新的参数调用 gadgets.io.makeRequest()。如果预先批准的请求令牌有效,小工具将获得用户数据的访问权限,并可以避免通过打开弹出窗口来获取用户的批准。

即使您希望小工具使用这些预先批准的请求令牌获取数据的访问权限,您也应该支持用户通过弹出窗口授予权限。如果用户从 iGoogle 目录添加您的小工具,将不会存在任何预先批准的请求令牌。

其他示例

以下是其他一些示例 OAuth 小工具,可帮助您入门:

高级 OAuth 技巧

Google 正致力于发展此功能的增强功能。如果您想了解有关这些增强功能的信息以及其他可用的 OAuth 高级技巧,请参阅 Google 维护的高级 OAuth 技巧站点。