1.OAuth2.0介绍
- OAuth(Open Authorization) 是一个关于授权(authorization)的网络开发标准,允许用户授权第三方应用访问他们存储在另外的服务提供者的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth在全世界得到广泛应用,目前最新的版本是2.0。
OAuth协议:https://tools.ietf.org/html/rfc6749
协议特点 - 简单,不管是OAuth服务供者还是应用开发者,都容易理解和使用
- 安全,没有使用到用户密匙等信息,更安全灵活
- 开放,任何服务提供者都可以使用OAuth,任何软件开发商都可以使用OAuth
1.1应用场景
- 原生app授权:app登录请求后台接口,为了安全认证,所有请求都带token信息。例如:登录验证,请求后台数据接口。
- 前后端分离单页面应用:前后端分离框架,前端请求后台数据,需要进行oauth2安全认证,比如使用vue,,react或者h5开发的app。
- 第三方应用登录,例如qq,微信,微博的授权登录。
例如下面这个例子 - 有一个"云冲印"的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让"云冲印"读取自己储存在Google上的照片。只有得到用户的授权,Google才会同意"云冲印"读取这些照片。那么,"云冲印"怎样获得用户的授权呢?
- 传统方法是,用户将自己的Google用户名和密码,告诉"云冲印",后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点:
"云冲印"为了后续的服务,会保存用户的密码,这样很不安全。 - Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。
"云冲印"拥有了获取用户储存在Google所有资料的权力,用户没法限制"云冲印"获得授权的范围和有效期。 - 用户只有修改密码,才能收回赋予"云冲印"的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。
只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。
生活中常见的oauth2场景,京东商城(https://www.jd.com/)接入微信开放平台,可以通过微信登录。
1.2基本概念
- Thrid-party application:第三方应用程序,,又称为客户端。例如上面例子中的云冲印。
- HTTP Servide:HTTP服务提供者,又称为服务提供商,例如;例子中的Goole。
- Resource Owner:资源拥有者,又称用户(user)。
- User Agent:用户代理,例如:浏览器。
- Authorization server:授权服务器,即服务提供商专门用来做认证授权的服务器。
- Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。与授权服务器可以是用一台服务器,也可以是不同的服务器。
OAuth的作用是让客户端安全可控的获取用户的授权,与服务提供商进行交互。
1.3优缺点
优点
- 更安全,客户端不接触用户密码,服务器端更易集中保护。
- 广泛传播,并被持续使用
- 短寿命和封装的token
- 资源服务器和授权服务器解耦
- 集中式授权,简化客户端
- HTTP/JJSON友好,易于请求和传递token
- 考虑多种客户端架构场景
- 客户可以具有多种不同的信任级别
缺点 - 协议框架太宽泛,造成各种实现的兼容性和互操作性较差
- 不是一个认证协议,本身并不能告诉你任何用户信息
2.OAuth2的设计思路
OAuth在客户端与服务提供商之间设置一个授权层(authorizer layer)。客户端不能直接登录服务提供商,只能登录授权层,,以此将用户与客户端分离开来。客户端登录授权层所用的令牌(token),与用户的密码不同,用户可以在登录的时候,指定授权层令牌的权限范围和有效期,客户端登录授权层以后,服务提供商根据令牌的权限范围和有效期,向客户端提供用户存储的资料。
OAuth2.0的运行流程如下图:摘自RFC 6749
(A)用户打开客户端以后,客户端要求用户给予授权
(B)用户同意客户端给予授权客户端
C)使用上一步获得的授权向授权服务器申请令牌
(D)授权服务器对客户端进行授权验证后,确认无误,同意发放令牌
(E)客户端使用令牌向资源服务器申请获取资源
(F)资源服务器确认令牌无误之后,同意向客户端开放资源
令牌(token)与密码(password)的作用都是一样的,都可以进入系统,但是有三点差异
- 令牌是短期的,到期会自动失效,用户自己无法修改,密码一般长期有效,用户不修改密码,就会一直有效
- 令牌可以被数据所有者撤销,会立即失效。密码一般不允许被他人撤销
- 令牌有权限范围(scope)。对于网络来说,只读令牌就比读写令牌更安全,密码一般是完整权限。
上面这些设计既保证了第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是OAuth2.0的优点
2.1客户端授权模式
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth2.0对于如何颁发令牌的细节,规定的非常详细。具体来说,一共分成四种授权类型(authorization grant),即四种颁发令牌的方式,适用于不同的互联网场景。
- 授权码模式(authorization code)
- 密码模式(resource owner password credentials)
- 简化(隐式)模式(implicit)
- 客户端模式(client credentials)
无论是哪一种授权模式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端ID(client ID)和客户端密匙(client secret)。这是为了防止令牌被滥用。没有备案过的第三方应用,是不会拿到令牌的。
授权码模式
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该授权码获取
这种方式是最常用的流程,安全性也最高。适用于那些有后端的web应用。授权码通过前端传送,令牌则是存储在后端,而且所有与资源服务器的通讯都在后端完成。这样做前后端分离的话。可以避免令牌泄露。
适用场景:目前市面上主流的第三方验证都是采用这种模式。
(A)用户访问客户端,后者将前者导向授权服务器
(B)用户选择是否给予客户端授权
©假设用户给予客户端授权,授权服务器将用户导向客户端事先指定的重定向URL(redirection URL),同时附上一个授权码
(D)客户端收到授权码,附上早先的重定向URL,向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)授权服务器核对了授权码和重定向URL,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)
1.A网站提供一个链接,用户点击链接以后就可以跳转到B网站,授权用户数据给B网站使用。下面是A网站跳转B网站的一个实例链接。
> https://b.com/oauth/authorize?
> response_type=code& #要求返回授权码(code)
> client_id=CLIENT_ID& #让 B 知道是谁在请求
> redirect_uri=CALLBACK_URL& #B 接受或拒绝请求后的跳转网址
> scope=read # 要求的授权范围(这里是只读)
>
客户端申请授权的URl必须包含以下参数
- response_type:表示授权类型,必选项,此处的值固定为code
- client_id:客户端的id,必选项
- redirect_uri:表示重定向URL,可选项
- scope:表示申请权限的范围,可选项
- state:表示客户端的当前状态,可以指定任意值,授权服务器会原封不动的返回这个值
2.用户跳转后B网站会要求用户登录,然后询问用户是否给与A网站授权,用户表示同意,这时B网站就会跳回redirect_uri参数指定的网址,跳转时会返回一个授权码,例如下面
> https://a.com/callback?code=AUTHORIZATION_CODE #code参数就是授权码
>
3.A网站拿到授权码以后,就可以在后端向B网站申请令牌。用户不可见,服务端行为
> https://b.com/oauth/token?
> client_id=CLIENT_ID&
> client_secret=CLIENT_SECRET& # client_id和client_secret用来让 B 确认 A 的身份,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":{...}
> }
简化(隐式)模式
有些web应用是纯前端应用,没有后端,这时就不能用授权吗模式,必须将令牌存储在前端。RFC 6479就规定了第二种方式,允许直接向前端颁发令牌,这个模式没有授权码这个中间步骤,所以称为(授权码)隐藏模式(implicit)
简化模式不通过第三方应用程序的服务器,直接在浏览器中向授权服务器申请令牌,跳过了授权码这个步骤,所有步骤在浏览器中完成,且客户端不需要认证。这种方式直接把令牌传给前端是非常不安全的。因此,只能适用于对安全性要求不高的场景,并且令牌的有效期非常短,通常就是会话期间(session)有效,浏览器关闭,令牌就失效。
(A)客户端将用户导向授权服务器
(B)用户是否给予客户端授权
©假设用户给予授权,授权服务器将用户导向客户端指定的重定向URL,并在URL的Hash部分包含了访问令牌
(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值
(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌
(F)浏览器执行上一步获得的脚本,提取出令牌
(G)浏览器将令牌发给客户端
1.A网站提供一个链接,要求用户转到B网站,授权用户数据给A网站使用
> https://b.com/oauth/authorize?
> response_type=token& # response_type参数为token,表示要求直接返回令牌
> client_id=CLIENT_ID&
> redirect_uri=CALLBACK_URL&
> scope=read
>
2.用户跳转到B网站,登录后同意给予A网站授权。这时,B网站就会跳回redirect_uri参数指定的跳转网址,并且把令牌最为URL参数,传给A网站
> https://a.com/callback#token=ACCESS_TOKEN #token参数就是令牌,A 网站直接在前端拿到令牌。
>
密码模式
如果你高度信任某个应用,RFC 6749也允许把用户名和密码直接告诉该应用。该应用就使用你的密码申请令牌,这种方式成为密码模式(password)。
在这种模式下用户必须把密码传给客户端,但是客户端不得存储密码。这通常在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而授权服务器只有在其他模式无法执行的情况下,才能考虑使用这种模式。
适用场景:自家公司搭建的授权服务器
(A)用户向客户端提供用户名与密码
(B)客户端将用户名和密码发给授权服务器,向后者请求令牌
©授权服务器确认无误后,向客户端发送令牌
1. A 网站要求用户提供 B 网站的用户名和密码,拿到以后,A 就直接向 B 请求令牌。整个过程中,客户端不得保存用户的密码。
> https://oauth.b.com/token?
> grant_type=password& # 授权方式是"密码式"
> username=USERNAME&
> password=PASSWORD&
> client_id=CLIENT_ID
>
- B 网站验证身份通过后,直接给出令牌。注意,这时不需要跳转,而是把令牌放在 JSON 数据里面,作为 HTTP 回应,A 因此拿到令牌。
客户端模式
客户端模式(Client Credentials Grant) 指客户端以自己的名义而不是用户的名义,向服务提供商进行授权
适用于没有前端的命令行应用,既在命令行下请求应用,一般用来提供给我们完全信任的服务器端服务。
(A)客户端向授权服务器进行身份认证,并要求一个访问令牌
(B)授权服务器确认无误后,向客户端提供访问令牌
1.A应用在命令行向B发出请求
> https://oauth.b.com/token?
> grant_type=client_credentials&
> client_id=CLIENT_ID&
> client_secret=CLIENT_SECRET
2. B 网站验证通过以后,直接返回令牌
2.2令牌的使用
A网站拿到令牌以后,就可以向B网站的API请求数据了
此时,每个发到API的请求,都必须携带令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面
> curl -H "Authorization: Bearer ACCESS_TOKEN" \
> "https://api.b.com"
>
也可以通过添加请求参数access_token请求数据
2.3更新令牌
令牌的有效期如果到了,让用户再走一遍上面的流程,再申请一个新的令牌,体验很不好,并且也没有必要。OAuth2.0允许用户自动更新令牌。
具体做法是:B网站颁发令牌的时候,颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token字段)。令牌到期前,用户使用refresh token发一个请求,去更新令牌。
> https://b.com/oauth/token?
> grant_type=refresh_token& # grant_type参数为refresh_token表示要求更新令牌
> client_id=CLIENT_ID&
> client_secret=CLIENT_SECRET&
> refresh_token=REFRESH_TOKEN # 用于更新令牌的令牌
>
3. Spring Security OAuth2快速开始
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。主要实现了Autherization(认证,who are you)和Access Control(访问控制,也就是 what are you allowed to do)。在架构上将认证和授权分离,并提供了扩展点。
认证(Authentication):用户认证就是判断一个用户的身份是否是合法的过程,用户去访问系统资源时,系统要求验证用户的身份信息,身份合法可继续访问,不合法则拒绝访问。常见的用户身份验证方式有:用户密码登录,二维码登录,手机验证码是登录,指纹认证等方式。
授权(Authorization):授权是用户认证通过根据用户的权限来控制用户访问的资源的过程。拥有资源的访问权限则正常访问,没有权限则拒绝访问。
将OAuth2和Spring Security集成,就可以得到一套完整的安全解决方案。我们可以通过Spring Security OAuth2构建一个授权服务器来验证用户身份以提供access_token,并使用这个access_token来从资源服务器请求数据。
- Authorize Endpoint:授权端点,进行授权
- Token Endpoint:令牌端点,经过授权拿到对应的Token
- Introspection Endpoint:校验端点,校验Token的合法性
- Revocation Endpoint:撤销端点,撤销授权
3.2整体架构
- 用户访问,此时没有token。oauth2 RestTemplate会报错,这个报错信息会被oauth2ClientContextFilter捕获并重定向到授权服务器
- 授权服务器通过Authorization Endpoint进行授权,并通过AuthorizationServerTokenServices生成授权码并返回给客户端
- 客户端拿到授权码去授权服务器通过Token Endpoint调用AuthorizationServerTokenServices生成token并返回给客户端
- 客户端拿到token去资源服务器访问资源,一般会通过Oauth2AuthenticationManager调用ResourceServerTokenServices进行校验,校验通过可以获取资源