MaxKey/docs/protocols/openid.md
shimingxy e51f4ffcfe U
2020-03-08 09:00:25 +08:00

212 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<h2>1 OpenID Connect简介</h2>
OpenID Connect是基于OAuth 2.0规范族的可互操作的身份验证协议。它使用简单的REST / JSON消息流来实现和之前任何一种身份认证协议相比开发者可以轻松集成。
OpenID Connect允许开发者验证跨网站和应用的用户而无需拥有和管理密码文件。OpenID Connect允许所有类型的客户,包括基于浏览器的JavaScript和本机移动应用程序,启动登录流动和接收可验证断言对登录用户的身份。
扩展阅读参看:官方技术说明<a href="http://openid.net/connect/" title="http://openid.net/connect/" target="_blank" rel="nofollow">Connect标准英文 </a> | <a href="http://en.wikipedia.org/wiki/OpenID_Connect" title="http://en.wikipedia.org/wiki/OpenID_Connect" target="_blank" rel="nofollow">OpenID Connect维基百科en</a>
<h2>2 OpenID的历史是什么</h2>
OpenID Connect是OpenID的第三代技术。首先是原始的OpenID它不是商业应用但让行业领导者思考什么是可能的。OpenID 2.0设计更为完善提供良好的安全性保证。然而其自身存在一些设计上的局限性最致命的是其中依赖方必须是网页但不能是本机应用程序此外它还要依赖XML这些都会导致一些应用问题。
OpenID Connect的目标是让更多的开发者使用并扩大其使用范围。幸运的是这个目标并不遥远现在有很好的商业和开源库来帮助实现身份验证机制。
<h2>3 OIDC基础</h2>
简要而言OIDC是一种安全机制用于应用连接到身份认证服务器Identity Service获取用户信息并将这些信息以安全可靠的方法返回给应用。
在最初因为OpenID1/2经常和OAuth协议一种授权协议一起提及所以二者经常被搞混。
OpenID是Authentication即认证对用户的身份进行认证判断其身份是否有效也就是让网站知道“你是你所声称的那个用户”
OAuth是Authorization即授权在已知用户身份合法的情况下经用户授权来允许某些操作也就是让网站知道“你能被允许做那些事情”。
由此可知,授权要在认证之后进行,只有确定用户身份只有才能授权。
(身份验证)+ OAuth 2.0 = OpenID Connect
OpenID Connect是“认证”和“授权”的结合因为其基于OAuth协议所以OpenID-Connect协议中也包含了client_id、client_secret还有redirect_uri等字段标识。这些信息被保存在“身份认证服务器”以确保特定的客户端收到的信息只来自于合法的应用平台。这样做是目的是为了防止client_id泄露而造成的恶意网站发起的OIDC流程。
举个例子。某个用户使用Facebook应用“What online quiz best describes you?” 该应用可以通过Facebook账号登录则你可以在应用中发起请求到“身份认证服务器”也就是Facebook的服务器请求登录。这时你会看到如下界面询问是否授权。
<img src="{{ "/images/openid/1.png" | prepend: site.baseurl }}?{{ site.time | date: "%Y%m%d%H%M" }}" alt=""/>
在OAuth中这些授权被称为scope。OpenID-Connect也有自己特殊的scope--openid ,它必须在第一次请求“身份鉴别服务器”Identity Provider,简称IDP时发送过去。
<h2>4 OIDC流程</h2>
OAuth2提供了Access Token来解决授权第三方客户端访问受保护资源的问题相似的OIDC在这个基础上提供了ID Token来解决第三方客户端标识用户身份认证的问题。OIDC的核心在于在OAuth2的授权流程中一并提供用户的身份认证信息ID-Token给到第三方客户端ID-Token使用JWT格式来包装得益于JWTJSON Web Token的自包含性紧凑性以及防篡改机制使得ID-Token可以安全的传递给第三方客户端程序并且容易被验证。应有服务器在验证ID-Token正确只有使用Access-Token向UserInfo的接口换取用户的更多的信息。
有上述可知OIDC是遵循OAuth协议流程在申请Access-Token的同时也返回了ID-Token来验证用户身份。
<h3>4.1 相关定义</h3>
EUEnd User用户。
RPRelying Party 用来代指OAuth2中的受信任的客户端身份认证和授权信息的消费方
OPOpenID Provider有能力提供EU身份认证的服务方比如OAuth2中的授权服务用来为RP提供EU的身份认证信息
ID-TokenJWT格式的数据包含EU身份认证的信息。
UserInfo Endpoint用户信息接口受OAuth2保护当RP使用ID-Token访问时返回授权用户的信息此接口必须使用HTTPS。
下面我们来看看OIDC的具体协议流程。
根据应用客户端的不同OIDC的工作模式也应该是不同的。和OAuth类似主要看是否客户端能保证client_secret的安全性。
如果是JS应用其所有的代码都会被加载到浏览器而暴露出来没有后端可以保证client_secret的安全性则需要是使用默认模式流程(Implicit Flow)。
如果是传统的客户端应用后端代码和用户是隔离的能保证client_secret的不被泄露就可以使用授权码模式流程Authentication Flow
此外还有混合模式流程(Hybrid Flow),简而言之就是以上二者的融合。
OAuth2中还有口令模式和“应有访问模式”的方式来获取Access Token关于OAuth2的内容可以参见OAuth2.0 协议入门指南为什么OIDC没有扩展这些方式呢?
"口令模式"是需要用户提供账号和口令给RP的既然都已经有用户名和口令了就不需要在获取什么用户身份了。至于“应有访问模式”这种方式不需要用户参与也就无需要认证和获取用户身份了。这也能反映授权和认证的差异以及只使用OAuth2来做身份认证的事情是远远不够的也是不合适的。
<h3>4.2 授权码模式流程</h3>
<img src="{{ "/images/openid/2.png" | prepend: site.baseurl }}?{{ site.time | date: "%Y%m%d%H%M" }}" alt=""/>
授权码模式流程
和OAuth认证流程类似
RP发送一个认证请求给OP其中附带client_id
OP对EU进行身份认证
OP返回响应发送授权码给RP
RP使用授权码向OP索要ID-Token和Access-TokenRP验证无误后返回给RP
RP使用Access-Token发送一个请求到UserInfo EndPoint UserInfo EndPoint返回EU的Claims。
<h4>4.2.1 基于Authorization Code的认证请求</h4>
RP使用OAuth2的Authorization-Code的方式来完成用户身份认证所有的Token都是通过OP的Token EndPointOAuth2中定义来发放的。构建一个OIDC的Authentication Request需要提供如下的参数
scope必须。OIDC的请求必须包含值为“openid”的scope的参数。
response_type必选。同OAuth2。
client_id必选。同OAuth2。
redirect_uri必选。同OAuth2。
state推荐。同OAuth2。防止CSRF, XSRF。
示例如下:
<pre><code class="http hljs">
GET /authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb HTTP/1.1
Host: server.example.com
</code></pre>
<h4>4.2.2 基于Authorization Code的认证请求的响应</h4>
在OP接收到认证请求之后需要对请求参数做严格的验证具体的规则参见http://openid.net/specs/openid-connect-core-1_0.html#AuthRequestValidation验证通过后引导EU进行身份认证并且同意授权。在这一切都完成后会重定向到RP指定的回调地址(redirect_uri)并且把code和state参数传递过去。比如
<pre><code class="http hljs">
HTTP/1.1 302 Found
Location: https://client.example.org/cb?
code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj
</code></pre>
<h4>4.2.3 获取ID Token</h4>
RP使用上一步获得的code来请求Token EndPoint这一步桶OAuth2就不再展开细说了。然后Token EndPoint会返回响应的Token其中除了OAuth2规定的部分数据外还会附加一个id_token的字段。id_token字段就是上面提到的ID Token。例如
<pre><code class="json hljs">
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5
NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ
fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz
AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q
Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ
NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd
QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS
K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4
XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg"
}
</code></pre>
其中看起来一堆乱码的部分就是JWT格式的ID-Token。在RP拿到这些信息之后需要对id_token以及access_token进行验证具体的规则参见http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation和http://openid.net/specs/openid-connect-core-1_0.html#ImplicitTokenValidation。至此可以说用户身份认证就可以完成了后续可以根据UserInfo EndPoint获取更完整的信息。
<h4>4.2.4 安全令牌 ID-Token</h4>
上面提到过OIDC对OAuth2最主要的扩展就是提供了ID-Token。下面我们就来看看ID-Token的主要构成
iss = Issuer Identifier必须。提供认证信息者的唯一标识。一般是Url的host+path部分
sub = Subject Identifier必须。iss提供的EU的唯一标识最长为255个ASCII个字符
aud = Audience(s)必须。标识ID-Token的受众。必须包含OAuth2的client_id
exp = Expiration time必须。ID-Token的过期时间
iat = Issued At Time必须。JWT的构建的时间。
auth_time = AuthenticationTimeEU完成认证的时间。如果RP发送认证请求的时候携带max_age的参数则此Claim是必须的。
nonceRP发送请求的时候提供的随机字符串用来减缓重放攻击也可以来关联ID-Token和RP本身的Session信息。
acr = Authentication Context Class Reference可选。表示一个认证上下文引用值可以用来标识认证上下文类。
amr = Authentication Methods References可选。表示一组认证方法。
azp = Authorized party可选。结合aud使用。只有在被认证的一方和受众aud不一致时才使用此值一般情况下很少使用。
<pre><code class="json hljs">
{
"iss": "https://server.example.com",
"sub": "24400320",
"aud": "s6BhdRkqt3",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"auth_time": 1311280969,
"acr": "urn:mace:incommon:iap:silver"
}
</code></pre>
另外ID Token必须使用JWT(JSON Web Token)进行签名和JWE(JSON Web Encryption)加密从而提供认证的完整性、不可否认性以及可选的保密性。关于JWT的更多内容请参看JSON Web Token - 在Web应用间安全地传递信息
<h3>4.3 默认模式流程</h3>
<img src="{{ "/images/openid/3.png" | prepend: site.baseurl }}?{{ site.time | date: "%Y%m%d%H%M" }}" alt=""/>
默认模式流程
默认流程和OAuth中的类似只不过也是添加了ID-Token的相关内容。
这里需要说明的是OIDC的说明文档里很明确的说明了用户的相关信息都要使用JWT形式编码。在JWT中不应该在载荷里面加入任何敏感的数据。如果传输的是用户的User ID。这个值实际上不是什么敏感内容一般情况下被知道也是安全的。
但是现在工业界已经不推荐使用OAuth默认模式而推荐使用不带client_Secret的授权码模式。
<h3>4.4混合模式</h3>
混合模式简而言之就是以上提到的两种模式的混合不过也有一些小的改变就是允许直接向客户端返回Access-Token。
业界普遍认为后端传递Token比如服务器之间通信要比前端比如页面之间可靠所以如果直接返回令牌的情况下会把令牌的过期时间设置较短但是比较
UserInfo Endpoint
可能有的读者发现了ID-Token只有sub是和EU相关的这在一般情况下是不够的必须还需要EU的用户名头像等其他的资料OIDC提供了一组公共的cliams来提供更多用户的信息这就是——UserIndo EndPoin。
在RP得到Access Token后可以请求此资源然后获得一组EU相关的Claims这些信息可以说是ID-Token的扩展ID-Token中只需包含EU的唯一标识sub即可避免ID Token过于庞大和暴露用户敏感信息然后在通过此接口获取完整的EU的信息。此资源必须部署在TLS之上例如
<pre><code class="http hljs">
GET /userinfo HTTP/1.1
Host: server.example.com
Authorization: Bearer SlAV32hkKG
</code></pre>
成功之后响应如下:
<pre><code class="json hljs">
HTTP/1.1 200 OK
Content-Type: application/json
{
"sub": "248289761001",
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"preferred_username": "j.doe",
"email": "janedoe@example.com",
"picture": "http://example.com/janedoe/me.png"
}
</code></pre>
其中sub代表EU的唯一标识这个claim是必须的其他的都是可选的。