OAuth2.0四种授权模式
写在前面
在《OAuth2.0的一个简单解释》一文中,我们对OAuth2.0的含义和设计思想有了一个较为清晰的认识,那么接下来就学习OAuth2.0中的四种授权模式。
OAuth2角色
在学习授权模式之前,了解OAuth2中的4种基本角色对于学习和理解OAuth的工作原理有重要意义。
(1)资源所有者:资源所有者即用户,具有头像、照片、视频等资源;
(2)客户端:客户端即第三方应用,如前一篇中提到的知乎;
(3)授权服务器:授权服务器用来验证用户提供的信息是否正确,并返回一个令牌给第三方应用;
(4)资源服务器:资源服务器是提供给用户资源的服务器,如头像、照片、视频等资源。
通常来说,授权服务器和资源服务器可以是同一台服务器。
OAuth2授权流程
在熟悉了OAuth2中的4个基本角色以后,接下来开始学习OAuth2的授权流程,具体的流程如下:
(1)客户端(第三方应用)向用户请求授权;
(2)用户单击客户端所呈现的服务授权页面上的同意授权按钮后,服务端返回一个授权许可凭证给客户端;
(3)客户端拿着授权许可凭证去授权服务器申请令牌;
(4)授权服务器验证信息无误后,发放令牌给客户端;
(5)客户端拿着令牌去资源服务器访问资源;
(6)资源服务器验证令牌无误后开放资源访问。
上述是一个大致的流程,因为OAuth2有4种不同的授权模式,每种授权模式的授权流程又存在一定的差异,不过大致流程如下图所示:

OAuth2授权模式
OAuth2.0的标准是RFC 6749 文件。该文件首先解释 OAuth 是什么:
OAuth引入了一个授权层,用来分离两种不同的角色:客户端和资源所有者。……资源所有者同意以后,资源服务器可以向客户端颁发令牌。客户端通过令牌,去请求数据。
也就是说OAuth的核心是向第三方应用颁发令牌。之后RFC 6749接着写道:
(由于互联网有多种场景,)本标准定义了获得令牌的四种授权方式(authorization grant )。
从RFC 6749中可以知道OAuth 2.0规定了四种获得令牌的流程。开发者可以根据自己的实际情况来选择最适合的一种,进而向第三方应用颁发令牌。
OAuth2.0支持下面四种授权方式:授权码模式、简化模式、密码模式和客户端模式。
(1)授权码模式:授权码模式(authorization code)是功能最完整、流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本上都是使用这种模式。
(2)简化模式:简化模式不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌,一般若网站是纯静态页面,则可以采用这种方式。
(3)密码模式:密码模式是用户把用户密码直接告诉客户端,客户端使用这些信息向授权服务器申请令牌。这就需要用户对客户端高度信任,如客户端和服务提供商是同一家公司。
(4)客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务器提供者申请授权。严格来说,客户端模式并不能算作OAuth协议要解决的问题的一种解决方案,但是对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是较为方便的。
请注意,不管哪一种授权模式,第三方应用在申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。
授权码模式
授权码(authorization code)模式,指的是第三方应用先申请一个授权码,然后再用该码来获取令牌。这种方式是最常用的流程,安全性也最高,它适用于那些有后端的Web应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
在授权码模式中,分为授权服务器和资源服务器,授权服务器用来派发Token,拿着Token就可以去资源服务器获取资源,资源服务器和授权服务器可以分开,也可以合并,通常都是合并。
笔者之前开发过微信支付,里面涉及到的微信授权使用的就是授权码模式,因此这里以个人博客接入微信授权为例进行介绍。(微信授权就是微信登录)

第一步,笔者在个人博客的登录页面放上一个微信登录选项(其实是一个超链接),此时博客就相当于第三方应用。之后用户A(笔者博客的用户)点击这个超链接就会去请求授权服务器(其实是微信授权服务器)。用户A点击的过程其实就是博客向用户A要授权的过程。即上图1步。
第二步,用户A点击了超链接之后,向授权服务器发送请求,笔者博客上让用户点击的超链接可能URL如下所示:
1 | https://open.weixin.qq.com/connect/oauth2/authorize?appId=envythink&response_type=code&scope=snsapi_base&redirect_uri=http://envythink.com#wechat_redirect |
可以看到这个URL中有一些参数,这些都是我们后续会使用到的参数。这里简单说一下这些参数,appId表示应用id,这个是从微信开放平台申请得到的。其实从这里也就能看出授权服务器在进行校验的时候,一是校验应用的Id,二是检验用户身份。response_type表示授权类型,使用授权码模式时值为code,后续会拿着这个code来换取网页授权access_token。scope表示应用授权作用域,也就是笔者博客拿着用户的token能获取用户的什么信息,一般都是一些非敏感的基本信息。redirect_uri表示用户登录成功或者失败后跳转的地址,请注意在跳转的时候会携带授权码code。即上图2、3步。
第三步,笔者博客网站拿着第二步中获取到的授权码code及其他信息去微信授权服务器请求令牌,微信授权服务器在校验这些数据之后,会发送一个令牌回来,也就是access_token:
1 | { |
请注意微信的授权服务器校验数据的过程是在后端完成的,不是使用js来完成的。即上图4、5步。
第四步,拿着第三步获取到的令牌access_token就可以去请求用户信息。即上图6步。如果觉得笔者介绍的不够详细,可以参看 阮一峰博客。
通常我们都认为授权码模式是4种模式中最安全的一种模式,因为此种模式下的access_token不经过浏览器或者移动端,而是直接从后台发送到授权服务器上,这在一定程度上减少了access_token泄露的风险。
简化模式
简化模式又称为隐藏式模式,当某些Web应用是纯前端应用没有后端时,就可以使用简化模式。简化模式允许授权服务器直接向前端颁发令牌,这种方式没有授权码这个中间步骤,所以称为(授权码)”隐藏式”(implicit):

第一步,笔者在个人博客的登录页面放上一个微信登录的超链接,该超链接可能URL如下所示:
1 | https://open.weixin.qq.com/connect/oauth2/authorize?appId=envythink&response_type=token&scope=snsapi_base&redirect_uri=http://envythink.com#wechat_redirect |
可以看到上述URL中的参数和授权码模式非常相似,不同的是response_type参数的值变成了token,它表示授权服务器直接返回令牌access_token。
第二步,用户点击第一步中的超链接,页面跳转到微信登录页面,之后用户进行登录。即上图1、2步。
第三步,用户登录成功后,页面会自动重定向到redirect_uri参数所指定的URL,同时携带令牌access_token,这样用户在前端就能获取到令牌access_token。请注意,令牌的位置是URL锚点(fragment),而不是查询字符串(querystring),那是因为OAuth 2.0允许跳转网址是HTTP协议,因此就可能存在”中间人攻击”的风险,而浏览器跳转时,锚点不会发到服务器,这样就减少了令牌泄漏的风险。即上图3步。
第四步,拿着第三步获取到的令牌access_token就可以去请求用户信息。即上图4步。
很明显这种方式是很不安全的,它直接将令牌传给前端,因此只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉后令牌就失效。
密码模式
如果用户高度信任某个应用,RFC6749也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为”密码式”(password)。
密码模式在SpringCloud项目中应用非常广泛,尤其是服务之间的调用,使用密码模式进行鉴权是非常合适的,这一点笔者在SpringCloud那部分进行了介绍,这里就不赘述了。
密码模式有一个前提就是用户高度信任第三方应用,因此如果笔者在个人博客中接入了微信登录并且采用了密码模式,那么就需要用户在笔者博客中输入微信用户名和密码,这肯定是不合适的,因此说密码模式要求用户高度信任第三方应用。

第一步,笔者在个人博客的登录页面放上一个微信登录的超链接,用户点击该链接就会跳转到一个登陆页面,之后用户输入用户名和密码,并点击登录。
第二步,当用户点击第一步中的登录按钮后,笔者博客会使用类似下面的URL来发送一个post请求:
1 | https://open.weixin.qq.com/connect/oauth2/authorize?response_type=password&client_id=envythink&username=envythink&password=1234 |
可以看到上述URL中的参数和授权码模式差别较大,response_type参数的值变成了password,表示密码,注意这里没有重定向的redirect_uri,因此此处不需要重定向。即上图1步。
第三步,微信服务器会校验用户名和密码,校验成功后会在HTTP响应中直接将access_token返回给客户端。即上图2步。
第四步,拿着第三步获取到的令牌access_token就可以去请求用户信息。即上图3步。
可以看到此种方式需要用户给出自己的用户名/密码,因此风险较大,只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。
客户端模式
客户端模式又称为凭证式模式,当某些应用是纯后端应用没有前端时,就可以使用客户端模式,即在命令行下请求令牌:

第一步,客户端会使用类似下面的URL来发送一个get请求:
1 | https://api.open.weixin.qq.com/connect/oauth2/authorize?grant_type=client_credential&client_id=APPID&client_secret=APPSECRET |
可以看到上述URL中的参数和授权码模式差别较大,参数变为了grant_type,值为client_credential,它用于获取access_token。client_id和client_secret这两个参数用来确认客户端的身份。即上图1步。
第二步,微信服务器会校验客户端信息,校验成功后会在HTTP响应中直接将access_token返回给客户端。即上图2步。
第三步,拿着第二步获取到的令牌access_token就可以去请求用户信息。即上图3步。
可以很明显的发现在此过程中并不涉及到用户,那是因为客户端模式给出的令牌是针对第三方应用的,而不是针对用户,也就是说会存在多个用户共享同一个令牌的情况。
令牌使用
笔者个人博客网站在拿到令牌以后,就可以向微信服务器的API请求用户基本信息数据了。请注意此时,每个发到API的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面。
1 | curl -H "Authorization: Bearer ACCESS_TOKEN" \ |
可以看到在上面命令中,ACCESS_TOKEN就是拿到的令牌。
更新令牌
我们知道令牌是存在有效期的,如果令牌过了有效期,需要用户重新走一遍上述流程来申请一个新的令牌,这势必会大大降低用户的体验感,因此OAuth2.0在设计的时候就考虑到允许用户自动更新令牌。
具体做法是:微信服务器颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(也就是refresh_token字段)。在令牌到期之前,用户可以使用refresh_token去发一个请求,进而更新令牌。类似的请求如下所示:
1 | https://open.weixin.qq.com/oauth/token? |
可以看到上面URL中,grant_type参数值为refresh_token,它表示要求更新令牌,client_id参数和client_secret参数用于确认客户端身份,而refresh_token参数就是用于更新令牌的令牌。当微信服务器验证通过后,就会颁发新的令牌,这样就实现了令牌的自动更新。
(完)
