教程:OAuth

OAuth 是一种开放的协议,旨在标准化桌面与网上应用程序访问用户个人数据的方式。OAuth 提供了一种机制,让用户授权访问私有数据,而不用共享它们的私有凭据(用户名/密码)。由于它的安全性以及存在一组标准库,许多网站已经开始启用 API 使用 OAuth。

这一教程将会引导您了解创建使用 OAuth 访问某个 API 的 Google Chrome 浏览器扩展程序的必要步骤,它提供了一个您可以在自己的扩展程序中重用的库。

这一教程使用 Google 文档列表数据 API 作为支持 OAuth 的 API 端点的例子。

要求

这一教程要求您具有编写 Google Chrome 浏览器扩展程序的一些经验,并且要求您对三方 OAuth 流程有一定的熟悉。尽管您不需要有 Google 文档列表数据 API(或者其他 Google 数据 API)的开发背景,但是理解这一协议可能有所帮助。

入门

首先,从位于 .../examples/extensions/oauth_contacts/ 的 Chromium 源代码树中将如下四个库文件复制到您的计算机上:

将这四个库文件放在您的扩展程序的根目录中(或者您的 JavaScript 代码存放的位置,然后在您的后台网页中按照以下顺序包含那两个 .js 文件:

<script type="text/javascript" src="chrome_ex_oauthsimple.js"></script>
<script type="text/javascript" src="chrome_ex_oauth.js"></script>
<script type="text/javascript" src="onload.js"></script>

您的后台网页将会管理 OAuth 流程。

扩展程序中的 OAuth 认证

如果您对 OAuth 协议比较熟悉,您能回想起来 OAuth 认证包括三个步骤:

  1. 获得初始的请求令牌
  2. 让用户为请求令牌授权
  3. 获得访问令牌

在扩展程序的环境中,这一流程遇到了一些问题。在服务提供商和应用之间无法建立使用者密钥/机密,在授权过程之后没有网上应用的 URL 让用户重定向。

幸运的是,Google 和另外几家公司正在开发用于已安装应用的 OAuth解决方案,这样您就可以从扩展程序的环境中使用。在已安装应用的 OAuth 认证过程中,使用者密钥/机密为'anonymous'/'anonymous',您还可以提供一个应用名称(而不是应用 URL),使用户授予访问权限。最终结果是一样的:您的后台页面请求初始令牌,打开新标签页进入授权页面,最后发出异步调用请求访问令牌。

设置代码

要初始化库,在后台网页中创建一个 ChromeExOAuth 对象:

var oauth = ChromeExOAuth.initBackgroundPage({
  'request_url': <OAuth 请求 URL>,
  'authorize_url': <OAuth 认证 URL>,
  'access_url': <OAuth 访问令牌 URL>,
  'consumer_key': <OAuth 消费者密钥>,
  'consumer_secret': <OAuth 消费者机密>,
  'scope': <要访问的数据范围。并不是所有 OAuth 提供商都使用它>,
  'app_name': <应用名称。并不是所有 OAuth 提供商都使用它>
});

如果是文档列表 API 和 Google 的 OAuth 端点,初始化代码如下所示:

var oauth = ChromeExOAuth.initBackgroundPage({
  'request_url': 'https://www.google.com/accounts/OAuthGetRequestToken',
  'authorize_url': 'https://www.google.com/accounts/OAuthAuthorizeToken',
  'access_url': 'https://www.google.com/accounts/OAuthGetAccessToken',
  'consumer_key': 'anonymous',
  'consumer_secret': 'anonymous',
  'scope': 'https://docs.google.com/feeds/',
  'app_name': '我的 Google 文档扩展程序'
});

要使用 OAuth 库,您必须在扩展程序的清单文件中声明 "tabs" 权限,您还必须声明您正在使用的网站,包括请求 URL、认证 URL、访问 URL,如果有必要的话还有区域 URL。例如:

"permissions": [ "tabs", "https://docs.google.com/feeds/*",
  "https://www.google.com/accounts/OAuthGetRequestToken",
  "https://www.google.com/accounts/OAuthAuthorizeToken",
  "https://www.google.com/accounts/OAuthGetAccessToken"
]

获取和认证请求令牌

一旦您设置好了后台网页后,就能调用 authorize() 函数,开始 OAuth 认证,使用户重定向至 OAuth 提供商。客户端库抽象了这一过程的大部分,所以您需要做的只是向 authorize() 函数传递一个回调函数,然后会打开一个新标签页,并重定向。

oauth.authorize(function() {
  // ... 准备获取个人数据...
});

您不需要提供任何额外的逻辑来存储令牌和机密,因为该库已经在浏览器的 localStorage 中储存了这些值。如果库已经为当前作用域储存了访问令牌,则不用再打开新标签页。在任何一种情况下,都会调用回调函数。

发送已签名的 API 请求

一旦您指定的回调函数开始执行,就能调用 sendSignedRequest() 函数,向您的 API 发送已签名的请求。sendSignedRequest() 有三个参数:URI、回调函数和可选的参数对象。回调函数有两个参数:响应文本以及用来发出请求的 XMLHttpRequest 对象。

这一例子发送一个 HTTP GET 请求:

function callback(resp, xhr) {
  // ... 处理文本响应 ...
};

function onAuthorized() {
  var url = 'https://docs.google.com/feeds/default/private/full';
  var request = {
    'method': 'GET',
    'parameters': {'alt': 'json'}
  };

  // 发送:GET https://docs.google.com/feeds/default/private/full?alt=json
  oauth.sendSignedRequest(url, callback, request);
};

oauth.authorize(onAuthorized);

一个更复杂的例子使用 HTTP POST 请求,如下列代码所示:

function onAuthorized() {
  var url = 'https://docs.google.com/feeds/default/private/full';
  var request = {
    'method': 'POST',
    'headers': {
      'GData-Version': '3.0',
      'Content-Type': 'application/atom+xml'
    },
    'parameters': {
      'alt': 'json'
    },
    'body': '要发送的数据'
  };

  // 发送:POST https://docs.google.com/feeds/default/private/full?alt=json
  oauth.sendSignedRequest(url, callback, request);
};

默认情况下,sendSignedRequest() 函数在 URL 中(通过调用 oauth.signURL())发送 oauth_* 参数。如果您希望在 Authorization 头信息中发送 oauth_* 参数(或者需要直接访问已生成的头信息),请使用 getAuthorizationHeader()。它的参数包括 URL、HTTP 方法以及可选的 URL 查询参数对象,表示为键/值对。

如下是上面所说内容的一个例子,使用 getAuthorizationHeader() 和一个 XMLHttpRequest对象:

function stringify(parameters) {
  var params = [];
  for(var p in parameters) {
    params.push(encodeURIComponent(p) + '=' +
                encodeURIComponent(parameters[p]));
  }
  return params.join('&');
};

function onAuthorized() {
  var method = 'POST';
  var url = 'https://docs.google.com/feeds/default/private/full';
  var params = {'alt': 'json'};

  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function(data) {
    callback(xhr, data);
  };
  xhr.open(method, url + '?' + stringify(params), true);
  xhr.setRequestHeader('GData-Version', '3.0');
  xhr.setRequestHeader('Content-Type', 'application/atom+xml');
  xhr.setRequestHeader('Authorization', oauth.getAuthorizationHeader(url, method, params));

  xhr.send('Data to send');
};

示例代码

使用这些技术的示例扩展程序在 Chromium 源代码树的如下位置可用: