OAuth2 四种授权模式
OAuth2 协议一共支持四种不同的授权模式:
-
授权码模式:常见的第三方平台登录功能基本都是使用这种模式。
-
简化模式:简化模式是不需要第三方服务端参与,直接在浏览器中向授权服务器申请令牌(token),如果网站是纯静态页面,则可以采用这种方式。
-
密码模式:密码模式是用户班用户名密码直接告诉客户端,客户端使用这些信息向授权服务器请令牌(token)。这需要用户对客户端高度信任,例如客户端应用和服务提供商就是同一家公司。
-
客户端模式:客户端模式是指客户端使用自已的名支而不是用户的名义同服务提供者申请授权。严格来说,客户端模式并不能算作 OAuth 协议解决问题的一种解决方案,但是,对于开发者而言,在一些为移动端提供的授权服务器上使用这种模式还是非常方便的。
如图 15-1 所示,无论哪种授权模式,其授权流程都是相似的,只不过在个别步骤上有所差异而己。

从图中我们可以看出 OAuth2 中包含四种不同的角色:
-
Client:第三方应用。
-
Resource Owner:资源所有者。
-
Authorization Server:授权服务器。
-
Resource Server:资源服务器。
接下来我们对四种授权模式进行详细介绍。
授权码模式
授权码模式(AuthorizationCode)是最安全并且使用最广泛的一种 OAuth2 授权模式,同时也是最复杂的一种授权模式,其具体的授权流程如图 15-2 所示(图片来自 RFC6749 文档,https://tools.ietf.org/html/rfc6749)。

这个流程图初看有点复杂,这里结合一个具体的案例来讲解:假设现在想给 www.javaboy.org 这个网站引入 GitHub 第三方登录功能,如果使用了 OAuth2 协议中的授权码模式,那么流程应该是这样的:
首先 www.javaboy.org 这个网站相当于一个第三方应用,在该网站的首页上放一个登录超链接,用户(服务方的用户,例如 GitHub 用户)单击这个超链接,就会去请求授权服务器(GitHub 的授权服务器)。
www.javaboy.org 网站首页的登录超链接可能是下面这样的:
https://github.com/oauth/authorize?response_type=code&client_id=javaboy&redirect_uri=www.javaboy.org&scope=all&state=123
这个请求链接中的参数比较多,我们解释一下:
-
response_type:该参数表示授权类型,使用授权码模式的时候这里固定为 code,表示要求返回授权码,拿到授权码之后,再根据授权码去获取 Access Token。
-
client_id:该参数表示客户端 id,也就是第三方应用的 id。有的读者可能对这个参数不理解,这里解释一下:如果想让 www.javaboy.org 接入 GitHub 第三方登录功能,开发者首先要去 GitHub 开放平台注册,去填入第三方应用的基本信息,信息填完之后,会获取到一个 APPID,在第三方应用发起授权请求时需要携带上该 APPID,也就是这里超链接中的 client_id 参数。从这里我们也可以看出,授权服务器在校验的时候,会做两件事:1) 校验客户端的身份;2) 校验用户身份。
-
redirect_uri:该参数表示在登录校验成功/失败后,跳转的地址,即校验成功后,跳转到 www.javaboy.org 中的哪个页面。跳转的时候,还会携带上一个授权码参数,开发者再根据这个授权码获取 Access Token。
-
scope:该参数表示授权范围,即 www.javaboy.org 这个第三方应用拿着获取到的 Access Token 能干什么。
-
State:授权服务器会原封不动地返回该参数,通过对该参数的校验,可以防止 CSRF 攻击。
当用户单击登录超链接时,系统会将用户导入授权服务器的登录页面,这对应了图 15-2 中所示的步骤A;用户选择是否给予授权,这对应了图 15-2 中所示的步骤B;如果用户同意授权,则授权服务器会将页面重定向到 redirect_uri 指定的地址,同时携带一个授权码参数(如果一开始的链接提供了 state 参数,这单也会将 state 参数原封不动返回),这对应了图 15-2 中所示的步骤 C;根据步骤 C 中获取到的授权码,再结合自有的 client_id 和 grant_type、redirect_uri 等参数,向授权服务器请求令牌,这一步是在客户端的后端进行的,对用户不可见,这对应了图 15-2 中所示的步骤 D;授权服务器对参数进行校验之后,会返回 Access Token 和 Refresh Token,这个过程对应了图 15-2 中所示的步骤 E。
这就是授权码模式的工作流程。一般认为授权码模式是四种模式中最安全的一种模式,因为这种模式的 Access Token 不用经过浏览器或者移动端 App,是直接从项目的后端获取,并从后端发送到资源服务器上,这样就很大程度上减少了 Access Token 泄漏的风险。
简化模式
现在程序员搭建博客网站都流行静态网站,例如 Hexo、Jekyll 等,这类技术栈有一个共同的特点就是没有后端,开发者不需要将精力放在维护后端网站上,只需要专注于内容创作即可。
对于这种没有后端,只有页面的网站,如果想接入 GitHub 第三方登录功能,该怎么办呢?这就是本小节要介绍的简化模式(Implicit),图 15-3 所示表示简化模式的工作流程(图片来自 RFC6749 文档)。

它的工作流程为:
首先在 www.javaboy.org 网站上有一个 GitHub 第三方登录的超链接,这个超链接如下所示:
https://github.com/oauth/authorize?response_type=token&client_id=javaboy&redirect_uri=www.javaboy.org&scope=all&state=123
这里的参数和前面授权码模式的参数基本相同,只有 response_type 的参数值不一样,这里是 token,表示要求授权服务器直接返回 Access Token,和授权码模式相比,这里省略了获取授权码那一步,所以品作简化模式。
当用户单击登录超链接,系统会将用户导入授权服务器的登录页面,这对应了图 15-3 中所示的步骤A:用户选择是否给予授权,这对应了图 15-3 中所示的步骤B:如果用户同意授权,则授权服务器会将页面重定向到 redirect_uri 指定的地址,同时在 URL 中添加锚点参数携带 Access Token,如下所示:
http://localhost:8082/index.html#access_token=9fda1800-3b57-4d32-ad01-05ff700d44cc&token_type=bearer&expires_in=1940&state=123
这里采用锚点参数而不是 URL 地址参数,主要是为了避免中间人攻击,其中 token_type 表示令牌类型,expires_in 表示令牌过期时间,这对应了图15-3中所示的步骤C。
接下来客户端向资源服务器发送请求,这个请求不需要携带令牌,这对应了图15-3中所示的步骤D:资源服务器返回一段 JS 脚本,这对应了图15-3中所示的步骤E:客户端执行 JS 脚本,提取出令牌 Access Token,这对应了图 15-3 中所示的步骤F、步骤G。
这就是简化模式的工作流程,它的整端很明显,因为没有后端,所以非常不安全,除非对安全性要求不高,否则不建议使用。
密码模式
使用密码模式(Password)有一个前提就是高度信任第三方应用,只有高度信任第三方应用,才可以让用户在第三方应用的页面上输入登录凭证。我们来看一下密码模式的流程,如图 15-4 所示(图片来自 RFC6749 文档)。

密码模式流程比较简单:
首先用户在第三方应用的登录页面输入登录凭证(用户名/密码),这对应了图 15-4 中所示的步骤A:第三方应用将用户的登录信息发送给授权服务器,获取到令牌 Access Token,这对应了图 15-4 中所示的步骤B:授权服务器检查用户的登录信息,如果没有问题,则返回 Access Token 和 Refresh Token,这对应了图15-4中所示的步骤C。
客户端模式
有的应用可能没有前端页面,只有一个后台,这种时候如果要用 OAuth2,就可以考虑客户端模式(Client Credentials),我们来看一个客户端模式的流程,如图 15-5 所示。

客户端模式的流程很简单,只有两步:
-
客户端发送一个请求到授权服务器,对应图 15-5 中所示的步骤A。
-
授权服务器验证通过后,会直接返回 Access Token 给客户端,对应图 15-5 中所示的步骤B。
由于客户端模式是以客户端的名义向授权服务器请求令牌,所以授权服务器的令牌 Access Token 颁发给客户端而非用户。
以上就是 OAuth2 中的四种不同授权模式。