前面的四篇文章都是介绍OAuth 2.0中最常用、最安全的授权码许可类型,其实相比于OAuth 1.0,OAuth 2.0 最关键的一个变化就是授权许可,即授权流程,且不止授权码一种类型,还有隐式许可类型、客户端凭据许可类型、资源拥有者凭据许可类型(即密码类型)以及断言许可类型,本文将详细介绍除了授权码之外的其他四种许可类型以及应用场景。
一、OAuth 2.0中的许可类型
其实只要掌握了授权码许可类型,再学习其他几种许可类型的时候,就会发现它们都是授权码许可类型在不同应用场景下的简化版本!
1.1 授权码许可类型
不再赘述,详情见:https://www.cnblogs.com/hellowhy/p/15481745.html
1.2 隐式许可类型
如上图所示,隐式许可类型中的客户端和资源拥有者都在同一个服务上,比如完全运行在浏览器中的JavaScript应用就属于这种情况。客户端无法对浏览器隐藏任何秘密,因此当完成身份认证之后,授权服务器直接返回访问令牌,然后由客户端(浏览器)请求受保护的资源!
使用隐式许可类型需要注意:
- 使用这种许可流程的客户端无法持有客户端密钥。因为都是用过浏览器通信,所有信息完全暴露在外
- 饮食许可流程不可用于刷新令牌。因为浏览器内的应用具有短暂运行的特点,只会在被加载到浏览器的期间保持会话,但是刷新令牌一般的有效期比较长,需要进行存储才能在以后使用,所以刷新令牌的使用就比较局限。
- 当访问令牌过期之后,可以使用首次使用时信任(TOFU:Trust Of First Use)的原则,通过允许重新授权做到用户无感知。
当使用这种许可流程时,客户端向授权服务器的请求就会变成下面这样:
curl --location --request GET 'http://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&redirect_uri=http://client.com/callback1&response_type=token&scope=read%20write' \ --header 'Content-Type: application/json'
与授权码流程相比,有以下不同:
- response_type的值变成了token
- 没有了state
这样当授权服务器接收到请求,完成客户端、权限等的校验之后,就直接返回访问令牌access_token给客户端,返回的方式是直接追加到客户端重定向地址后面,如下所示:
http://client.com/callback#access_token=dffsfdafda52254&token_type=Bearer&reditect_uri=http://client.com/callback1
注意:授权服务器重定向到客户端redirect_uri时,要使用重定向URI的散列片段而不是查询参数来返回错误信息。
当从授权服务器返回时,客户单返回后在重定向URI的散列中带有令牌的值,受保护资源对令牌的处理和验证没有不同,但需要进行跨域资源(CORS)设置。
1.3 客户端凭据许可类型
在没有明确的资源拥有者,或者对于客户端而言不能区分资源拥有者时,可以使用客户端凭据类型,如上图所示。
在这种许可流程中,资源拥有者被转化到了客户端上,正好与隐式许可流程相反,因此这种许可流程只使用了后端信道,客户端就是代表自己从令牌端点访问令牌。没有了资源拥有者委托客户端以及客户端让用户进行授权的过程,而是客户端直接向授权服务器的令牌端点请求访问令牌,这与通过授权码请求访问令牌的流程是一样的,只是许可类型grant_type用client_credentials,授权服务器对客户端完成身份认证之后,直接颁发令牌,请求示例如下:
curl --location --request POST 'http://tokenendpoint.com/token' \ --header 'Content-Type: application/json' \ --header 'Authorization: basic BASE64(clientId:clientSecret)' \ --data-raw '{ "grantType":"client_credentials", "scope":"read" }'
授权服务器的响应就是一个普通的令牌端点响应,:一个包含令牌信息的JSON对象。在客户端凭据许可流程中也不会颁发刷新令牌,因为客户端随时可以获取新的访问令牌,返回结果如下:
{ "accessToken": "68bjh4543", "tokenType": "Bearer ", "expiration": "2021-11-07 9:33:00", "expiresInSeconds": "3600", "scope": "read" }
当客户端获取到令牌之后就能访问受保护资源了 ,相比之下这种方式对客户端减去了很多中间步骤,可以看作这样的流程:用户登录之后,拿着登录token去访问需要登录态访问的资源。
1.4 资源拥有者凭据许可类型(密码流程)
如果资源拥有者在授权服务器上有纯文本的用户名和密码,那么客户端可以向用户索要用户的凭据,然后用这个凭据换取令牌,这种方式就是资源拥有者许可类型。相比于隐式许可类型和客户端凭据许可类型,这种类型有完整的资源拥有者和客户端,但又不同于授权码许可类型,资源拥有者直接与客户端交互,而不是和授权服务器,并且只用到了后端信道和授权服务器中的令牌端点。因为索要了用户的密码,所以这种方式又称密码流程,这种许可类型通常是为那些要求资源拥有者输入用户名和密码,然后向所有受保护资源使用这些凭据的客户端使用的。
这种流程需要完成以下几步:
- 用户向客户端发起请求后,客户端获取到用户的用户名和密码
- 客户端拿着用户名和密码向令牌端点请求访问令牌
- 客户端得到访问令牌请求资源
这种方式的请求示例如下:
curl --location --request POST 'http://tokenendpoint.com/token' \ --header 'Content-Type: application/json' \ --header 'Authorization: basic BASE64(clientId:clientSecret)' \ --data-raw '{ "grantType":"password", "userName":"root", "password":"root1234", "scope":"read" }'
当授权服务器接受到的许可类型是密码password时,首先还是要对客户端进行身份认证,然后对参数中的用户进行身份认证,就是检查参数中的用户和密码是否正确,当一切检查通过之后,令牌端点就会颁发令牌,而且在这种许可类型中令牌端点是可以颁发刷新令牌的,因为这样一来,客户端就不需要保存用户的用户名和密码,直接用刷新访问令牌即可,值得注意的是当用户操作了修改密码之后,与之关联的所有令牌信息都要删除,并重新进行获取令牌的流程。
摘抄一段书本中很有趣的原话,哈哈哈:
现在你已经知道如何使用此种许可类型了,但是如果有可能避免,请不要在现实中使用它。这种许可类型只能作为过渡方案,用于那些原本就直接索取用户名和密码但要转投OAuth怀抱的客户端,而且应该尽快将这种客户端转到授权码流程上来。因此,除非没有其他选择,否则不要使用这种许可类型。互联网在此感谢你!
1.5 断言许可类型
断言许可类型是由OAuth工作组发布的第一个官方扩展许可类型。在这种许可类型下,客户端会得到一条结构化的且被加密保护的信息,叫做断言,使用断言向授权服务器换取令牌。可以把断言想象成某种经过认证的文档,例如文凭或许可证,只要能信任认证机构保证声明的真实性,就可以相信温暖的那个中内容的真实性。
目前标准化的断言格式有两种:一种是使用安全断言标记语言(SAML),另一种是使用JSON Web Token(JWT)。这种许可类型只使用后端信道,与客户端凭据许可类型很相似,没有明确的资源拥有者的参与,不同之处在于由此流程颁发的令牌所关联的权限范围取决于所出示的断言,而不仅仅取决于客户端本身。而且由于断言一般来自于客户端之外的第三方,因此客户端可以不知道断言本身的含义。
客户端可以从用户那里获取断言,也可以从某个配置系统中获取断言,获取访问令牌时,与授权码类似,只要向授权服务器出示自己的断言即可。请求示例如下:
curl --location --request POST 'http://tokenendpoint.com/token' \ --header 'Content-Type: application/json' \ --header 'Authorization: basic BASE64(clientId:clientSecret)' \ --data-raw '{ "grantType":"urn:ietf:params:oauth:grant-type:jwt-bearer", "scope":"read", "assertion":"{JWT}" }'
授权服务器接收到请求之后,要先解析断言,检查其加密保护,并处理其内容以确定生成何种令牌,授权服务器通常会有一个策略,用于决定接收哪些前发放的断言,并为断言的含义制定规则解释。
以上就是OAuth 2.0中所有的许可类型,但是在实际应用场景中到底怎么选择呢?书中给出了一个很详细的说明,一张图看懂
二、许可类型的选择
对于许可类型的选择,仅这一张图就够了!!
三、不同客户端的部署方式
OAuth 客户端的形式粗略地分为三类:Web应用、浏览器应用和原生应用。
3.1 Web应用
OAuth客户端最初的应用场景就是Web应用。这类应用运行在远程服务器上,需要通过Web浏览器访问。这种应用可以使用授权码、客户端凭据和断言等许可流程。
3.2 浏览器应用
浏览器应用完全运行在浏览器内,一般使用JS。因为浏览器应用受限于同源策略以及其他安全限制条件,因此最适合这类应用的就是隐式许可类型了。
3.3 原生应用
原生应用是直接在最终用户的设备上运行的应用。应用软件通常在外部经过编译或者打包之后再安装到设备上,比如移动端的app。这类应用很容易使用后端信道,直接向远程服务器发送HTTP请求。在移动设备上,自定义URI格式是最常用的。授权码许可、客户端凭据和断言许可流程都适用于原生客户端,但不推荐使用隐式许可类型,因为应用能够在浏览器之外保留信息。
四、密钥的处理
密钥的作用是让客户端软件实例向授权服务器进行身份认证,与资源拥有者的授权无关。客户端密钥不提供给资源拥有者和浏览器使用,它用于唯一标识客户端软件应用。
区分以下两个概念:
- 配置期间秘密:在客户端的每个副本中都相同
- 运行期间秘密:在各个客户端实例中都不同
客户端密钥属于配置期间秘密,因为它代表客户端本身,是配置在客户端软件内部的;访问令牌、授权码、刷新令牌等都属于运行时秘密,因为它们都是在客户端软件被部署之后由客户端存储的。OAuth 2.0体现了这两种不同的概念,它不要求所有客户端都拥有客户端密钥,而是将客户端分为两种类型:公开客户端和保密客户端,划分依据是能否持有配置期间秘密。
公开客户端:不能持有配置期间秘密,因而没有客户端密钥
保密客户端:能够持有配置期间秘密,客户端软件的每一个实例都有独立的配置信息,包括客户端ID和密钥,并且这些信息都是最终用户难以获取的。
五、总结
OAuth 2.0在一个通用的协议框架中提供了很多选项:
- 可以针对不同的部署场景,对标准的授权码许可类型进行多种优化
- 隐式许可类型能够用于无独立客户端的浏览器应用中
- 客户端凭据类型和断言许可类型能够用于特定资源拥有者的服务端应用
- 除非没有其他选择,否则不要使用资源拥有者凭据许可类型
- Web应用、浏览器应用、原生应用在OAuth的使用上都有各自的独特之处,但核心思想都是一样的
- 保密客户端能够持有客户端密钥,公开客户端则不能
至此,OAuth生态系统中各个组件的运行原理整理的差不多了,下一篇文章将会记录常见的漏洞以及预防方法!!!