由浅入深剖析OAuth2.0的授权码模式
# 剖析OAuth2.0如何进行「认证」
# 一、认证与授权的区别
首先必须认清两个概念:认证(Authentication)和鉴权(Authorization)。比较有趣的是,这两个词的英文也很容易混淆。
这两个词的辨析用一句话来描述:「认证」表示「你是谁」,也即我们常说的登录。而「鉴权」表示「你能干什么」。
鉴权是说,知道你是谁后,还要知道你能拿到哪些资源。比如一个银行前台职工能看到的与一个银行行长所能看到的内容一定是有差距的,系统必须鉴别你是拥有哪些权限。
一个完备的服务端必须同时拥有「认证」和「鉴权」两个基础功能。「认证」这个环节就要用到OAuth2.0的原理了,至于「鉴权」就涉及其他的框架和理论,比如Apache Shiro和Spring Security等。
此外,我们常常听到一个词叫SSO,即Single Sign On,单点登录。那么SSO和OAuth2.0的联系是什么呢?其实OAuth2.0针对的是一次认证的完整过程,关注认证这个过程本身。而 SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。从关系上说,可以依据OAuth2.0来实现SSO,也就是OAuth2.0偏底层原理,而SSO更像一个产品。后面会找机会更新一篇关于SSO的文章。本文讲主要关注OAuth2.0本身。
# 二、什么是OAuth2.0
前面已经提到,这里重申一下,OAuth2.0是2012年的一种开放网络标准,它的详细的文档见RFC6749 (opens new window)(本文很多插图也来源于此)。
本文讲解OAuth2.0的核心内容,重点是从一个高一些的角度看一次「认证」的全过程,并对其中的细节进行剖析,对于一些细节「为什么这么设计」做出一些解释。
# 三、OAuth2.0的运行流程
在说OAuth2.0的正式流程前,必须先说几个名词的概念:
- Client:客户,即某个资源的申请者。这里映射到实际中,就是一个第三方网站或者APP。这里的第三方指的是区别于「用户」和「鉴权服务器」(后面会提到这二者)的一方。
- Resource Owner:资源拥有者,也就是用户。这里必须要理解,一个资源一定属于一个人,登录一个网站的过程就是“用户取得属于用户的资源”的过程,所以这里说得就是资源拥有者,即用户。
- Authorization Server:鉴权服务器。这个服务器可以对资源进行鉴定。
- Resource Server:资源服务器,即存放资源的服务器,存储用户所需访问的数据。
上面四个概念可能一时难以区分,下面是文档中的一张OAuth2.0的流程图,结合图来看会有很好的理解。
我们来看RFC6749中如何定义OAuth2.0的流程(为了便于理解,进行了对应的概念转换):
- A:第三方网站向用户申请获取用户认证
- B:用户同意认证
- C:第三方网站拿着「用户的同意证明」去找鉴权服务器
- D:鉴权服务器给第三方网站一个Token
- E:第三方网站拿着Token找资源服务器索要资源
- F:资源服务器看Token没问题,于是给Client它想要的资源
整个过程抽象的就是一件事:资源拥有者的信息(Resource)可以被资源拥有者(Resource Owner)愿意给的第三方网站(Client)获取。其他的存在都是为了辅助完成这件事。
于是,这里有一个问题:
问题:为什么不省略掉鉴权服务器这个东西,直接让Client和资源服务器之间用某种证明完成上述流程(即跳过C和D)?
这其实恰恰是OAuth2.0的核心所在。鉴权服务器的存在,可以让第三方网站和资源服务器之间不依靠「用户名」和「密码」来交流。也就是说,第三方网站始终不知道用户的这些敏感信息。这一方面防止了第三方网站的为所欲为,拿到的Token只能用来申请对应的资源。另一方面,防止了第三方网站的信息被人恶意窃取(毕竟我们不能保证每个第三方网站都绝对安全)。因此,鉴权服务器的存在大大提高了整个过程的安全性。除此之外,鉴权服务器的存在,也可以轻松实现一个鉴权服务器服务多种资源服务器的架构,进而实现所谓的「单点登录」,即SSO(比如用QQ号登录某第三方网站,或是一个集团or学校的正好可以登录所有集团or学校的网站服务)。
回答完上述的问题,我们发现,OAuth2.0的核心就是如何获取用户的授权,进而拿到Token这个过程。因为有了Token,就可以为所欲为了。如何拿到Token,OAuth2.0给出了4种模式:
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
其中授权码模式是最完整和严密的方式,本文仅介绍此模式。其他模式请参见其他优秀的文章,例如:https://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
# 四、OAuth2.0的授权码模式
这里我们首先还是拿RFC6749的一张图来描述流程:
这里我们看到多了一个东西:User Agent。这个东西其实就是用户所依赖的浏览工具,也就是浏览器。这样我们用转化后的过程来描述一下:
- A:用户通过浏览器打开某第三方网站,第三方网站发现用户没有登录,导向鉴权服务器,并附上原来访问的(第三方自己的页面)作为Redirection URI,并初始自己的网站身份证(Client ID,唯一识别该网站,或网站的某服务)
- B:用户同意授权
- C:授权服务器给一个授权用的code到第三方网站,这里依靠的是重定向,向链接中附一个code
- D:第三方网站拿到code后,带着指向刚才那个Redirection URI,找鉴权服务器申请Token
- E:鉴权服务器给第三方网站所需的Token
然后第三方服务器就能拿着那个Token去找相关资源服务器索要资源了。
我们再用一个更清晰的图来描述上述的整个过程:
看起来过程比较简单,但是接下来我们以问题和回答的方式梳理清楚上述过程中的一些容易被忽略的细节。
问题:两个Redirection URI分别是起什么作用的?
第一个URI拿给鉴权服务器,鉴权服务器会给一个code。这个code是和Client ID + URI唯一对应的。因此,等回头第三方网站拿着code找URI的时候,鉴权服务器会检查,这个code是不是它当初根据这一组Client ID + URI发给这个第三方网站的。如果是自己发的,那就可以给Token了,否则就是伪造的。
除了这个功能,第一个URI还有一个功能,也就是本身的重定向。鉴权服务器拿到了COde,他得知道给谁才行。要知道HTTP是无状态的,他也不知道自己该把Code给谁,因此URI要指明接下来的路怎么走。
问题:Code和Token都是鉴权服务器给的一种「凭证」,它俩的区别在哪?
拿Code换Token这一步在开始可能会让人感觉多余。
首先,Code是一次性的,只能一个Client+URI用一次,之后就过期了(同时也有分钟级的时间限制)。而Token只要拿到了,就能在有效期内(小时级)一直用来申请到资源。
那我们为什么要有用Code换Token这一步呢?原因在于安全性。
首先,code怎么返回过去的?用的是重定向,也就是code会直接夹带在链接中返回回去。这意味着,不需要任何技术手段,就可以直接从链接中获取code。然而这并没有用,因为code必须要校验Client ID和URI,别人拿到了也用不了。一旦我们直接获取到Token,要知道Token 的使用就是直接用了,不校验源这些东西,谁拿到谁就能盗窃所有者的信息。因此不能直接给Token。
这里又会衍生出两个问题:
- 为什么要用重定向,而不是HTTP访问的方式获取code?
- 为什么用Code拿到Token以后,剩下的使用就是安全了的呢(使用时也是可以被浏览器截获的)?
针对第一个问题,这其实是因为我们不想让第三方网站拿到我们认证用的用户名和密码。因为我们输入用户名密码的网站是鉴权服务器本身提供的,而不是输入给第三方网站,再由第三方网站递给鉴权服务器的。由第三方服务器获取到用户名和密码的话,前面兜这么多圈子来登录保证安全就功亏一篑了。
针对第二个问题,如果是明文访问的话,的的确确就危险了。但是我们有HTTPS,加密后的信息是非常安全的,因此理论上可以保证Token不被轻易窃取。
至此,OAuth2.0的授权码模式就已经剖析清楚了。其他的模式不过是做了不同程度的简化,有兴趣的读者可以再去学习。
# 参考
https://kefeng.wang/2018/04/06/oauth2-sso/
https://segmentfault.com/a/1190000010540911
https://www.cnblogs.com/i3yuan/p/14064380.html
https://www.zhihu.com/question/27446826