Yes, we’re men. Men is what we are.
Oauth: Introduction and Tips
目录
- Terminology / Reference
- OAuth 1.0a (one-legged)
- OAuth 1.0a (two-legged)
- OAuth 1.0a (three-legged)
- OAuth 1.0a (Echo)
- OAuth 1.0a (xAuth)
- OAuth 2 (two-legged)
- OAuth 2 (three-legged)
- OAuth 2 (refresh token)
- Tips & Tricks
- References
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 theconsumer_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
.
- 作为特定 token 的响应被发送,用来进行
- Query
- URL 中的用
?
符号隔开的一些键值对部分。键和值之间用=
分隔,例如?query=looks&like=this
- URL 中的用
- Parameter / Argument
- 一般指 Query 中的键,比如
oauth_token="helloWorld"
中oauth_token
被称为一个parameter
或者argument
而helloWorld
则是它的值。
- 一般指 Query 中的键,比如
- 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 Secret
和 Token Secret
用 &
字符连接起来组成的:
kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE
Note: 如果是使用 RSA 或者 xAuth, signing key
可能只有 Consumer Secret
部分外加一个可以省略的 &
。 更多相关信息可以从 mashape-oauth/lib/oauth.js 的 233行和 238行了解。
Signature编码
有了 signature base string
和 signature key
之后,就需要从这两个字符串中提取信息完成编码。编码的方式共有三种:PLAINTEXT, HMAC, 或者是 RSA。
PLAINTEXT
没有任何编码,直接传 Signature Key
HMAC-SHA1
这种编码方式下,二进制格式的 key 被用来更新 base,然后被编码成 Base64
放到signature string
里面:
tnnArxj06cWHq44gCs1OSKk/jLY=
RSA-SHA1
这种更复杂但是更安全的方式是根据Signature Base
来生成一对private key
和public key
进行加密。
然后在服务端,会用 key 来验证编码后的 oauth_signature
字段。
Note: mashape-oauth/tests/oauth.js 的第74行说明了如何使用生成的private key
来对signature base
进行编码。
OAuth请求头
OAuth 请求头包括了oauth_signature
和 oauth_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_secret
和 oauth_callback_confirmed
参数表示这次 OAuth 请求是成功的。接下来你就可以用 oauth_token_secret
来生成你的签名作为 access token
然后使用 oauth_token
参数发送出去进行认证。
一般来说, oauth_token
发送的格式是 ?oauth_token=[token]
,在认证的 endpoint 收到之后会进行一次 3-Legged OAuth 1.0a
并返回 oauth_token
和 oauth_verifier
。返回的参数也会被用到 Access Token
request3中去。
OAuth 1.0a (one-legged)
一般被叫成 two-legged
的 OAuth 其实是只有一步的。
- 应用发送一个 signed request 到服务提供商,request 里包括:
oauth_token
Empty Stringoauth_consumer_key
oauth_timestamp
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
Optional
- 服务提供商验证后,提供相应的资源供应用访问。
- 应用请求可以访问的资源。
这种最简单的方式当然也是安全是漏洞最多的方式。通常如果你都已经想到要用 OAuth 了,就不该考虑这么简陋的方式了。
Note: Google 要求请求里面要带一个不是oauth
开头的参数叫 xoauth_requester_id
4,这个要求在 OAuth2 里面过期了。
OAuth 1.0a (two-legged)
真正的two-legged
是 1.0a 版本的 OAuth。
- 应用发送一个 signed request 到服务提供商请求一个
Request Token
,request 里包括:oauth_consumer_key
oauth_timestamp
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
可选
- 服务提供商返回
Request Token
:oauth_token
oauth_token_secret
- … 其他额外的参数
- 应用再次发送signed request 来用
Request Token
换Access Token
,请求中包括:oauth_token
Request Tokenoauth_consumer_key
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
- 服务提供商返回
Access Token
和Token Secret
,整个 payload 的参数和第二步一样主要是oauth_token
和oauth_token_secret
。 - 应用使用
oauth_token
和oauth_token_secret
来访问被权限保护的资源。
这里我们可以看到安全性被增强了,而应用开发者不会有太多的工作,用户更是完全觉察不到。
OAuth 1.0a (three-legged)
最完备同时也是带来最多麻烦的一个版本,特别是引入了需要用户操作来确认的部分,增加了开发和交互上的复杂度。一开始推出的时候,让很多用户感到不知所措。
- 应用发送一个 signed request 到服务提供商请求一个
Request Token
,request 里包括:oauth_consumer_key
oauth_timestamp
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
Optionaloauth_callback
- 服务提供商返回
Request Token
:oauth_token
oauth_token_secret
oauth_callback_confirmed
- … Additional Parameters / Arguments
- 返回包含下面参数的 url
oauth_token
- 弹出窗口访问返回的 url,要求用户授权
- 用户授权
- 返回到应用中,并保存下面的参数:
oauth_token
oauth_verifier
- 应用发送signed request,用
Request Token
/Verifier
换Access Token
, 请求中包括:oauth_token
Request Token;oauth_consumer_key
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
oauth_verifier
- 服务提供商返回
Access Token
和Token Secret
,整个 payload 的参数和第二步一样主要是oauth_token
和oauth_token_secret
。 - 应用使用
oauth_token
和oauth_token_secret
来访问被权限保护的资源。
Note: 在第6步 如果 oauth_verifier
没有被发送,那么就会认证失败。只有极少数的实现可以接受只发送oauth_token
,这样的服务提供商被认为是不完整的实现了 OAuth 1.0a 3-Legged。
OAuth 1.0a (Echo)
非主流的一种实现,但是确实是存在的:发明者是 Twitter 的 Raffi。这种实现允许在首次发送的请求 token 里面多带两个 header,这样可以通过代理的方式在代理服务商那里对原始服务商的用户进行认证。
- 应用发送一个 signed request 到代理服务提供商,request 里包括:
oauth_consumer_key
oauth_timestamp
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
Optionaloauth_callback
还包括额外的 header:
X-Auth-Service-Provider
X-Verify-Credentials-Authorization
- 代理服务商拿到额外的 header 信息到原始服务商认证
- 代理服务商认证通过后,可以返回受限资源的 url 给应用。
OAuth 1.0a (xAuth)
xAuth 是一种桌面程序或者手机程序(没有使用 webview 等控件不能完成完整流程的程序)使用的 OAuth 方式。它通过提供用户的email
和password
给服务器提供商来换取access token
。
这种方式返回的一般是具有只读性质的 access token,并且这种 token 能操作的资源也是有限的。比如 Twitter 的 DM(类似私信)就不能使用 xAuth 而必须用完整的three-legged
流程获取 token 才能取到。
- 应用请求用户的 Credentials
- 应用发送一个 signed request 到服务提供商请求一个
Access Token
,request 里包括:oauth_consumer_key
oauth_timestamp
oauth_nonce
oauth_signature
oauth_signature_method
oauth_version
Optionaloauth_callback
额外还包括:
x_auth_mode
=client_auth
x_auth_username
x_auth_password
x_auth_permission
可选;5
- 服务提供商验证用户的 Credentials 之后返回 Access Token
oauth_token
oauth_token_secret
- 应用使用
Access Token
来访问被权限保护的资源。
OAuth 2 (two-legged)
目前为止最容易解释的一种流程,也被称为 Client Credentials 认证流程6,也有一种 Resource Owner Password 流程属于这类。
Client Credentials
- 应用发送请求给服务提供商:
grant_type
=client_credentials
如果不是用的 Authorization
header:
client_id
client_secret
- 应用服务商返回
Access Token
等信息:
access_token
expires_in
token_type
- 应用服务商返回
Resource Owner Password
基本上就是 OAuth 1.0a Echo
流程,但是去掉了签名等复杂的部分。
- 应用向 resource owner(一般就是用户)请求 credentials
username
password
- 应用向服务提供商发送 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
- 应用服务商返回
Access Token
等信息:access_token
expires_in
token_type
OAuth 2 (three-legged)
同样去掉了很多复杂的步骤。
- 应用把用户引导到认证页面:
一个例子(为了可读性没有进行 Encode):
https://oauth_service/login/oauth/authorize?client_id=3MVG9lKcPoNINVB&redirect_uri=http://localhost/oauth/code_callback&scope=user
- 用户登录到服务中,批准应用申请权限。
- 服务提供商把用户重定向到
redirect_url
并带上:code
state
- 应用使用
code
用来请求Access Token
: - 如果
client_id
和client_secret
正确,服务提供商调用回调到redirect_url
返回access_token
等信息:access_token
expires_in
refresh_token
- 应用保存
access_token
并使用。- 一般来说保存到 session 或者是 cookie 里,然后放在
Authorization: [Bearer] access_token
header 里面用,其中[Bearer]
是Header Authorization Bearer Name
,如Bearer
,OAuth
,MAC
等。
- 一般来说保存到 session 或者是 cookie 里,然后放在
有趣的事实: 有些 RFC 里面的规定,比如 scope 的分隔符用空格等,根本没有人遵守。所以开发者根本不知道 API 会在下个版本变成什么样子。
OAuth 2 (refresh token)
在 OAuth2 中,access_token
一般是有有效期的。一个过期的 token 被使用时,服务器会返回一个 token 过期的错误,并带上refresh_token
。应用使用refresh token
获取新的access_token
会比前面描述的流程简单得多。
- 发送请求到服务提供商的
Refresh Token URI
:grant_type
="refresh_token"
scope
可选; 更新时不能指定之前没有的 scoperefresh_token
client_id
client_secret
- 服务提供商验证参数后返回:
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
- Authorizing with OAuth - Flickr Documentation
- OAuth on Bitbucket - Bitbucket Documentation
- OAuth Documentation - Twitter Documentation
- OAuth Extended Flows
- 2-Legged OAuth - OAuth-PHP
- OAuth for Consumer Requests
- OAuth Example - term.ie
- OAuth 1.0 Guide - Heuniverse
- OAuth 1.0a Diagram
- OAuth Wiki
- 2-Legged OAuth 1.0 & 2.0 - DZone
- OAuth & OAuth2 - Google Documentation
- What is 2-legged OAuth? - Nerdbank
- List of Service Providers - Wikipedia
- OAuth Echo - mobypicture
- OAuth Echo - Twitter
- Advanced API - Vimeo Developer();
- About xAuth - Twitter xAuth Documentation
- Implementing Sign-in - Twitter Sign-in Documentation
- RFC6749 - IETF
- Web Application Flow - Github OAuth2
- OAuth2 Quickstart - Salesforce
- Authentication Mechanisms - Geoloqi
- Understanding Web Server OAuth Flow - Salesforce
- CSRF & OAuth2 - Springsource
- OAuth v2-31 - IETF
- Resource Owner Flow - Hybris