OpenID 认证 1.1
Abstract
OpenID 认证提供了证明最终用户拥有其身份标识 URL 的方法。而不通过密码、邮件地址或者任何他们不想要的。
OpenID 是完全分散式的,也就是说任何人都可以选择成为消费者或者身份识别提供方而不需要去登记或者被一个中央当局批准。
最终用户可以选择他们喜欢的身份提供方并且可以任意地在不同的提供方之间移动切换。
在协议中也没有对 JavaScript 和现代浏览器的要求,认证方案允许用 AJAX 样式的设定,来使得最终用户不需要离开消费者页面。
OpenID 认证规范不提供任何机制来交换数据, 消费者可以从各种公开场获得最终用户的信息,比如下面的这些:
(FOAF, RSS,
Atom, vCARD, 等)。 扩展则提供了一个交换数据的机制。
Table of Contents
1.
符号
2.
术语
3.
概述
3.1.
将 HTML 文档转换成标识
3.1.1.
委派认证
3.2.
提交声称的身份标识
3.3.
消费者站点抓取身份标识
3.4.
智能模式对沉默模式
3.5.
消费者核实身份标识
4.
方式
4.1.
associate
4.1.1.
请求参数
4.1.2.
返回参数
4.1.3.
其他注意事项
4.2.
checkid_immediate
4.2.1.
请求参数
4.2.2.
返回参数
4.2.3.
特别注意
4.3.
checkid_setup
4.3.1.
请求参数
4.3.2.
返回参数
4.3.3.
特别注意
4.4.
check_authentication
4.4.1.
Request Parameters
4.4.2.
返回参数
4.4.3.
特别注意
5.
安全考虑
Appendix A.
默认值
Appendix A.1.
Diffie-Hellman P 值
Appendix B.
Error Responses
Appendix C.
键-值格式
Appendix D.
限制
Appendix E.
其它
6.
规范参考
§
Authors' Addresses
1.
符号
本文中所使用的关键词“必须”、“不能”、“必需的”、“应”、“不得”、“应该”、
“不应该”、“建议”、“可能”和“可选”由 [RFC2119] (Bradner, B., “Key words for use in RFCs to Indicate Requirement Levels,” .) 解释。
2.
术语
- 最终用户(End User):
- 想向消费者证明他们的身份的实际人类用户。
- 身份标识(Identifier):
- 身份标识只是一个 URL。OpenID
认证协议的整个流程就是证明一个最终用户拥有一个 URL。
- 声称的身份标识(Claimed Identifier):
- 最终用户声称自己拥有的
但尚未被消费者验证的身份标识。
- 已验证的身份标识(Verified Identifier):
- 最终用户已经向消费者
证明了自己拥有的身份标识。
- 消费者(Consumer):
- 想要证明最终用户拥有其声
称的身份标识的 Web 服务。
- 身份标识提供方(Identity Provider):
- 又称“IdP”或者“服务
器”。消费者为了证明最终用户拥有其声称的身份标识,所联系的 OpenID 认证服务器。
最终用户如何向身份标识提供方提供证据不在 OpenID 认证范畴。
- 用户代理(User-Agent):
- 最终用户的 Web 浏览器。特殊插件
或 JavaScript 支持不是必需的。
3.
概述
3.1.
将 HTML 文档转换成标识
为了让消费者知道身份标识的提供方,最终用户必须在他们的 URL 所指的 HTML 文
档的 HEAD 部分增加一些标记。HTML 文档和最终用户的身份标识提供方不一定需要是同
一台主机;身份标识 URL 和身份标识提供方可以是两个完全分开的服务。
要使用 http://example.com/ 作为最终用户的身份标识 http://openid.example.com
作为他们的身份标识提供方,将下面的标签加入到身份标识 URL 所指的 HTML 文档的 HEAD 部分。
<link rel="openid.server"
href="http://openid.example.com/">
3.1.1.
委派认证
如果最终用户的主机不能运行一个身份标识服务器,或者最终用户希望使用一个不
同的主机,他们就需要委派认证。例如,他们想使用自己的站点 http://www.example.com/
作为身份标识,并不是说要求他们自己运行一个身份标识服务器。
如果他们有一个 LiveJournal 帐号(比如“exampleuser”),并知道 LiveJournal
提供了一个 OpenID 的身份标识服务器,也就是说他们了拥有身份标识
http://exampleuser.livejournal.com/,那么就可以把他们的认证委派给
LiveJournal 的身份标识服务器。
所以,他们使用 www.exampoe.com 作为他们的身份标识,但消费者实际上是
通过 http://www.livejournal.com/openid/server.bml 这个身份标识服务器验证的
http://exampleuser.livejournal.com/,只需要将下面的标签加入到他们的身份标识
的 URL 所指的 HTML 文档的 HEAD 部分。
<link rel="openid.server"
href="http://www.livejournal.com/openid/server.bml">
<link rel="openid.delegate"
href="http://exampleuser.livejournal.com/">
现在,当消费者看到那个标签,它就会和
http://www.livejournal.com/openid/server.bml
联系询问最终用户是否是 exampleuser.livejournal.com。
这个功能主要的好处是让最终用户在服务商发生变化时可以保证他们的身份标识不发生变化;
他们只需要修改一下委派对象即可。
3.1.2.
注意
- 所定义的 openid.server URL 可以包含查询参数并且必须正确地添加参数。
比如,不要添加第二个问号(?)如果已经有一个问号(?)存在了。
- openid.server 和 openid.delegate URL 必须是一个绝对的 URL 地址。
消费者不应尝试去处理相对的 URL 地址。
- openid.server 和 openid.delegate URL 地址不能包含
&、 <、 > 和 "。 其他的会导致 HTML 文档
的不正确的字符或者不能以当前文档编码显示的字符必须用
[RFC2396] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) 中所描绘的 %xx 的方式来转义。
3.2.
提交声称的身份标识
继续这个例子,最终用户访问一个支持 OpenID 认证的消费者站点。消费者呈现一个表单让他们输入身份标识 URL。
例如:
----------------------------------
|[徽标]example.com | [登录按钮]
----------------------------------
3.2.1.
注意
- 建议每个消费者都在让最终用户输入身份标识 URL
的表单的输入框的开始放入一个这样的
徽标。
- 最终用户不必输入身份标识 URL 的前缀“http://”或者最后的的“/”。
消费者必须格式化身份标识 URL,跟随重定向,记录最终的 URL。
最终的、标准的 URL 才是最终用户的身份标识 URL。
- 表单字段的名字推荐使用“openid_url”,这样用户代理可以自动完成身份标识 URL 的输入,
如同在电子商务系统中常常使用“address1”和“address2”那样。
3.3.
消费者站点抓取身份标识
现在消费者站点需要去抓取用户声称的身份标识所指的文档。然后消费者解析其 HEAD 部分,
找到 “openid.server” 和可选的“openid.delegate” 定义。
3.3.1.
注意
- 最终用户可能恶意地让消费者去链接到一个内部网络、tarpit等。
因此建议消费者使用安全的 HTTP 类库,
像 LWPx::ParanoidAgent ,
来保护以避免此类攻击。
- 消费者必须实现对委派 (委派认证)的支持。
3.4.
智能模式对沉默模式
OpenID 认证同时支持“智能模式”和“沉默模式”来容纳不同的消费者。
一个智能的消费者用保存状态信息的方式以便在开始阶段完成少许的工作。
一个“沉默模式”的消费者则是完全的无状态的,但是需要一个额外的 HTTP 请求。
3.4.1.
智能模式注意事项
- 如果消费者尚未缓存一个共享密钥,那么建议消费者首先提交一个
关联请求 (associate)
到最终用户的身份标识提供者并请求一个共享密钥。
这个共享密钥用来在将来的身份标识检查中作为 HMAC-SHA1 密钥,知道密钥过期。
- 共享密钥可以以纯文本或者用 Diffie-Hellman-negotiated 加密交换。
注意,如果采用了 Diffie-Hellman 加密交换,那么它只能用在关联(associate)模式。
checkid_immediate (checkid_immediate)
和 checkid_setup (checkid_setup)
模式假设消费者已经得到了一个共享密钥,不管它是通过什么方法得到的。
3.5.
消费者核实身份标识
消费者现在可以构建 URL 给身份提供者checkid_immediate (checkid_immediate) (或
checkid_setup (checkid_setup))并发送用户代理给它。
由用户代理发回最终用户的 cookies 或者其他的一些登录凭证给身份标识提供者。
身份标识提供者完成它的工作后,添附它的回应到参数 openid.return_to 指定的 URL,
并引导用户代理返回到消费者。
4.
方式
4.1.
associate
- 描述:在消费者和身份识别提供方之间建立一个共享密钥。
- HTTP 方法: POST
- 流程: 消费者 -> 身份识别提供方 -> 消费者
4.1.1.
请求参数
-
openid.mode
值: "associate"
-
openid.assoc_type
值: 你喜欢的关联类型
默认: "HMAC-SHA1"
注: 任意;当前只有这一个值。
-
openid.session_type
值: 空白或 “DH-SHA1”
默认: 空白。 (明文)
注: 建议采用 DH-SHA1 方式去加密共享密钥。
-
openid.dh_modulus
值: base64(btwoc(p))
注: 参见 Appendix A.1 (Diffie-Hellman P 值) 中的默认 p 值。
-
openid.dh_gen
值: base64(btwoc(g))
默认: g = 2
注: 仅在使用 DH-SHA1 session_type 时。当指定了 openid.dh_modulus 时,必须指定。
-
openid.dh_consumer_public
值: base64(btwoc(g ^ x mod p))
注: 在使用 DH-SHA1 session_type 时必需指定该值。
4.1.2.
返回参数
返回格式: 键值对
-
assoc_type
值: 返回值的关联类型。
注: 当前唯一的模式是 HMAC-SHA1,所有的消费者必须支持这个模式。
当缓存的时候,消费者必须把 assoc_handle 映射到它自己的密钥和 assoc_type.
-
assoc_handle
值:为将来的交换提供的关联句柄。
注: 消费者不能在 expires_in 值对应的时间后重复利用关联句柄。
-
expires_in
值: 关联据并在 base10 ASCII 可用的秒数。
-
session_type
值: 身份标识提供方选择的加密模式。可以是空白、 "DH-SHA1"、或者不设置该值。
-
dh_server_public
值: base64(btwoc(g ^ y mod p))
描述: 如果使用了 DH-SHA1,身份标识提供方的 Diffie-Hellman 公钥 (Rescorla, E., “Diffie-Hellman Key Agreement Method,” .) [RFC2631]
-
enc_mac_key
值: base64(SHA1(btwoc(g ^ (xy) mod p)) XOR secret(assoc_handle))
描述: 如果使用了 DH-SHA1,加密过的共享密钥。
-
mac_key
值: base64(secret(assoc_handle))
描述: 如果没有使用 DH-SHA1,明文格式的共享密钥。
4.1.3.
其他注意事项
- 消费者可以请求服务器 DH-SHA1 加密,并得到一个纯文本的密钥。
如果你觉得这很麻烦,就不用使用这个句柄,而使用 dumb 模式。
如果默认嗅探到了纯文本的密钥,没有关系,因为你从未接受查询使用那个关联句柄。
如果身份提供方可能受到什么限制而无法使用 DH-SHA1,
使用 dumb 模式依然是安全的, if not a little slower.
- 如果身份标识提供方选择服务器私钥 1 <= y < p-1。公用的 DH-SHA1 密钥是
g ^ xy mod p = (g ^ x) ^ y mod p = (g ^ y) ^ x mod
p。要了解更多信息,请参考 Crypt::DH docs。
- 下面的 mac_key 的长度必须和 H 的输出一样, 哈希函数 - 在这种场合, 对DH-SHA1, 160
位 (20 字节)。
- 如果身份表示提供方不支持 DH-SHA1,他们将会忽略请求中的 DH-SHA1 字段并当作一个没有 DH-SHA1 的请求来响应。
- 当使用 DH-SHA1 时, 则结果 key 应该当作二进制字符串来处理。
- 多数整数被表示为 big-endian 的补全记法,Base64 编码。换句话说,
btwoc 是采取 bigint 并返回一个短一些的 big-endian 补全记法。
4.2.
checkid_immediate
- 描述:询问身份标识提供方,最终用户是否拥有其宣称的身份标识,立即得到一个“yes”或“can't say”的答案。
- HTTP 方法: GET
- 流程: 消费者 -> 用户代理 -> 身份标识提供方 -> 用户代理 ->
消费者
4.2.1.
请求参数
-
openid.mode
值: "checkid_immediate"
-
openid.identity
值: 宣称的身份标识
-
openid.assoc_handle
值: 来自 associate 请求的 assoc_handle。
注: 可选的;消费者必须使用
check_authentication 如果没有提供关联句柄或者身份标识提供方认为它不正确。
-
openid.return_to
值: 提供方应该将用户代理返回到的 URL。
-
openid.trust_root
值:提供方要求最终用户信任的 URL。
默认: return_to URL
可选的; 最终用户实际上将看到的批准的 URL。
4.2.2.
返回参数
返回格式: 查询字符串参数
4.2.2.1.
总是发送的
4.2.2.2.
在断言失败时发送
-
openid.user_setup_url
值: 重定向用户代理到的 URL,这样最终用户可以填入断言所需的必要资料。
4.2.2.3.
在断言肯定时发送
-
openid.identity
值: 已被核实的身份标识
-
openid.assoc_handle
值: 用来查找签名用的 HMAC key 的不透明的关联句柄。
-
openid.return_to
值: 在身份标识提供方修改它之前,请求中发送的 return_to URL 参数的逐字拷贝。
-
openid.signed
值: 逗号分割的签名字段列表。
注: 字段不要添加 "openid." 前缀。例如,
"mode,identity,return_to"。
-
openid.sig
值: base64(HMAC(secret(assoc_handle), token_contents)
注: 内容是返回值中所有的签名的键和键值的键值对格式的字符串。
他们必须和 openid.signed 字段里面列出的有相同的顺序。
消费者必须在检查的签名前再创建一个字符。参见 Appendix D (限制)。
-
openid.invalidate_handle
值: 可选的;如果身份标识提供方不接受或者无法识别请求中发送的关联句柄。
4.2.3.
特别注意
- 这个方式常用作“AJAX”样式的设定。
检查一个宣传的身份标识的更加经典的方式是
checkid_setup (checkid_setup)。
- 身份识别提供方应该仅断言其直接管理或提供的 URL。
如果最终用户要求断言其他的在身份识别提供方的领域意外的 URL,那么就需要使用委派 (委派认证)。
- 所提供的 openid.return_to URL 可以包含查询字符串(query string),
并且身份标识提供方在将它附加到返回参数时必须保持原来的值。
OpenID 消费者应该用时间戳添加一个自签名到 openid.return_to URL 参数中,
以防止重放攻击。
Details of that
are left up to the Consumer.
However, because the openid.return_to URL is signed by
the Idenity Provide, a Consumer can make sure outside
parties haven't sent id_res responses with mismatching
openid.return_to URLs and signatures.
- If the Identity Provider didn't accept/recognize the
provided assoc_handle for whatever reason, it'll choose
its own to use, and copy the one provided back into
openid.invalidate_handle, to tell the Consumer to stop
using it. The Consumer SHOULD then send it along in a
check_authentication (check_authentication)
request to verify it actually is no longer valid.
- If the Identifier assertion fails, the Identity
Provider provides the openid.user_setup_url for where
the End User can do whatever's necessary to fulfill the
assertion, be it login, setup permissions, etc. The
server SHOULD return a URL which doesn't imply anything
about what's needed, so the Consumer is left in the dark
about why the assertion failed.
The Identity Provider handling SHOULD eventually return
the End User to the openid.return_to URL, acting like a
checkid_setup response, with either a "id_res" or "cancel"
mode.
- The openid.return_to URL MUST descend from the
openid.trust_root, or the Identity Provider SHOULD
return an error. Namely, the URL scheme and port MUST
match. The path, if present, MUST be equal to or below
the value of openid.trust_root, and the domains on both
MUST match, or, the openid.trust_root value contain a
wildcard like http://*.example.com. The wildcard SHALL
only be at the beginning. It is RECOMMENDED Identity
Provider's protect their End Users from requests for
things like http://*.com/ or http://*.co.uk/.
- 在应答中,身份标识提供方的签名必须覆盖 openid.identity 和 openid.return_to。
4.3.
checkid_setup
- 描述: Ask an Identity Provider if a End User owns the
Claimed Identifier, but be willing to wait for the reply.
The Consumer will pass the User-Agent to the Identity
Provider for a short period of time which will return
either a "yes" or "cancel" answer.
- HTTP 方法: GET
- 流程: 消费者 -> 用户代理 -> [身份标识提供方 -> 用户代理
->]+ 消费者
4.3.1.
请求参数
-
openid.mode
值: "checkid_setup"
-
openid.identity
值: 宣称的用户身份标识
-
openid.assoc_handle
值: The assoc_handle from the associate request.
注: 可选的; Consumer MUST use
check_authentication if an association handle isn't
provided or the Identity Provider feels it is invalid.
-
openid.return_to
值: URL where the Provider SHOULD return the
User-Agent back to.
-
openid.trust_root
值: URL the Provider SHALL ask the End User to trust.
默认: return_to URL
可选的; the URL which the End User SHALL
actually see to approve.
4.3.2.
返回参数
返回格式: 查询字符串参数
4.3.2.1.
总是发送
-
openid.mode
值: "id_res" 或 "cancel"
4.3.2.2.
在断言肯定时发送
-
openid.identity
值: 已被核实的身份标识
-
openid.assoc_handle
值: Opaque association handle being used to
fine the HMAC key for the signature.
-
openid.return_to
值: Verbatim copy of the return_to URL
parameter sent in the request, before the Provider
modified it.
-
openid.signed
值: Comma-seperated list of signed fields.
注: Fields without the "openid." prefix that
the signature covers. For example,
"mode,identity,return_to".
-
openid.sig
值: base64(HMAC(secret(assoc_handle), token_contents)
注: Where token_contents is a key-value
format string of all the signed keys and values in
this response. They MUST be in the same order as
listed in the openid.signed field. Consumer SHALL
recreate the token_contents string prior to
checking the signature. See Appendix D (限制).
-
openid.invalidate_handle
值: Optional; The association handle sent in
the request if the Provider did not accept or
recognize it.
4.3.3.
特别注意
- In the response, the Identity Provider's signature
MUST cover openid.identity and openid.return_to.
- In a lot of cases, the Consumer won't get a cancel mode; the
End User will just quit or press back within their
User-Agent. But if it is returned, the Consumer SHOULD
return to what it was doing. In the case of a cancel
mode, the rest of the response parameters will be
absent.
4.4.
check_authentication
- 描述: Ask an Identity Provider if a message is
valid. For dumb, stateless Consumers or when verifying an
invalidate_handle response.
WARNING: Only validates signatures
with stateless association handles. Identity Providers
MUST NOT ever validate a signature for an association
handle whose secret has been shared with anybody. They
MUST differentiate its stateless vs. associated
association handles, and only offer check_authentication
service on the stateless handles.
- HTTP method: POST
- Flow: Consumer -> IdP -> Consumer
4.4.1.
Request Parameters
-
openid.mode
值: "check_authentication"
-
openid.assoc_handle
值: The association handle from checkid_setup
or checkid_immediate response.
-
openid.sig
值: The signature from the checkid_setup or
checkid_immediate request the Consumer wishes to
verify.
-
openid.signed
值: The list of signed fields from the checkid_setup
or checkid_immediate request the Consumer wishes to
verify the signature of.
-
openid.*
值: The Consumer MUST send all the openid.* response
parameters from the openid.signed list which they'd
previously gotten back from a checkid_setup or
checkid_immediate request, with their values being
exactly what were returned from the Provider.
-
openid.invalidate_handle
值: Optional; association handle returned via
invalidate_handle.
4.4.2.
返回参数
返回格式:键值对
-
openid.mode
值: "id_res"
-
is_valid
值: “true” 或者 “false”
描述: 布尔值;签名是否正确。
-
invalidate_handle
值: opaque association handle
描述: If present, the Consumer SHOULD
uncache the returned association handle.
4.4.3.
特别注意
- Identity Providers MUST implement this mode for error
recovery and dumb Consumers, which can't keep state
locally, but it's RECOMMENDED that it is used as little
as possible, as it shouldn't be necessary most the
time. It's good for debugging, though, as you develop
your Consumer library.
- If you got an invalidate_handle response during a
checkid_setup or checkid_immediate request, that means
the Identity Provider didn't recognize the association
handle, maybe it lost it, and had to pick its own.
This means the Consumer will have to fallback to dumb
mode, since you don't have the shared secret which the
Identity Provider is using. While doing this
check_authentication request, also send along the
invalidate_handle response from the Identity Provider
and it'll be checked to see if it actually is
missing/bogus.
- When verifying the signature using openid.* query
values, the openid.mode value must be changed to
"id_res".
5.
安全考虑
- 虽然在 OpenID 认证协议中经常提供使用 HTTP,HTTPS 可以用来提供额外的安全。
推荐在associate 模式 (associate)中采用,来防止中间人攻击,DNS攻击,钓鱼攻击等。
- 在请求最终用户通过 OpenID 登录时,消费者不应该使用 IFrames 或弹出式窗口。
Appendix A.
默认值
Appendix A.1.
Diffie-Hellman P 值
1551728981814736974712322577637155\
3991572480196691540447970779531405\
7629378541917580651227423698188993\
7278161526466314385615958256881888\
8995127215884267541995034125870655\
6549803580104870537681476726513255\
7470407658574792912915723345106432\
4509471500722962109419434978392598\
4760375594985848253359305585439638443
Appendix B.
Error Responses
这个章节适用于协议/运行时错误,而不是认证错误。认证错误被定义在本协议中了。
- 没有定义错误编码;直接用自然语言描述错误。
- 如果是一个错误参数的 GET 请求,但是提供了有效的 openid.return_to URL,
那么,身份标识提供方应该带上参数 openid.mode=error 和
openid.error=Error+Text 重定向用户代理到 openid.return_to URL。
- 如果是一个错误参数的 GET 请求,并且没有提供有效的 openid.return_to URL,
那么,身份标识提供方应该返回“400 Bad Request”和错误消息。
- 如果是一个没有参数的 GET 请求,身份标识提供方应该显示一个 200 text/html
错误:“This is an
OpenID server endpoint. For more information, see
http://openid.net/”。
- 如果是一个错误参说或者没有参数的 POST 请求,身份标识提供方应该返回一个
400 Bad Request,并带有一个 Key-Value 格式的返回,键为“error”,
值为自然语言的描述的文本。身份标识提供方可以附加一些它想添加的键值。
Appendix C.
键-值格式
Lines of:
- some_key:some value
- 在冒号的前后不允许出现空白。
- 换行符必须是 Unix 样式的,即 ASCII 字符 10 ("\n")。
- Newlines MUST BE at end of each line as well as between lines.
- MIME type is unspecified, but text/plain is
RECOMMENDED.
- 字符编码必须为 UTF-8。
Appendix D.
限制
- 身份标识 URL: 最大 255 个字节
- 身份标识提供方 URL: 在消费者添加了 URL 参数后,最大为 2047 个字节。 原始的 URL 应该保持在这个之下
- return_to URL: 在身份标识提供方添加了 URL 参数后最大为 2047 个字节。
原始的 return_to URL 最好保持在这个之下。
- assoc_handle: 255 个字符或更少,并且只能由 ASCII 字符值在 33-126 之间的组成(可打印的非空白字符)。
Appendix E.
其它
- 时间戳必须是 w3c 格式,并且必须是在 UTC 时区,用 “Z” 标识。例如:
2005-05-15T17:11:51Z
6. 规范参考
| [RFC2119] |
Bradner, B., “Key words for use in RFCs to Indicate Requirement Levels.” |
| [RFC2396] |
Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax.” |
| [RFC2631] |
Rescorla, E., “Diffie-Hellman Key Agreement Method.” |
Authors' Addresses