@Lenciel

We Are Men

Yes, we’re men. Men is what we are.

Oauth: Introduction and Tips

目录

Terminology / Reference

  • Signed / Signature
    • 由一系列的 HTTP request 元素组成的一个字符串。

    这里说的 HTTP request 元素一般包括了Request Method & URL Query & Parameters, 并且这些元素用(consumer_secret & token_secret)组成的 key 进行了加密。In some cases this may be the key, plaintext, or may use simply the consumer_secret, for RSA encryption.

  • Consumer Secret
    • 由应用提供出来作为 OAuth 握手的保密的 token
  • Consumer Key
    • 由应用随 Consumer Secret 一起提供,用来做 OAuth 的握手的 key
  • Nonce / UID
    • 通常32个字符长度,由a-zA-Z0-9中的字符生成的一个独一无二的 ID
  • OAuth Token
    • 由服务器或者是其他 Endpoint 发送的,用来作为 Request 或者 Access 的 token
  • OAuth Token Secret
    • 作为特定 token 的响应被发送,用来进行 exchanges / refreshing.
  • Query
    • URL 中的用 ? 符号隔开的一些键值对部分。键和值之间用 = 分隔,例如 ?query=looks&like=this
  • Parameter / Argument
    • 一般指 Query 中的键,比如 oauth_token="helloWorld"oauth_token 被称为一个 parameter 或者 argumenthelloWorld 则是它的值。
  • PLAINTEXT
    • 使用普通文本作为 Signature 的保存方式
  • HMAC-SHA1 1
    • Signature 的保存方式,基于 Secure Hash Algorithm(1),是加密的文本
  • RSA-SHA1 2
    • Signature 的保存方式,基于 Secure Hash Algorithm(1),由一对 public/ private 的 key 组成。
  • Service
    • 服务方指信息的提供者,在 OAuth 语境中,Facebook/Twitter/腾讯/新浪等就是一个个的 Service
  • Signature Method
    • OAuth 接受的加密算法,包括: PLAINTEXT, HMAC-SHA1 和 RSA-SHA1
  • Value
    • 键值对中的值
  • URL / URI
    • URL 是 URI 的一种,你应该懂的吧

Signed Requests

本章描述的是OAuth 1.0

对 request 签名(Sign)是非常重要的,本章主要解释签名流程和各个参数的作用。从数据流上来说,签名的过程就是把应用所获取和所生成的信息放到一个地方去:可以是通过 OAuth 头,也可以是 Query 字符串。

Signature Base String

签名的基本组成有:request 的 Method,request 的 URL (如果是 OAuth Echo则是 credentials uri) 和 request 的 Query String。没有加密前它看起来会是下面这样 (例子来自 twitter):

POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521
Signing Key

上面的 signature base 字符串会被加密。加密时用到的 key 就是 signing key , 是由 OAuth Consumer SecretToken Secret& 字符连接起来组成的:

kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE

Note: 如果是使用 RSA 或者 xAuth, signing key 可能只有 Consumer Secret 部分外加一个可以省略的 & 。 更多相关信息可以从 mashape-oauth/lib/oauth.js 的 233行和 238行了解。


Signature编码

有了 signature base stringsignature key 之后,就需要从这两个字符串中提取信息完成编码。编码的方式共有三种:PLAINTEXT, HMAC, 或者是 RSA。

PLAINTEXT

没有任何编码,直接传 Signature Key

HMAC-SHA1

这种编码方式下,二进制格式的 key 被用来更新 base,然后被编码成 Base64 放到signature string里面:

tnnArxj06cWHq44gCs1OSKk/jLY=
RSA-SHA1

这种更复杂但是更安全的方式是根据Signature Base来生成一对private keypublic key进行加密。

然后在服务端,会用 key 来验证编码后的 oauth_signature 字段。

Note: mashape-oauth/tests/oauth.js 的第74行说明了如何使用生成的private key来对signature base进行编码。

OAuth请求头

OAuth 请求头包括了oauth_signatureoauth_signature_method 等参数及值。这些oauth_* 参数一般会用名字和其他复杂的规则排序,相互之间用 , 或者是空格分隔。下面是一个取得 Twitter 的 Request Token 的例子:

POST /oauth/request_token HTTP/1.1
User-Agent: themattharris' HTTP Client
Host: api.twitter.com
Accept: */*
Authorization:
        OAuth oauth_callback="http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F",
              oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
              oauth_nonce="ea9ec8429b68d6b77cd5600adbbb0456",
              oauth_signature="F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D",
              oauth_signature_method="HMAC-SHA1",
              oauth_timestamp="1318467427",
              oauth_version="1.0"

oauth_callback 是整个认证过程完毕后的回调地址,默认情况下服务方会提供一个 oauth_callback_confirmed token。

下面是一个response的例子:

HTTP/1.1 200 OK
Date: Thu, 13 Oct 2011 00:57:06 GMT
Status: 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 146
Pragma: no-cache
Expires: Tue, 31 Mar 1981 05:00:00 GMT
Cache-Control: no-cache, no-store, must-revalidate, pre-check=0, post-check=0
Vary: Accept-Encoding
Server: tfe

oauth_token=NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0&
oauth_token_secret=veNRnAWe6inFuo8o2u8SLLZLjolYDmDP7SzL0YfYI&
oauth_callback_confirmed=true

可以看到, 200 response 以及 oauth_token, oauth_token_secretoauth_callback_confirmed 参数表示这次 OAuth 请求是成功的。接下来你就可以用 oauth_token_secret 来生成你的签名作为 access token 然后使用 oauth_token参数发送出去进行认证。

一般来说, oauth_token 发送的格式是 ?oauth_token=[token] ,在认证的 endpoint 收到之后会进行一次 3-Legged OAuth 1.0a 并返回 oauth_tokenoauth_verifier。返回的参数也会被用到 Access Token request3中去。

OAuth 1.0a (one-legged)

一般被叫成 two-legged 的 OAuth 其实是只有一步的。

  1. 应用发送一个 signed request 到服务提供商,request 里包括:
    • oauth_token Empty String
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
  2. 服务提供商验证后,提供相应的资源供应用访问。
  3. 应用请求可以访问的资源。

这种最简单的方式当然也是安全是漏洞最多的方式。通常如果你都已经想到要用 OAuth 了,就不该考虑这么简陋的方式了。


Note: Google 要求请求里面要带一个不是oauth开头的参数叫 xoauth_requester_id4,这个要求在 OAuth2 里面过期了。


OAuth 1.0a (two-legged)

真正的two-legged是 1.0a 版本的 OAuth。

  1. 应用发送一个 signed request 到服务提供商请求一个 Request Token,request 里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version 可选
  2. 服务提供商返回 Request Token:
    • oauth_token
    • oauth_token_secret
    • … 其他额外的参数
  3. 应用再次发送signed request 来用Request TokenAccess Token,请求中包括:
    • oauth_token Request Token
    • oauth_consumer_key
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version
  4. 服务提供商返回 Access TokenToken Secret,整个 payload 的参数和第二步一样主要是oauth_tokenoauth_token_secret
  5. 应用使用oauth_tokenoauth_token_secret 来访问被权限保护的资源。

这里我们可以看到安全性被增强了,而应用开发者不会有太多的工作,用户更是完全觉察不到。

OAuth 1.0a (three-legged)

最完备同时也是带来最多麻烦的一个版本,特别是引入了需要用户操作来确认的部分,增加了开发和交互上的复杂度。一开始推出的时候,让很多用户感到不知所措。

  1. 应用发送一个 signed request 到服务提供商请求一个 Request Token,request 里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
    • oauth_callback
  2. 服务提供商返回 Request Token:
    • oauth_token
    • oauth_token_secret
    • oauth_callback_confirmed
    • … Additional Parameters / Arguments
  3. 返回包含下面参数的 url
    • oauth_token
  4. 弹出窗口访问返回的 url,要求用户授权
  5. 用户授权
  6. 返回到应用中,并保存下面的参数:
    • oauth_token
    • oauth_verifier
  7. 应用发送signed request,用Request Token / VerifierAccess Token, 请求中包括:
    • oauth_token Request Token;
    • oauth_consumer_key
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version
    • oauth_verifier
  8. 服务提供商返回 Access TokenToken Secret,整个 payload 的参数和第二步一样主要是oauth_tokenoauth_token_secret
  9. 应用使用oauth_tokenoauth_token_secret 来访问被权限保护的资源。

Note:第6步 如果 oauth_verifier 没有被发送,那么就会认证失败。只有极少数的实现可以接受只发送oauth_token,这样的服务提供商被认为是不完整的实现了 OAuth 1.0a 3-Legged。


OAuth 1.0a (Echo)

非主流的一种实现,但是确实是存在的:发明者是 Twitter 的 Raffi。这种实现允许在首次发送的请求 token 里面多带两个 header,这样可以通过代理的方式在代理服务商那里对原始服务商的用户进行认证。

  1. 应用发送一个 signed request 到代理服务提供商,request 里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
    • oauth_callback

    还包括额外的 header:

    • X-Auth-Service-Provider
    • X-Verify-Credentials-Authorization
  2. 代理服务商拿到额外的 header 信息到原始服务商认证
  3. 代理服务商认证通过后,可以返回受限资源的 url 给应用。

OAuth 1.0a (xAuth)

xAuth 是一种桌面程序或者手机程序(没有使用 webview 等控件不能完成完整流程的程序)使用的 OAuth 方式。它通过提供用户的emailpassword给服务器提供商来换取access token

这种方式返回的一般是具有只读性质的 access token,并且这种 token 能操作的资源也是有限的。比如 Twitter 的 DM(类似私信)就不能使用 xAuth 而必须用完整的three-legged流程获取 token 才能取到。

  1. 应用请求用户的 Credentials
  2. 应用发送一个 signed request 到服务提供商请求一个 Access Token,request 里包括:
    • oauth_consumer_key
    • oauth_timestamp
    • oauth_nonce
    • oauth_signature
    • oauth_signature_method
    • oauth_version Optional
    • oauth_callback

    额外还包括:

    • x_auth_mode = client_auth
    • x_auth_username
    • x_auth_password
    • x_auth_permission 可选;5
  3. 服务提供商验证用户的 Credentials 之后返回 Access Token
    • oauth_token
    • oauth_token_secret
  4. 应用使用Access Token来访问被权限保护的资源。

OAuth 2 (two-legged)

目前为止最容易解释的一种流程,也被称为 Client Credentials 认证流程6,也有一种 Resource Owner Password 流程属于这类。

Client Credentials

  1. 应用发送请求给服务提供商:
    • grant_type = client_credentials

如果不是用的 Authorization header:

  • client_id
  • client_secret
    1. 应用服务商返回 Access Token等信息:
    • access_token
    • expires_in
    • token_type

Resource Owner Password

基本上就是 OAuth 1.0a Echo 流程,但是去掉了签名等复杂的部分。

  1. 应用向 resource owner(一般就是用户)请求 credentials
    • username
    • password
  2. 应用向服务提供商发送 request,请求内容为:
    • grant_type = password
    • username
    • password

    其格式如下:

     grant_type=password&username=my_username&password=my_password
    

    如果不是用的 Authorization header, 下面的也需要被放到 request 里面:

    • client_id
    • client_secret

    整个加起来回是:

     grant_type=password&username=my_username&password=my_password&client_id=random_string&client_secret=random_secret
    
  3. 应用服务商返回 Access Token等信息:
    • access_token
    • expires_in
    • token_type

OAuth 2 (three-legged)

同样去掉了很多复杂的步骤。

  1. 应用把用户引导到认证页面:
    • client_id
    • redirect_uri
    • response_type7
    • state 可选; 防止 CSRF8
    • scope 可选; 你可以获取的资源范围

    一个例子(为了可读性没有进行 Encode):

    https://oauth_service/login/oauth/authorize?client_id=3MVG9lKcPoNINVB&redirect_uri=http://localhost/oauth/code_callback&scope=user
    
  2. 用户登录到服务中,批准应用申请权限。
  3. 服务提供商把用户重定向到 redirect_url 并带上:
    • code
    • state
  4. 应用使用 code 用来请求 Access Token:
    • client_id
    • client_secret
    • code
    • redirect_uri 可选;9
    • grant_type = "authorization_code" 9
  5. 如果 client_idclient_secret 正确,服务提供商调用回调到 redirect_url 返回 access_token等信息:
    • access_token
    • expires_in
    • refresh_token
  6. 应用保存 access_token 并使用。
    • 一般来说保存到 session 或者是 cookie 里,然后放在 Authorization: [Bearer] access_token header 里面用,其中[Bearer]Header Authorization Bearer Name,如Bearer, OAuth, MAC等。

有趣的事实: 有些 RFC 里面的规定,比如 scope 的分隔符用空格等,根本没有人遵守。所以开发者根本不知道 API 会在下个版本变成什么样子。


OAuth 2 (refresh token)

在 OAuth2 中,access_token一般是有有效期的。一个过期的 token 被使用时,服务器会返回一个 token 过期的错误,并带上refresh_token。应用使用refresh token获取新的access_token会比前面描述的流程简单得多。

  1. 发送请求到服务提供商的Refresh Token URI:
    • grant_type = "refresh_token"
    • scope 可选; 更新时不能指定之前没有的 scope
    • refresh_token
    • client_id
    • client_secret
  2. 服务提供商验证参数后返回:
    • access_token
    • issued_at

Tips & Tricks

生成Access Token和Refresh Key

最好使用 uuid,也就是固定长度的随机字符组成的字符串。

例子

var OAuth = require('mashape-oauth').OAuth,
    access_token = OAuth.nonce(/* Length, Default 32 */);

References

  1. Authorizing with OAuth - Flickr Documentation
  2. OAuth on Bitbucket - Bitbucket Documentation
  3. OAuth Documentation - Twitter Documentation
  4. OAuth Extended Flows
  5. 2-Legged OAuth - OAuth-PHP
  6. OAuth for Consumer Requests
  7. OAuth Example - term.ie
  8. OAuth 1.0 Guide - Heuniverse
  9. OAuth 1.0a Diagram
  10. OAuth Wiki
  11. 2-Legged OAuth 1.0 & 2.0 - DZone
  12. OAuth & OAuth2 - Google Documentation
  13. What is 2-legged OAuth? - Nerdbank
  14. List of Service Providers - Wikipedia
  15. OAuth Echo - mobypicture
  16. OAuth Echo - Twitter
  17. Advanced API - Vimeo Developer();
  18. About xAuth - Twitter xAuth Documentation
  19. Implementing Sign-in - Twitter Sign-in Documentation
  20. RFC6749 - IETF
  21. Web Application Flow - Github OAuth2
  22. OAuth2 Quickstart - Salesforce
  23. Authentication Mechanisms - Geoloqi
  24. Understanding Web Server OAuth Flow - Salesforce
  25. CSRF & OAuth2 - Springsource
  26. OAuth v2-31 - IETF
  27. Resource Owner Flow - Hybris