微服务技术手记

这篇文章主要是微服务技术的学习笔记,参考资料: time.geekbang.org/course/intr…
www.bbsmax.com/A/RnJWNaBwz…

持续总结更新。。。

微服务安全解决什么问题

背景

OAuth2 提出的背景就是开放系统间授权问题。比如我有一组照片存储在七牛云上,我想让云冲印打印出来,他门之间的关系如下: image.png 对于上面那种情况,可以分成三种角色:资源拥有者、客户应用、受保护的资源。也就是说我的照片,那么我本人就是照片的拥有者,而存储在七牛云上的照片就是受保护的资源,云冲印就是客户应用。那么我想让云冲印打印照片,那么云冲印就需要去七牛云访问我的照片,这就出现了 开放系统间授权的问题。 image.png

针对以上的问题,考虑一下解决方案:

  • 方案一:密码用户名复制
    也就是说,资源拥有者需要把用户名密码给云冲印,然后云冲印拿着用户名密码去访问七牛云。 image.png 显然这种方式,十分不安全,用户名密码泄露给云冲印了,这种方式常用于公司内部的应用。而开放系统间,这种第三方应用一般是不信任的。

  • 方案二:万能钥匙
    客户应用和受保护的资源间商定一个通用的开发者钥匙,这种方式需要客户应用和受保护的资源间有信任关系的,比如合作商等,这样才可以。但是这种 developer key 也会被丢失。 image.png

  • 方案三:特殊令牌
    这种方式是资源拥有者给客户应用一个特殊令牌,这个令牌只能访问受保护的资源。这种方式和OAuth2很相似。当然这里面牵扯着令牌发放、过期等一些管理令牌的操作。 image.png

除了开放系统间授权问题还有传统单块应用安全问题image.png

现代微服务安全

image.png 现代微服务安全主要有这样的问题,微服务粒度划分比较小,服务之间如何认证鉴权,应用形态也变成多种多样,不再是单一的浏览器应用。这个时候不再是传统服务去授权,需要独立的服务去做认证鉴权,核心的技术点也从原来的用户名密码改为Token机制。

总结

OAuth2解决问题域和场景:

  • 开放系统间授权(社交联合登录、开放API平台)
  • 现代微服务安全(单页浏览器App(HTML5/JS/无状态)、无线原生App、服务器端WebApp、微服务和API间调用)
  • 企业内部应用认证授权(IAM/SSO)

OAuth2白话介绍

OAth2最简向导(简述流程):

  1. 有用户的数据;
  2. 有个资源服务器(负责管理用户数据);
  3. 有个客户应用需要访问用户的数据;
  4. 给资源服务器按个门暴露用户数据称为API。
  5. 客户应用可以通过API访问用户数据。
  6. 资源服务器返回用户数据。 image.png

安全问题

但是如何有恶意客户应用通过API来调用,也来访问用户数据那怎么办呢?如果没有安全机制,资源服务器也会把用户数据返回给恶意客户应用。 image.png

授权服务器

针对上面说的安全问题,所以需要一个安全机制来保护用户数据。
那么业界实践是提前给客户应用颁发一个Access Token,它表示客户应用被授权可以访问用户数据。
客户应用去调用API时会携带 Access Token,资源服务器会校验这个 Access Token 是否合法。
这样就可以完成安全的数据访问。 image.png 上面这个机制工作的前提就是必须给客户应用颁发 Access Token,也就是需要一个颁发 Access Token 的角色。这样就引入一个新的角色叫 授权服务器
授权服务器干的事情就是生成 Access Token,并且把 Access Token 颁发给客户应用。

授权服务器和客户应用的关系

image.png

总结,一共有以下角色:

  • 一个授权服务器:负责生成 Access Token,并颁发给客户应用。
  • 一个客户应用:客户应用携带 Access Token 通过API去资源服务器获取用户数据。
  • 一个资源服务器:校验客户应用携带的 Access Token是否具有访问用户数据的权限,校验成功,返回数据给客户应用。 image.png 上面流程中第一步是授权服务器生成Access Token,但不是自作主张可以颁发给客户应用的,在真实流程中,在颁发Token前先要征询用户(资源拥有者)同意。如果资源拥有者同意才可以颁发给客户应用。所以还需要用户这个角色参与进来,当客户应用向授权服务器申请 Access Token时,授权服务器就会把页面跳转到用户端,询问用户是否同意将权限授予客户应用,如果用户确认可以,那么授权服务器就会把Token颁发给客户应用。 image.png OAuth 2.0标准化了Access Token的请求和响应部分,OAuth2.0的细接在RFC 6749(OAuth 2.0授权框架)中描述。

OAuth2的正式定义

什么是 OAuth 2.0?

  1. 用户REST、APIs的代理授权框架(delegated authorization framework)。
  2. 基于令牌Token的授权,在无需暴露用户密码的情况下,使应用能获取对用户数据的有限访问权限。
  3. 解耦认证和授权。
  4. 事实上的标准安全框架,支持多种用例场景:
    • 服务器端webApp
    • 浏览器单页SPA
    • 无线/原生App
    • 服务器对服务器之间

令牌可以类比仆从钥匙,给应用授予有限的访问权限,让应用能够代表用户去访问用户的数据。

OAuth2.0 优势

  • OAuth2.0OAuth1.0 易于实现
  • 更安全,客户端不接触用户密码,服务器端更容易集中保护
  • 广泛传播并被持续采用
  • 短寿命和封装的tokentoken可以配置生效时间,token上还可以封装一些用户信息
  • 资源服务器和授权服务器解耦
  • 集中式授权,简化客户端
  • HTTP / JSON友好,易于请求和传递token
  • 考虑多种客户端架构场景(服务器端webApp、浏览器单页SPA、无线/原生App、服务器对服务器之间)
  • 客户可以具有不同信任级别

OAuth2.0 不足

  • 协议框架太宽泛,造成各种实现的兼容性和互操作性差,各家具体实现都有些差异,甚至是不兼容
  • OAuth 1.0不兼容
  • OAuth2.0 不是一个认证协议,是一个授权框架,OAuth2.0 本身并不能告诉你任何用户信息,当然也可以通过扩展获取用户信息

OAuth2.0 主要涉及角色

  • 授权服务器(AS):在客户应用成功认证并获得授权之后,向客户应用颁发访问令牌 Access Token
  • 资源拥有者(RO):数据拥有者,想要分享某些资源给第三方应用,比如:七牛云上图片拥有者
  • 客户应用:第三方应用,通常是一个web或者无线应用,它需要访问用户的受保护资源,比如:云冲印服务
  • 资源服务器(RS):数据存储的地方,一般是一个web站点或者web service API,用户的受保护数据保存于此,比如:七牛云 image.png

OAuth 术语

  • 客户凭证(Client Credentials):客户的clientId和密码用于认证客户,客户应用想要访问资源服务器需要有凭证,一般在授权服务器上注册获得的
  • 令牌(Tokens):授权服务器在接收到客户请求后,颁发的访问令牌
  • 作用域(Scopes):客户请求访问令牌时,由资源拥有者额外指定的细分权限

总结

OAuth 的本质是如何获取Token,如何使用Token
OAuth 提供一个宽泛的协议框架,具体安全场景需要定制;
OAuth 是一种在系统之间的代理授权协议;
OAuth 使用代理协议的方式解决密码共享反模式问题。

OAuth 2.0 有哪些典型的模式

典型的 OAuth Flow 和选型

授权码模式(最复杂的也是最安全的,是日常开发中最常用的)

image.png 解释流程
(A)用户访问客户端,后者将前者导向认证服务器。 (B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

下面是上面这些步骤所需要的参数。
A步骤中,客户端申请认证的URI,包含以下参数:

  • response_type:表示授权类型,必选项,此处的值固定为"code"
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

案例

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

C步骤中,服务器回应客户端的URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

案例

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
          &state=xyz

D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
  • code:表示上一步获得的授权码,必选项。
  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
  • client_id:表示客户端ID,必选项。

案例

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

E步骤中,认证服务器发送的HTTP回复,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

案例

 HTTP/1.1 200 OK
 Content-Type: application/json;charset=UTF-8
 Cache-Control: no-store
 Pragma: no-cache
 {
   "access_token":"2YotnFZFEjr1zCsicMWpAA",
   "token_type":"example",
   "expires_in":3600,
   "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
   "example_parameter":"example_value"
 }

简化模式

image.png 简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
解释流程:
(A)客户端将用户导向认证服务器。
(B)用户决定是否给于客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。
(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
(F)浏览器执行上一步获得的脚本,提取出令牌。
(G)浏览器将令牌发给客户端。

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

  • response_type:表示授权类型,此处的值固定为"token",必选项。
  • client_id:表示客户端的ID,必选项。
  • redirect_uri:表示重定向的URI,可选项。
  • scope:表示权限范围,可选项。
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

案例

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

C步骤中,认证服务器回应客户端的URI,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

案例

HTTP/1.1 302 Found
Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
          &state=xyz&token_type=example&expires_in=3600

密码模式

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

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

  • grant_type:表示授权类型,此处的值固定为"password",必选项。
  • username:表示用户名,必选项。
  • password:表示用户的密码,必选项。
  • scope:表示权限范围,可选项。

案例

 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

C步骤中,认证服务器向客户端发送访问令牌

案例

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}

之前我们说 OAuth2 主要解决问题就是用户名密码不能泄露,尽量不要传递客户端,那为什么还是有一种密码模式允许客户端拿到用户名密码呢?
其实这也是有场景的,如果说这个应用程序也是你们公司自己造的,这种情况下,输入用户名密码也不会有什么问题。

客户端模式

这种更简单,都没有资源拥有者的角色,一般是机器对机器的场景,比如 docker client 要拉取镜像,它可以通过客户端模式去获取访问令牌,然后去拉取镜像。 image.png 解释流程:
(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

认证服务器必须以某种方式,验证客户端身份。
B步骤中,认证服务器向客户端发送访问令牌

案例

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"example_parameter":"example_value"
}

刷新令牌

如果用户访问的时候,客户端的"访问令牌"已经过期,则需要使用"更新令牌"申请一个新的访问令牌。
客户端发出更新令牌的HTTP请求,包含以下参数:

  • granttype:表示使用的授权模式,此处的值固定为"refreshtoken",必选项。
  • refresh_token:表示早前收到的更新令牌,必选项。
  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。

案例

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