Spring Security oAuth2(一)
认证与授权是所有应用都必须要实现的,也是所有后端工程师必备的技能,本篇文章旨在快速上手使用 Spring 提供的 Spring Security oAuth2 搭建一套验证授权及资源访问服务,在实现企业微服务架构时能够有效的控制多个服务的统一登录、授权及资源保护工作。主要内容是oAuth2协议,认证/授权登录的交互过程以及 oAuth 2.0 定义的四种授权模式。
oAuth协议
oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。
简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
令牌与密码
令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。
(1)令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
(2)令牌可以被数据所有者撤销,会立即失效。以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。
(3)令牌有权限范围(scope),比如只能进小区的二号门。对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。
Spring Security
Spring Security 是一个安全框架,前身是 Acegi Security,能够为Spring企业应用系统提供声明式的安全访问控制。Spring Security 基于Servlet过滤器、IoC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。所以 Spring Security 像所有Spring项目一样,它的真正强大之处在于它可以轻松扩展以满足定制化需求。
oAuth应用场景
我们假设你有一个“云笔记”产品,并提供了“云笔记服务”和“云相册服务”,此时用户需要在不同的设备(PC、Android、iPhone、TV、Watch)上去访问这些“资源”(笔记,图片) 那么用户如何才能访问属于自己的那部分资源呢?此时传统的做法就是提供自己的账号和密码给我们的“云笔记”,登录成功后就可以获取资源了。但这样的做法会有以下几个问题:
1、笔记服务和相册服务会分别部署,难道我们要分别登录吗?
2、如果有第三方应用程序想要接入我们的笔记,难道需要用户提供账号和密码给第三方应用程序,让他记录后再访问我们的资源吗?
3、用户如何限制第三方应用程序在我们“笔记”的授权范围和使用期限?难道把所有资料都永久暴露给它吗?
4、如果用户修改了密码收回了权限,那么所有第三方应用程序会全部失效。
5、只要有一个接入的第三方应用程序遭到破解,那么用户的密码就会泄露,后果不堪设想。
为了解决如上问题,oAuth 应运而生,下面是一些oAuth中出现的名词解释: 1、第三方应用程序(Third-party application)又称之为客户端(client),假设我们的App想要使用 QQ、微信等第三方登录。对我们的产品来说,QQ、微信登录是第三方登录系统,对于 QQ、微信等系统我们又是第三方应用程序。 2、HTTP 服务提供商(HTTP service):我们的云笔记产品以及 QQ、微信等都可以称之为“服务提供商”。 3、资源所有者(Resource Owner):又称之为用户。 4、用户代理(User Agent):比如浏览器,代替用户去访问这些资源。 5、认证服务器(Authorization server):即服务提供商专门用来处理认证的服务器,简单点说就是登录功能(验证用户的账号密码是否正确以及分配相应的权限) 6、资源服务器(Resource server):即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。简单点说就是资源的访问入口。
oAuth交互过程
oAuth 在 “客户端” 与 “服务提供商” 之间,设置了一个授权层(authorization layer)。“客户端” 不能直接登录 “服务提供商”,只能登录授权层,以此将用户与客户端区分开来。“客户端” 登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。“客户端” 登录授权层以后,“服务提供商” 根据令牌的权限范围和有效期,向 “客户端” 开放用户储存的资料。拿慕课网使用QQ登录作为例子画出流程图:
令牌的访问与刷新
Access Token
Access Token 是客户端访问资源服务器的令牌。拥有这个令牌代表着得到用户的授权。然而,这个授权应该是临时的,有一定有效期。这是因为,Access Token 在使用的过程中可能会泄露。给 Access Token 限定一个 较短的有效期可以降低因 Access Token 泄露而带来的风险。
然而引入了有效期之后,客户端使用起来就不那么方便了。每当 Access Token 过期,客户端就必须重新向用户索要授权。这样用户可能每隔几天,甚至每天都需要进行授权操作。这是一件非常影响用户体验的事情。希望有一种方法,可以避免这种情况。
于是 oAuth2.0 引入了 Refresh Token 机制
Refresh Token
Refresh Token 的作用是用来刷新 Access Token。认证服务器提供一个刷新接口,例如:
1http://www.xxx.com/refresh?refresh_token=&client_id=
传入 refresh_token 和 client_id,认证服务器验证通过后,返回一个新的 Access Token。为了安全,oAuth2.0 引入了两个措施:
oAuth2.0 要求,Refresh Token 一定是保存在客户端的服务器上 ,而绝不能存放在狭义的客户端(例如 App、PC 端软件)上。调用 refresh 接口的时候,一定是从服务器到服务器的访问。 oAuth2.0 引入了 client_secret 机制。即每一个 client_id 都对应一个 client_secret。这个 client_secret 会在客户端申请 client_id 时,随 client_id 一起分配给客户端。客户端必须把 client_secret 妥善保管在服务器上,决不能泄露。刷新 Access Token 时,需要验证这个 client_secret。 实际上的刷新接口类似于:
1http://www.xxx.com/refresh?refresh_token=&client_id=&client_secret=
以上就是 Refresh Token 机制。Refresh Token 的有效期非常长,会在用户授权时,随 Access Token 一起重定向到回调 URL,传递给客户端。
客户端四种授权模式
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。oAuth 2.0 定义了四种授权方式。
模式 | 场景 |
---|---|
implicit | 简单模式,用于纯前端应用,无Refresh Token |
authorization code | 授权码模式最常用,安全性也最高,适合有后端的应用 |
resource owner password credentials | 密码模式,直接给密码,但是客户端不应该存储密码,信任时才用,通常用于企业内部之间的系统的认证/授权 |
client credentials | 客户端模式,信任度极高,无用户界面操作,直接获得授权 |
① 授权码模式
授权码模式适用于有自己的服务器的应用,它是一个一次性的临时凭证,用来换取 access_token
和 refresh_token
。认证服务器提供了一个类似这样的接口:
1https://www.xxx.com/exchange?code=&client_id=&client_secret=
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
② 简单模式
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。这种模式允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit),也叫简单模式,这种模式是拿不到 Refresh Token 的。其整个授权流程如下:
③ 密码模式
在你高度信任某个应用时可以使用这种模式,在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分。
一个典型的例子是同一个企业内部的不同产品要使用本企业的 oAuth2.0 体系。在有些情况下,产品希望能够定制化授权页面。由于是同个企业,不需要向用户展示“xxx将获取以下权限”等字样并询问用户的授权意向,而只需进行用户的身份认证即可。这个时候,由具体的产品团队开发定制化的授权界面,接收用户输入账号密码,并直接传递给鉴权服务器进行授权即可。
④ 客户端模式
如果信任关系再进一步,或者调用者是一个后端的模块,没有用户界面的时候,可以使用客户端模式。鉴权服务器直接对客户端进行身份验证,验证通过后,返回 token。