代码改变世界

IdentityServer4 实现 OpenID Connect 和 OAuth 2.0

2019-02-28 19:20  Tony、  阅读(412)  评论(0编辑  收藏  举报

OAuth 2.0 。顺便说一下个人理解授权与认证的区别,授权可以理解为房间的主人允许你进入他的房间,但是房间的主人不知道你是谁。认证就是让房间的主任知道你是谁。

OAuth 2.0 简介

OAuth 2.0是OAuth的升级版本,是个授权协议,总的来说就是允许应用提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据,。它可以让那些控制资源的人允许某个应用以代表他们来访问他们控制的资源, 这个应用从资源的所有者那里获得到授权(Authorization)和access token, 随后就可以使用这个access token来访问资源。

OpenID Connect 简介

OpenID Connect是建立在OAuth2协议上的一个简单的身份标识层, 是一个基于OAuth2协议的身份认证标准协议。所以OpenID Connect兼容OAuth2.。使用OpenID Connect, 客户端应用可以请求一个叫identity token的Id token, 它会和access token一同返回给客户端应用. 这个Id token就可以被用来登录客户端应用程序, 而这个客户端应用还可以使用access token来访问API资源。OpenID Connect还定义了一个UserInfo端点, (OAuth2定义了Authorization端点和Token端点)它允许客户端应用获取用户的额外信息.。

IdentityServer4 简介

Identityserver是一个实现了OpenID Connect和OAuth 2.0框架。它实现了这两种协议流程,也提供了客户端以便于集成。

OAuth 2.0中的4个成员

在OAuth2.0中有4个成员,Resource Owner、Client、Resource Server、Authorization Server,如图所示:

授权服务器和资源服务器可以在同一个项目中。

OAuth 2.0授权流程

流程解析:

  1. 用户打开客户端,客户端要求向资源所有者(即用户)给予授权;
  2. 用户同意授权;
  3. 客户端得知用户同意授权后,向授权服务器获取授权;
  4. 授权服务器给予客户端授权,并将授权码(Access Token);
  5. 客户端携带授权码去请求资源服务器;
  6. 资源服务器将受限的资源开放给客户端。

 OAuth 2.0授权模式

 

授权模式是表示客户用来获取访问令牌的资源所有者授权的凭证。此规范协议规定了4种授权类型:

  • client credentials(客户端模式)
  • resource owner password credentials(密码模式)
  • authorization code(授权码模式)
  • implicit(简化模式)

下面详细说明各种授权模式的具体流程:

client credentials(客户端模式)

客户端模式是4种模式中最简单的一种模式。客户端可以使用客户端凭据请求访问令牌(或者其他支持的认证方式),在这种模式中,客户端占据主导地位,它不需要用户的同意,可以直接向授权服务器索取令牌,严格来说,该模式并不存在授权的问题,流程如下:

  1. 客户端向授权服务器发起请求索要令牌;
  2. 授权服务器将令牌发放给客户端。
  3. 客户端用令牌请求资源服务器

 

(A步骤)中所需的参数::

  • grant_type 必选项 此值必须为client_credentials
  • scope 可选项

例如:

POST /token HTTP/1.1

Host: server.example.com

Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

(B步骤)授权服务器返回结果:

HTTP/1.1 200 OK

Content-Type: application/json;charset=UTF-8

Cache-Control: no-store

Pragma: no-cache

{

"access_token":"2YotnFZFEjr1zCsicMWpAA",

"token_type":"example",

"expires_in":3600,

"example_parameter":"example_value"

}

resource owner password credentials(密码模式)

密码模式适合建立在客户端与资源所有者具有信任关系的情况下,例如它是一个设备的操作系统或者具有很高权限的应用。这种模式用户要向客户端提供自己的用户名密码,从而达到向服务提供商索取授权。授权服务器在启动此类型时,要特别小心,只有在其他的授权方式不被允许的情况下才可以使用这种授权模式。流程如下:

  1. 客户端要求使用资源所有者的密码;
  2. 资源所有者给予用户名密码后,客户端向授权服务器发起申请令牌的请求;
  3. 授权服务器将令牌发放给客户端。
  4. 客户端用令牌请求资源服务器。

(B步骤)所需参数:

  • grant_type 必选项 此值必须为"password"
  • username 必选项 用户名
  • password 必选项 密码
  • scope 可选项

例如:

POST /token HTTP/1.1

Host: server.example.com

Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

Content-Type: application/x-www-form-urlencoded

grant_type=password&username=johndoe&password=A3ddj3w

    (C步骤)发放令牌:

HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache
     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

Authorization code(授权码模式)

授权代码授权类型用于获取访问令牌和刷新令牌,并针对机密客户端进行优化。它是一个基于重定向的流程,因此客户端必须能够与资源所有者的用户代理(通常是Web浏览器)并且能够从授权服务器接收传入请求(通过重定向)。授权码模式是功能最完整、流程最严密的授权模式,它的特点就是通过客户端的后台服务器,与"服务提供商"的授权服务器进行互动。授权流程如下:

 

流程解析:

  1. 客户端通过用户代理,重定向请求授权服务器,所需参数:客户端标识及重定向客户端URL;
  2. 用户选择是否给予客户端授权;
  3. 授权服务器给予客户端一个认证授权码,并跳转到指定的客户端URI(为不重1中的URL);
  4. 客户端使用该授权码,重定向到授权服务器,获取令牌;
  5. 授权服务器校验该授权码以及重定向的URI后,向客户端发送令牌或者更新令牌。(此时会告知客户端清除授权码,如果授权码是存放在cookie中,会清除授权码的cookie)
  6. 客户端用令牌请求资源服务器

步骤(A)所需的几个参数:

  • response_type 必选项 表示的是要求指定的授权类型,此处必须设置为:code
  • client_id 必选项 客户端的唯一标识
  • redirect_uri 可选项 重定向的URI
  • scope 可选项 授权的管道
  • state 建议项 表示客户端状态,授权服务器会将该状态原值返回

问题答疑:为何引入authorization_code?

协议设计中,为什么要使用authorization_code来交换access_token?这是读者容易想到的一个问题。也就是说,在协议的第3步,为什么不直接将access_token通过重定向方式返回给Client呢?比如:

HTTP/1.1 302
Location:
https://www.facebook.com/?access_token=ya29.AHES6ZSXVKYTW2VAGZtnMjD&token_type=Bearer&expires_in=3600

如果直接返回access_token,协议将变得更加简洁,而且少一次Client与AS之间的交互,性能也更优。那为何不这么设计呢?协议文档[1]中并没有给出这样设计的理由,但也不难分析:

(1) 浏览器的redirect_uri是一个不安全信道,此方式不适合于传递敏感数据(如access_token)。因为uri可能通过HTTP referrer被传递给其它恶意站点,也可能存在于浏览器cacher或log文件中,这就给攻击者盗取access_token带来了很多机会。另外,此协议也不应该假设RO用户代理的行为是可信赖的,因为RO的浏览器可能早已被攻击者植入了跨站脚本用来监听access_token。因此,access_token通过RO的用户代理传递给Client,会显著扩大access_token被泄露的风险。 但authorization_code可以通过redirect_uri方式来传递,是因为authorization_code并不像access_token一样敏感。即使authorization_code被泄露,攻击者也无法直接拿到access_token,因为拿authorization_code去交换access_token是需要验证Client的真实身份。也就是说,除了Client之外,其他人拿authorization_code是没有用的。 此外,access_token应该只颁发给Client使用,其他任何主体(包括RO)都不应该获取access_token。协议的设计应能保证Client是唯一有能力获取access_token的主体。引入authorization_code之后,便可以保证Client是access_token的唯一持有人。当然,Client也是唯一的有义务需要保护access_token不被泄露。

(2) 引入authorization_code还会带来如下的好处。由于协议需要验证Client的身份,如果不引入authorization_code,这个Client的身份认证只能通过第1步的redirect_uri来传递。同样由于redirect_uri是一个不安全信道,这就额外要求Client必须使用数字签名技术来进行身份认证,而不能用简单的密码或口令认证方式。引入authorization_code之后,AS可以直接对Client进行身份认证(见步骤4和5),而且可以支持任意的Client认证方式(比如,简单地直接将Client端密钥发送给AS)。

在我们理解了上述安全性考虑之后,读者也许会有豁然开朗的感觉,懂得了引入authorization_code的妙处。那么,是不是一定要引入authorization_code才能解决这些安全问题呢?当然不是。笔者将会在另一篇博文给出一个直接返回access_token的扩展授权类型解决方案,它在满足相同安全性的条件下,使协议更简洁,交互次数更少。

implicit(简化模式)

简化模式用于获取访问令牌(但它不支持令牌的刷新,只所以称为简化模式,和授权码模式比少了获取授权码的步骤),并对运行特定重定向URI的公共客户端进行优化,而这一些列操作通常会使用脚本语言在浏览器中完成,令牌对访问者是可见的,且客户端也不需要验证。具体流程如下:

 注:Web-HostedClientResource服务器相当于是一个存储accessToken的地方,通常指浏览器中的存储(cookie、localStorage、SessionStorge、js变量等)

步骤解析:

  1. 客户端携带客户端标识以及重定向URI到授权服务器;
  2. 用户确认是否要授权给客户端;
  3. 授权服务器得到许可后,跳转到指定的重定向地址,并将令牌也包含在了里面;
  4. 客户端不携带上次获取到的包含令牌的片段,去请求资源服务器;
  5. 资源服务器会向浏览器返回一个脚本;
  6. 浏览器会根据上一步返回的脚本,去提取在C步骤中获取到的令牌;
  7. 浏览器将令牌推送给客户端。

(A步骤)中需要用到的参数,注意在这里要使用"application/x-www-form-urlencoded"格式:

  • response_type 必选项,此值必须为"token" 
  • client_id 必选项
  • redirect_uri 可选项
  • scope 可选项
  • state 建议选项

    例如:

GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
    Host: server.example.com
					

    (C步骤)中返回的参数包含:

  • access_token 必选项
  • token_type 必选项
  • expires_in 建议选项
  • scope 可选项
  • state 必选项

    例如:

HTTP/1.1 302 Found
     Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
               &state=xyz&token_type=example&expires_in=3600

 

OpenID Connect授权模式

认证流程主要是由OAuth2的几种授权流程延伸而来的,有以下3种:

  • Authorization Code Flow(授权码模式):基于OAuth2的授权码来换取Id Token和Access Token。
  • Implicit Flow(简化模式):基于OAuth2的Implicit流程获取Id Token和Access Token。
  • Hybrid Flow:混合Authorization Code Flow+Implici Flow获取Id Token和Access Token。

注:OpenID Connect 为什么没有基于OAuth2的Resource Owner Password Credentials Grant和Client Credentials Grant扩展,Resource Owner Password Credentials Grant是需要应用提供账号密码的,账号密码都有了在获取Id Token意义不大。Client Credentials Grant没有用户的参与所以获取Id Token 也没意义。这也能反映授权和认证的差异,以及只使用OAuth2来做身份认证的事情是远远不够的,也是不合适的。

Authorization Code Flow(授权码模式)

此模式是基于OAuth2的Authorization Code模式。流程和OAuth2的Authorization Code基本一致,只是在获取access Token的时候会返回Id Token。授权流程如下:

 

 

流程解析:

  1. 客户端通过用户代理,重定向请求授权服务器,所需参数:客户端标识及重定向客户端URL;
  2. 用户选择是否给予客户端授权;
  3. 授权服务器给予客户端一个认证授权码,并跳转到指定的客户端URI(为不重1中的URL);
  4. 客户端使用该授权码,重定向到授权服务器,获取令牌(access token)和Id token;
  5. 授权服务器校验该授权码以及重定向的URI后,向客户端发送令牌(access token)或者更新令牌(refresh token)以及Id token。(此时会告知客户端清除授权码,如果授权码是存放在cookie中,会清除授权码的cookie)
  6. 客户端用令牌请求资源服务器,通过Id token 解析出用户信息

步骤(A)所需的几个参数:

  • response_type 必选项 表示的是要求指定的授权类型,此处必须设置为:code
  • client_id 必选项 客户端的唯一标识
  • redirect_uri 可选项 重定向的URI
  • scope 可选项 授权的管道
  • state 建议项 表示客户端状态,授权服务器会将该状态原值返回

Implicit Flow(简化模式)

简化模式用于获取访问令牌(但它不支持令牌的刷新,只所以称为简化模式,和授权码模式比少了获取授权码的步骤),并对运行特定重定向URI的公共客户端进行优化,而这一些列操作通常会使用脚本语言在浏览器中完成,令牌对访问者是可见的,且客户端也不需要验证。具体流程如下:

此模式是基于OAuth2的implicit模式。流程和OAuth2的implicit基本夷之,只是在获取access Token的时候会返回Id Token。授权流程如下:

 

 注:Web-HostedClientResource服务器相当于是一个存储accessToken的地方,通常指浏览器中的存储(cookie、localStorage、SessionStorge、js变量等)

步骤解析:

  1. 客户端携带客户端标识以及重定向URI到授权服务器;
  2. 用户确认是否要授权给客户端;
  3. 授权服务器得到许可后,跳转到指定的重定向地址,并将令牌和Id token也包含在了里面;
  4. 客户端不携带上次获取到的包含令牌的片段,去请求资源服务器;
  5. 资源服务器会向浏览器返回一个脚本;
  6. 浏览器会根据上一步返回的脚本,去提取在C步骤中获取到的令牌;
  7. 浏览器将令牌和Id token推送给客户端。

(A步骤)中需要用到的参数,注意在这里要使用"application/x-www-form-urlencoded"格式:

  • response_type 必选项,此值为"token" 或者"Id_token"或者"token Id_token"
  • client_id 必选项
  • redirect_uri 可选项
  • scope 可选项(如果 reponse_type为"token",此时scope中不能包含身份认证的scope,为"Id_token"时反之)
  • state 建议选项

Hybrid Flow(混合模式)

Hybrid流程是前两者的混合, 在该流程里, 有一些tokens和授权码来自于授权端点, 而另外一些tokens则来自于Token端点。该流程允许客户端立即使用ID Token, 并且只需要一次往返即可获得授权码。这种流程也要求客户端应用可以安全的维护secret。

Hybrid流程的步骤如下:

  1. 客户端准备身份认证请求, 请求里包含所需的参数
  2. 客户端发送请求到授权服务器
  3. 授权服务器对最终用户进行身份认证
  4. 授权服务器获得最终用户的同意/授权
  5. 授权服务器把最终用户发送回客户端, 同时带着授权码, 根据响应类型的不同, 也可能还带着一个或者多个其它的参数.
  6. 客户端使用授权码向Token端点请求一个响应
  7. 客户端接收到响应, 响应的body里面包含着ID Token 和 Access Token
  8. 客户端验证ID Token, 并获得用户的一些身份信息.

而根据其response_type的不同, 它又分为三种情况:

  • response_type=code id_token
  • response_type=code token
  • response_type=code id_token token

 response_type=code id_token:

当reponse_type为这种类型的时候, 授权码和ID Token从授权端点发行返回, 然后Access Token 和 ID Token会从Token端点发行返回:

 

response_type=code token:

当reponse_type为这种类型的时候, 授权码和Access Token从授权端点发行返回, 然后Access Token 和 ID Token会从Token端点发行返回:

 

 

response_type=code id_token token:

当reponse_type为这种类型的时候, 授权码和Access Token和ID Token从授权端点发行返回, 然后Access Token 和 ID Token会从Token端点发行返回:

OAuth 2.0 vs OpenID Connect 主要授权方式/流程对比

流程特点的比较

 Refresh Token获取Access Token

  • grant_type 必选项,此值为"refresh_token" 
  • refresh_token 必选项
  • client_id 必选项
  • client_secret 必选项
  • scope 可选项

 

 

IdentityServer实现源码:下载源码

参考:https://www.cnblogs.com/Allen0910/p/8647935.html

   https://www.cnblogs.com/Tony100/p/9456755.html

   https://www.cnblogs.com/stulzq/p/8119928.html

IdentityServer