OAuth 2.0 一词中的 “Auth” 表示 “授权”,字母 “O” 是 Open 的简称,表示 “开放” ,连在一起就表示 “开放授权”。OAuth 2.0 就是一种授权协议。保证第三方软件只有在获得授权之后,才可以进一步访问授权者的数据。
OAuth 1.0: 想用一套授权机制来应对现实中的所有场景,弊端还包括安全上的固化攻击等问题。
第一部分是基础篇,就是你必须要掌握的 OAuth 2.0 的基础知识。在这一模块中,我会和你细致地讲解授权码许可(Authorization Code)类型的流程,包括 OAuth 2.0 内部组件之间的通信方式,以及授权服务、客户端(第三方软件)、受保护资源服务这三个组件的原理。
在此基础上,我还会为你讲解其他三种常见许可类型,分别是资源拥有者凭据许可(Resource Owner Password Credentials)、隐式许可(Implicit)、客户端凭据许可(Client Credentials)的原理,以及如何选择适合自己实际场景的授权类型。这样一来,你就能掌握整个 OAuth 2.0 中所有许可类型的运转机制了,并且能够从容地在实际工作环境中使用它们。
第二部分进阶篇的内容,我会侧重讲一些 OAuth 2.0 “更高级” 的用法,可以让你知道如何更安全地用、扩展地用 OAuth 2.0。
所以,这部分内容会包括如何在移动 App 中使用 OAuth 2.0,因使用不当而导致的 OAuth 2.0 安全漏洞有哪些,以及如何利用 OAuth 2.0 实现一个 OpenID Connect 用户身份认证协议。此外,我还邀请了微服务技术领域的专家杨波老师,给我们分享了一个架构案例,基于 OAuth 2.0/JWT 的微服务参考架构。
在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端(即第三方软件)、授权服务和受保护资源。
还是拿小兔软件来举例子,将官方的称呼 “照进现实”,对应关系就是,
资源拥有者 -> 小明,
第三方软件 -> 小兔软件,
授权服务 -> 京东商家开放平台的授权服务,
受保护资源 -> 小明店铺在京东上面的订单。
间接通信和直接通信
授权码许可流程有两种通信方式。一种是前端通信,因为它通过浏览器促成了授权码的交互流程,比如京东商家开放平台的授权服务生成授权码发送到浏览器,第三方软件小兔从浏览器获取授权码。正因为获取授权码的时候小兔软件和授权服务并没有发生直接的联系,也叫做间接通信。另外一种是后端通信,在小兔软件获取到授权码之后,在后端服务直接发起换取访问令牌的请求,也叫做直接通信
使用授权码:
不使用授权码,把访问令牌发送给第三方软件小兔的后端服务:
在 OAuth 2.0 中,访问令牌被要求有极高的安全保密性,因此我们不能让它暴露在浏览器上面,只能通过第三方软件(比如小兔)的后端服务来获取和使用,以最大限度地保障访问令牌的安全性。正因为访问令牌的这种安全要求特性,当需要前端通信,比如浏览器上面的流转的时候,OAuth 2.0 才又提供了一个临时的凭证:授权码。通过授权码的方式,可以让用户在授权服务上给第三方软件授权之后,还能重新回到第三方软件的操作页面上。
两个 “一伙”
OAuth 2.0 中的 4 个角色是 “两两站队” 的:资源拥有者和第三方软件“站在一起”,因为第三方软件要代表资源拥有者去访问受保护资源;授权服务和受保护资源“站在一起”,因为授权服务负责颁发访问令牌,受保护资源负责接收并验证访问令牌。
一定要有浏览器吗?
OAuth 2.0 是一个授权理念,或者说是一种授权思维。它的授权码模式的思维可以移植到很多场景中,比如微信小程序。
根据微信官方文档描述,开发者获取用户登录态信息的过程正是一个授权码的许可流程:
你可以看到,这个过程并没有使用到浏览器,但确实按照授权码许可的思想走了一个完整的授权码许可流程。
问题
答:一方面授权码也都有有效期,另外一方面除非再盗取了第三方应用软件的app_id、secret才能成功请求资源。
答:HTTPS 和 OAuth 是两个维度的安全,HTTPS解决的信息加密传输,OAuth 解决的是用token来代替用户名和密码传输。
小兔要去平台那里“备案”,也就是注册。注册完后,京东商家开放平台就会给小兔软件 app_id 和 app_secret 等信息,以方便后面授权时的各种身份校验。注册的时候,第三方软件也会请求受保护资源的可访问范围。
授权平台颁发授权码 code
颁发访问令牌 access_token
在颁发访问令牌的同时还会颁发刷新令牌 refresh_token 值,这种机制可以在无须用户参与的情况下用于生成新的访问令牌。正如我们讲到的小明使用小兔软件的例子,当访问令牌过期的时候,刷新令牌的存在可以大大提高小明使用小兔软件的体验。
JSON Web Token(JWT),是一种结构化、信息化令牌,结构化可以组织用户的授权信息,信息化就是令牌本身包含了授权信息。
JWT 这种结构化体可以分为 HEADER(头部)、PAYLOAD(数据体)和 SIGNATURE(签名)三部分。经过签名之后的 JWT 的整体结构,是被句点符号分割的三段内容。例如:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJVU0VSVEVTVCIsImV4cCI6MTU4NDEwNTc5MDcwMywiaWF0IjoxNTg0MTA1OTQ4MzcyfQ.1HbleXbvJ_2SW8ry30cXOBGR9FW4oSWBd3PWaWKsEXE
解码之后的数据:
HEADER 表示装载令牌类型和算法等信息,是 JWT 的头部。其中,typ 表示第二部分 PAYLOAD 是 JWT 类型,alg 表示使用 HS256 对称签名的算法。
PAYLOAD 表示是 JWT 的数据体,代表了一组数据。其中,sub(令牌的主体,一般设为资源拥有者的唯一标识)、exp(令牌的过期时间戳)、iat(令牌颁发的时间戳)是 JWT 规范性的声明,代表的是常规性操作。PAYLOAD 表示的一组数据允许我们自定义声明。
SIGNATURE 表示对 JWT 信息的加密签名。
JWT 的核心思想,就是用计算代替存储,有些 “时间换空间” 的 “味道”。当然,这种经过计算并结构化封装的方式,也减少了“共享数据库” 因远程调用而带来的网络传输消耗,所以也有可能是节省时间的
我们使用了 JWT 令牌之后,远程的服务端上面是不存储的,因为不再有这个必要,JWT 令牌本身就包含了信息。那么,如何来控制它的有效性问题呢?
给出了两种建议,一种是建立一个秘钥管理系统,将生成秘钥的粒度缩小到用户级别,另外一种是直接将用户密码当作密钥。
官方规范给出的使用访问令牌请求的方式,有三种,分别是:
POST /resource HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded access_token=b1a64d5c-5e0c-4a70-9711-7af6568a61fb
GET /resource?access_token=b1a64d5c-5e0c-4a70-9711-7af6568a61fb HTTP/1.1 Host: server.example.com
GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer b1a64d5c-5e0c-4a70-9711-7af6568a61fb
官方的建议是采用 Authorization 的方式来传递令牌。
我建议你采用表单提交,也就是 POST 的方式来提交令牌,因为表单提交的方式在保证安全传输的同时,还不需要去额外处理 Authorization 头部信息。
使用刷新令牌
刷新令牌是一次性的,使用之后就会失效,但是它的有效期会比访问令牌要长。
方式一:将访问令牌的 expires_in 值保存下来并定时检测,如果发现 expires_in 即将过期,则需要利用 refresh_token 去重新请求授权服务,以便获取新的访问令牌。
方式二:“现场”发现,收到一个访问令牌失效的响应,此时小兔软件立即使用 refresh_token 来请求一个访问令牌
受保护资源系统,需要注意的是权限的问题,这个权限范围主要包括:
不同的权限会有不同的操作,不同的权限也会对应不同的数据,不同的用户也会对应不同的数据。
资源拥有者凭据许可
如果小兔软件是官方出品,那么可以直接使用资源拥有者凭据许可:
步骤 1:当用户访问第三方软件小兔时,会提示输入用户名和密码。索要用户名和密码,就是资源拥有者凭据许可类型的特点。
步骤 2:这里的 grant_type 的值为 password,告诉授权服务使用资源拥有者凭据许可凭据的方式去请求访问。
步骤 3:授权服务在验证用户名和密码之后,生成 access_token 的值并返回给第三方软件。
客户端凭据许可
如果没有明确的资源拥有者,换句话说就是,小兔软件访问了一个不需要用户小明授权的数据,比如获取京东 LOGO 的图片地址,那可以直接使用客户端凭据许可类型:
步骤 1:第三方软件小兔通过后端服务向授权服务发送请求,这里 grant_type 的值为 client_credentials
步骤 2:在验证 app_id 和 app_secret 的合法性之后,生成 access_token 的值并返回
隐式许可
如果小兔软件就是只嵌入到浏览器端的应用且没有服务端,那就只能选择隐式许可:
步骤 1:用户通过浏览器访问第三方软件小兔。此时,第三方软件小兔实际上是嵌入浏览器中执行的应用程序。
步骤 2:通过app_id向授权服务发送请求,response_type 的值变成了 token,是要告诉授权服务直接返回 access_token 的值
步骤 3:生成 acccess_token 的值,通过前端通信返回给第三方软件小兔
隐式许可授权流程的安全性会降低很多
没有 Server 端的 App,使用PKCE 协议,不使用 app_secret,还要防止授权码 code 失窃
有 Server 端的 App,跟普通的 Web 应用几乎没有任何差别
OIDC (OpenID Connect) 是一种用户身份认证的开放标准,OIDC= 授权协议 + 身份认证,是 OAuth 2.0 的超集。单点登录、联合登录,都遵循的是 OIDC 的标准流程。
OIDC 和 OAuth 2.0 的角色对应关系
OIDC 的三个主要角色:
现在很多 App 都接入了微信登录,那么微信登录就是一个大的身份认证服务(OP)。一旦我们有了微信账号,就可以登录所有接入了微信登录体系的 App(RP),这就是我们常说的联合登录。
OIDC 和 OAuth 2.0 的关键区别
一个基于授权码流程的 OIDC 协议流程,跟 OAuth 2.0 中的授权码许可的流程几乎完全一致,唯一的区别就是返回access_token访问令牌时多返回了一个 ID_TOKEN,我们称之为 ID 令牌。这个令牌是身份认证的关键。
ID 令牌
D 令牌是一个 JWT 格式的令牌,可以解决身份认证后的登录态问题,是 OIDC 作为身份认证协议的关键所在。
备注:传统的登录基于Session,这里使用JWT Token方案,就不需要Session了(也不需要Cookie参与了)
用 OIDC 协议标准来实现单点登录
一个用户 G 要登录第三方软件 A,A 有三个子应用,域名分别是 a1.com、a2.com、a3.com。如果 A 想要为用户提供更流畅的登录体验,让用户 G 登录了 a1.com 之后也能顺利登录其他两个域名,就可以创建一个身份认证服务,来支持 a1.com、a2.com 和 a3.com 的登录。这就是我们说的单点登录,“一次登录,畅通所有”。