Spring Security系列教程28--OAuth2.0协议详解

3,086 阅读16分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

前言

截止到现在,一一哥 已经带各位把Spring Security中的主要功能学完了,并且剖析了这些内容的底层实现原理,希望你可以有所收获。

但是在安全认证领域,还有另一种很重要的授权机制---OAuth2.0开放授权。而且OAuth2.0开放授权与Spring Security经常配合使用,这两者之间可以说是“焦不离孟,孟不离焦”的搭配关系,所以为了进一步掌握Spring Security,在这里 壹哥 给大家再介绍另一个OAuth2.0开放授权协议。

OAuth2.0开放授权并不是Spring Security框架里的一部分,只是两者经常配合使用而已,所以我得先给大家普及一下OAuth2.0的内容,毕竟OAuth2.0的内容还是蛮难理解的。

一. OAuth2.0简介

1. OAuth2.0概述

咱们先来看看在RFC6749号文件中对OAuth2.0的定义,如下图所示:

简单来说,OAuth2.0 是一种授权机制,其内部引入了一个授权层,可以分离两种不同的角色:客户端和资源所有者。资源所有者同意客户端(第三方应用程序)的请求以后,资源服务器就可以向客户端颁发短期的进入令牌(token),用来代替密码,客户端通过这个令牌,去请求有限的资源数据来使用。所以OAuth2.0 的核心就是向第三方应用颁发令牌。

2. OAuth2.0发展历程

OAuth(Open Authorization)--开放授权,这是一种关于开放授权的协议标准。 所谓的OAuth2.0,表示这是OAuth协议第2版,但并不兼容之前的OAuth1.0协议,即现在已经完全废止了OAuth1.0协议。

那么为什么会废止OAuth1.0协议呢?我们先来简单说一下OAuth协议的发展历程。

2007年12月的时候,OAuth协议已经诞生了,并与2010年4月份,被IETF(国际互联网工程任务组)认可并作为认证授权的标准发布。但是这个OAuth1.0协议有些致命缺陷,就是它的签名逻辑特别复杂,开发时对程序员来说极度恶心。而且OAuth1.0的授权流程是很单一,存在较大的漏洞,容易被黑客攻击。所以在2012年10月的时候,IETF又更新发布了OAuth2.0协议,在这个版本中,放弃了之前复杂的数字签名和加密方案,使用HTTPS来作为安全保障手段,这降低了程序员的开发难度。但是OAuth2.0协议与OAuth1.0协议互不兼容,所以现在我们进行开发时,都是采用OAuth2.0协议,而不再采用OAuth1.0协议。

3. OAuth2.0功能

通过上一小节,我们了解到OAuth是一种关于开放授权的协议,该协议其实是一个 服务提供商 授权 第三方应用 获取 资源所有者 部分访问权限 的授权机制。通俗的说,就是OAuth协议允许用户在不提供密码给第三方应用的情况下,使得第三方应用有权获取用户数据等基本信息。在整个授权过程中,第三方应用都不必获取用户的密码,就可以取得用户部分资源的使用权限,所以OAuth是一种安全开发的协议。

比如我们在CSDN上进行登录时,可以利用CSDN这个平台自身注册的账号,也可以使用第三方账号(QQ、微信、微博等)来进行登录,如下图:

比如我们选择利用QQ账号登录CSDN,如下图:

这时候我们可以打开QQ扫码,或者点击自己的QQ头像,就可以实现利用QQ帐号登录到CSDN这个网站上。在这个过程中,我们其实不必担心自己的QQ密码会泄露给CSDN,因为CSDN是拿不到我们的QQ密码的。

在这个登录过程中,CSDN相对于QQ来说是第三方应用,QQ就是服务提供商。 在QQ内部,会有一个认证服务器作为专门的授权中心,QQ用户的用户名头像等信息都属于资源,这些资源可能会存放在一台专门的资源服务器上,也可能会直接存放在QQ的认证服务器上。当CSDN经过了QQ的认证之后,就可以获取到QQ上的用户昵称、头像、性别等信息,从而实现利用QQ的用户,登录到CSDN的网站上,免除用户重复的注册过程。

4. OAuth2.0使用场景

根据上一小节,我们可以总结一下OAuth2.0的使用场景,大致如下:

  • 第三方应用登录: 比如利用QQ,微博,微信授权登录到其他网站或App。
  • 分布式或微服务项目中授权: 在Java分布式或微服务开发时,后端业务拆分成若干服务,服务之间或前端进行请求调用时,为了安全认证,可以利用OAuth2.0进行认证授权。

二. OAuth2.0核心概念(重点)

1. 核心概念

另外从上面的章节中,我们也了解到OAuth2.0的几个核心概念,如下:

  • 客户端/第三方应用: 获取用户信息, 如果我们使用QQ账号来登录CSDN网站,这时候CSDN相对于QQ来说就是第三方应用,或者成为是QQ的客户端。
  • 服务提供商: 对外提供请求的服务器,比如上面说的QQ。
  • Resource Owner: 资源所有(拥有)者,首先我们要明确“资源”的含义,“资源”是指我们请求的各种URL接口及各种数据,这里的资源所有者,也就是登录用户。
  • Authorization Server: 认证服务器,即服务提供商(比如QQ)专门用来处理认证的服务器。
  • Resource Server: 资源服务器,即服务提供商存放用户资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
  • Access token: 访问令牌,供客户端用来请求Resource Server(API)的资源,Access token通常是short-lived短暂的。

2. 令牌与密码

在OAuth2.0中,提到了一个令牌的概念,那么令牌是什么?与我们平常所用的密码又有什么区别?其实OAuth中的令牌(token)与密码(password)作用是一样的,都可以控制用户是否可以进入系统,但是有几点不同。

  • 令牌是短期的,到期会自动失效,用户自己无法修改;密码一般是长期有效的,用户不修改,就不会发生变化。
  • 令牌可以被资源所有者撤销,会立即失效;密码一般不允许被他人撤销。
  • 令牌有一定的授权范围(scope),密码一般是完整权限。

注意:

令牌的出现,既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。但是只要知道了令牌,就能进入系统,系统一般不会再次确认身份,所以令牌必须保密,泄漏令牌与泄漏密码的后果是一样的。这也是为什么令牌的有效期,一般都设置得很短的原因。

三. OAuth2.0授权流程(重点)

了解完OAuth2.0中的各种基本概念之后,我们需要弄清楚OAuth2.0的授权流程,这样后面才能根据这个授权流程进行开发。

1. 授权流程概述

OAuth 2.0的运行流程如下图,摘自RFC 6749。

我们这里以QQ账号登录CSDN网站为例,给大家说一下OAuth2.0的授权执行流程:

(A). 某个用户(资源拥有者)打开CSDN(客户端)网站以后,CSDN(客户端)要求用户(资源拥有者)给予授权;
(B). 如果用户(资源拥有者)同意,就给予CSDN(客户端)授权;
(C). CSDN(客户端)使用上一步获得的授权,向QQ的认证授权服务器申请访问令牌Access Token;
(D). QQ的认证授权服务器对CSDN(客户端)进行认证,确认无误后,同意发放访问令牌Access Token;
(E). CSDN(客户端)使用访问令牌,向QQ的资源服务器申请获取某些受保护的资源;
(F). QQ的资源服务器确认访问令牌无误后,同意向CSDN(客户端)开放受保护的资源。

2. 授权流程详解

第一步:在CSDN官网点击选择QQ登录

当我们点击选择QQ登录的图标时,实际上是向CSDN服务器发起了一个

graph.qq.com/oauth2.0/sh…ype=qq的请求,CSDN服务器会响应一个302重定向地址,指向QQ授权登录。这次访问会携带一个pcAuthCallback,以便QQ那边授权成功后,再次让浏览器发起对这个callback的请求,这样QQ就知道授权后要返回到哪个页面。

第二步:跳转到QQ登录页面输入用户名密码,然后点授权并登录

接着浏览器跳转到重定向地址并访问 www.qq.com/authorize?c…** 。

第三步:跳回到CSDN页面,登录成功

这一步背后的过程其实是最繁琐的,但对于用户来说是完全感知不到的。

授权QQ服务器在判断登录成功后,使页面重定向到之前CSDN发来的callback地址并附上code授权码,接着会跳转到QQ的认证服务器发起一个新的请求,获取到AccessToken令牌。获取token成功后,CSDN服务器会用拿到的token换取用户信息,最后将用户信息储存起来,并返回给浏览器其首页的视图,到此OAuth2.0授权结束。

四. OAuth2.0授权方式(重点)

讲解完了OAuth2.0的授权流程,接下来壹哥再给大家介绍另一个重要概念,即OAuth2.0的授权方式。

在RFC 6749标准中定义了获得令牌的四种授权方式(authorization grant),即 OAuth2.0 有四种获得令牌的方式,我们开发时可以选择最适合自己的那一种,向第三方应用颁发令牌,这四种授权方式如下:

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

1. 授权码模式

授权码模式(authorization code),指的是第三方应用先申请一个授权码,然后再用该授权码获取访问令牌的方式。 这种方式是功能最完整、流程最严密的授权模式,也是开发时最常用,安全性最高的授权模式,适用于那些有后端的 Web 应用,比如我们前面介绍的以QQ信息登录到CSDN网站上就是采用授权码模式。授权码由前端发起申请,令牌则储存在后端,而且所有与资源服务器的通信都在后端完成,这样前后端分离,可以避免令牌泄漏。授权码模式执行流程可以分为如下4步:

接下来我把这4步再分开详细说明一下。

(1). 用户请求客户端A,

由客户端A将用户导向认证服务器B请求授权码,假设用户同意授权,认证服务器B会将用户导向客户端A事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

https://b.com/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

比如我们前面利用QQ进行登录到CSDN网站时,获取code授权码的url:

https://graph.qq.com/oauth2.0/show?which=Login
    &display=pc
    &client_id=100270988899
    &response_type=code
    &redirect_uri=https://passport.csdn.net/account/login?pcAuthType=qq
    &state=test
  • response_type:授权类型,必选项,为固定值code;
  • client_id: 客户端id,必选项,表示告诉 认证服务器B 是谁在发起请求;
  • redirect_uri:可选项,是 认证服务器B 接受或拒绝请求后的回调网址;
  • scope:可选项,表示要求的授权范围(这里是只读);
  • state: 客户端状态。

(2). 客户端A收到授权码code,

附上之前的"重定向URI"参数,向认证服务器B申请令牌:GET /oauth/token?response_type=code&client_id=test&redirect_uri=重定向页面链接,请求成功后返回code授权码,一般有效时间是10分钟。

https://a.com/callback?code=AUTHORIZATION_CODE

(3). 客户端A向认证服务器B发起申请令牌的请求,

B会核对授权码和重定向URI,确认无误后,向客户端A返回访问令牌(access token)和更新令牌(refresh token),POST /oauth/token?response_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=重定向页面链接。

https://b.com/oauth/token?
 client_id=CLIENT_ID&
 client_secret=CLIENT_SECRET&
 grant_type=authorization_code&
 code=AUTHORIZATION_CODE&
 redirect_uri=CALLBACK_URL

(4). 认证服务器B 收到请求以后,

就会颁发令牌,向redirect_uri指定的网址,发送一段 JSON 数据:

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101,
  "info":{...}
}

2. 简化模式

简化模式(implicit grant type) ,有些 Web 应用只有前端,没有后端,这时就只能将令牌储存在前端浏览器中。所以在RFC 6749中还规定了第二种授权方式,允许直接向前端颁发令牌,因为是直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,所以称为 "隐藏(授权码)式implicit",或者被称为隐式授权模式。

隐式授权模式的客户端一般是指用户浏览器,访问令牌通过重定向的方式传递到用户浏览器中,再通过浏览器的JavaScript代码来获取访问令牌。这种授权方式的所有步骤都是在浏览器中完成,没有通过第三方应用程序的服务器,访问令牌直接暴露在浏览器端,令牌对访问者是可见的,且客户端不需要认证,所以可能会导致访问令牌被黑客获取。该模式适用于对安全性要求不高的应用中,仅适用于需要临时访问的场景。

流程步骤:

(A). 客户端将用户导向认证服务器;
(B). 用户决定是否给于客户端授权;
(C). 假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌;
(D). 浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值;
(E). 资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌;
(F). 浏览器执行上一步获得的脚本,提取出令牌;
(G). 浏览器将令牌发给客户端。

请求URL:

GET /authorize?response_type=token&client_id=s6BhdRkqt3
    &state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 
    
HTTP/1.1

Host: server.example.com 

与授权码模式相比,用户的登录环节是一样的,只是在授权成功之后的重定向上,授权码模式是携带一个认证码,由客户端通过认证码申请访问令牌。而隐式授权模式则直接将访问令牌作为URL的散列部分传递给浏览器,散列部分是专门用于指导浏览器行为的,比如浏览器页面定位的锚点就可以由散列属性来进行判定。

注意

简化授权方式是把令牌直接传递给前端,这是很不安全的。因此,该授权方式适用于一些对安全性要求不高的场景,并且令牌的有效期必须非常短,通常是在会话期间(session)有效,当浏览器关掉,令牌就失效了。

3. 用户名密码模式

密码模式(Resource Owner Password Credentials Grant) ,客户端提供一个专用页面,然后用户向客户端提供自己的用户名和密码,客户端使用这些信息,向"服务提供商"索要授权。在这种模式中,用户必须把自己的用户名密码给客户端,但是客户端不得存储密码。该授权模式适用于用户对客户端高度信任的情况下使用,一般不支持refresh token。

执行步骤:

(A). 用户向客户端提供用户名和密码;
(B). 客户端将用户名和密码发给认证服务器,向后者请求令牌;
(C). 认证服务器确认无误后,向客户端提供访问令牌;

响应信息:

POST /token HTTP/1.1 
Host: server.example.com      
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW      
Content-Type: application/x-www-form-urlencoded  
grant_type=password&username=johndoe&password=A3ddj3w

注意

用户名密码授权方式需要用户给出自己的用户名/密码,显然风险很大,因此只适用于其他授权方式都无法采用的情况,而且必须是用户高度信任的应用。

4. 客户端模式

客户端模式, 指客户端以自己的名义,而不是以用户的名义,向"服务提供商"发起认证。严格地说,客户端模式并不属于OAuth2.0框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。


客户端授权模式通常是由客户端提前向授权服务器申请应用公钥、私钥,并通过这些关键信息向授权服务器申请访问令牌,从而得到资源服务器提供的资源,比如常见的微信公众平台、支付宝支付平台授权等。该模式适用于没有前端的命令行应用中,即在命令行下请求令牌。

执行步骤:

(A). 客户端向认证服务器发起身份认证,并要求一个访问令牌;
(B). 认证服务器确认无误后,向客户端提供访问令牌。

A步骤中,客户端发出的HTTP请求,包含以下参数:

  • granttype:表示授权类型,此处的值固定为"clientcredentials",必选项。
  • scope:表示权限范围,可选项。

响应信息:

POST /token HTTP/1.1
Host: server.example.com

Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials

#grant_type参数等于client_credentials表示采用凭证式,
#client_id和client_secret用来让 B 确认 A 的身份

注意

这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。

希望通过本文的介绍,可以让你对OAuth2.0协议有所掌握,后面我们可以把Spring Security与OAuth2.0结合,实现更灵活的认证授权。