Oauth2 的四种许可类型
专栏地址:Oauth2.0 实战
Oauth 即 Open auth ,一种开放授权协议,通过为每个第三方软件和每个用户的组合分别生成对受保护资源具有受限的访问权限的凭据,也就是访问令牌,来代替之前的用户名和密码。而生成访问令牌之前的登录操作,又是在用户跟平台之间进行的,第三方软件根本无从得知用户的任何信息,使第三方软件调用大量 API 的时候,不再传输用户名和密码,从而减少网络安全的攻击面。
假如你是一个卖家小明,在京东商城开了一个店铺, 日常运营中你要将订单打印出来以便给用户发货。但打印这事儿也挺繁琐的,之前你总是手工操作,后来发现有个叫“小兔”的第三方软件,它可以帮你高效率地处理这事。 但你想想,小兔是怎么访问到这些订单数据的呢?其实是这样,京东商城提供了开放平台, 小兔通过京东商家开放平台的 API 就能访问到用户的订单数据。只要你在软件里点击同意,小兔就可以拿到一个访问令牌,通过访问令牌来获取到你的订单数据帮你干活儿了。
在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。在上述例子中对应关系如下:资源拥有者 -> 小明,客户端 -> 小兔软件,授权服务 -> 京东商家开放平台的授权服务,受保护资源 -> 小明店铺在京东 上面的订单。
授权码许可(Authorization Code)
在授权码许可类型中,授权服务的工作,可以划分为两大部分,一个是颁发授权码 code,一个是颁发访问令牌 access_token。
整体流程图如下:
时序图如下:
其中重要的是两次重定向的过程:
- 第一次重定向,由第三方软件(客户端)发起,重定向到京东的开放平台授权页面,引导用户进行授权
- 第二次重定向,在用户授权完成后,授权服务生成了授权码 code,携带其重定向回三方软件
为什么需要这么做:
- 为什么不直接返回访问令牌 access_token,而先返回授权码 code
如果直接返回
- 若直接返回给小兔软件前端,相当于把非常重要的许可证暴露在了浏览器上,为了安全考虑这是不允许的
- 若直接返回给小兔软件后端,小兔软件此时已经可以去请求数据了,但是用户还停留在授权界面,并没有返回到小兔软件,影响用户体验。(相当于你打王者荣耀,使用微信登录,微信登录完成并授权后,一直没返回王者荣耀界面,虽然你已经登录成功了,但是还是停留在微信的授权界面)
综合这样的安全性和用户体验考虑,授权码许可类型设置为了这样的流程。但是有前置条件:
- 第三方软件首先要到授权服务处注册自己的一些信息,如回调地址,可选权限范围(王者荣耀在请求微信授权登录时可选头像、好友关系等),生成自己的 app_id 和 app_secret
如果不进行注册,虽然在获取授权码的时候第三方软件携带了想要的回调地址,但授权服务发现自己根本不知道这个软件是什么,也不知道这个软件的回调地址是否安全,自然不能进行授权
这种认证方式的 grant_type 为:authorization_code
资源拥有者凭证许可(Resource Owner Password Credentials)
试想一种这样的情况,小兔此时就是京东官方出品的一款软件,小明也是京东的用户,那么小明其实是可以使用用户名和密码来直接使用小兔这款软件的。
那么,只需要在小明首次登录时,带着用户名和密码去授权模块认证一次,就可以返回 access_token 用于后续操作了。
这种认证方式的 grant_type 为:password
客户端凭证许可(Client Credentials)
如果没有明确的资源拥有者
,换句话说就是,小兔软件访问了一个不需要用户小明授权的数据,比如获取京东 LOGO 的图片地址,这个 LOGO 信息不属于任何一个第三方用户,再比如其它类型的第三方软件来访问平台提供的省份信息,省份信息也不属于任何一个第 三方用户。
这种认证方式的 grant_type 为:client_credentials
隐式许可(Implicit)
隐式许可流程是唯一在前端通信中要求返回 access_token 的流程
如果小明使用的小兔打单软件应用没有后端服务
,就是在浏览器里面执行的,比如纯粹的 JavaScript 应用,应该如何使用 OAuth 2.0 呢? 在这种情况下,小兔软件对于浏览器就没有任何保密的数据可以隐藏了,也不再需要应用密钥 app_secret 的值了,也不用再通过授权码 code 来换取访问令牌 access_token 的值了。
因为使用授权码的目的之一,就是把浏览器和第三方软件的信息做一个隔离,确保浏览器看不到第三方软件最重要的访问令牌 access_token 的值
。
PKCE 协议
随着移动端应用的兴起,比如现在有一个没有服务端的 APP
,也想要像 Web 应用那样自由的请求和响应,那它应该把 app_secret 存在哪里呢?因为 APP 会被安装到成千上万台设备上,如果 app_secret 被破解,可能造成灾难性的后果。
OAuth 2.0 里面就有这样的指导方法。这个方法就是我们将要介绍的 PKCE 协议,,全称是 Proof Key for Code Exchange by OAuth Public Clients。
首先,App 自己要生成一个随机的、长度在 43~128 字符之间的,参数为 code_verifier 的字符串验证码;接着,我们再利用这个 code_verifier,来生成一个被称为“挑战码”的参数 code_challenge。
生成 code_challenge 的几种方式如下:
- 一种 code_challenge_method=plain,此时 code_verifier 的值就是 code_challenge 的值
- 外一种 code_challenge_method=S256,就是将 code_verifier 值进行 ASCII 编码之后再进行哈希,然后再将哈希之后的值进行 BASE64-URL 编码,代码示例如下:
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
在第一步获取授权码 code 的时候,我们使用 code_challenge 和 code_challenge_method 参数也传过去,目的是让授权服务知道生成 code_challenge 值的方法是 plain 还是 S256
在第二步获取访问令牌 access_token 的时候,我们使用 code_verifier 参数和授权码 code,授权服务根据上一步 code 和 (code_challenge,code_challenge_method)的对应关系,结合 code_challenge_method 生成 code_challenge,与第一步的值进行比较,,如果相同就颁发访问令牌。
PKCE 发布的目的是为了缓解针对公开客户端的攻击,主要是解决授权码窃听的攻击。