Spring Security系列教程28--OAuth2.0协议详解
Spring Security系列教程28--OAuth2.0协议详解
前言
截止到现在,已经带各位把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结合,实现更灵活的认证授权。