解决方案

随着 Iass(基础设施)、Pass(平台)、Sass(软件)蓬勃发展,企业产品平台化演变,由平台提供的基础能力打造出中台级企业服务。而作为平台的基础设施,应用架构转而拥抱微服务,前端迅猛发展使得我们开发模式转为前后端分离,前端开发人员承担了更多的职能,后端负责业务逻辑提供数据接口,控制层的逻辑则由前端路由去管理。

那么在这种大环境之下,一般的小型微服务项目数量达到几十,那么我们这么多的服务就会遇到以下几个问题:

  1. 前端请求鉴权
  2. 微服务之间通信鉴权
  3. 用户在产品不同服务之间切换时的SSO

第一方案:OAuth2.0

1. OAuth2.0 的四种授权模式

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

其中密码模式常用于外部服务的鉴权,客户端模式常用于内部服务鉴权和开放平台应用的授权,授权码模式常用于社会化登录和 SSO,因此 OAuth2.0 可作为完整的统一身份认证和授权方案。

2. OAuth2.0 的几种重要角色

必须注意的是,这些角色是相对的概念。

  • 客户端 Client:一般指第三方应用程序,例如用 QQ 登录豆瓣网站,这里的豆瓣网就是 Client;但在微服务体系里,Client 通常是服务本身,如 APP 端的注册登录服务;
  • 资源所有者 Resource Owner:一般指用户,例如用 QQ 登录豆瓣网站,这里的所有者便是用户;但在微服务体系里,资源所有者是服务提供者本身;
  • 资源服务器 Resource Server:一般指资源所有者授权存放用户资源的服务器,例如用 QQ 登录豆瓣网站,这里的 QQ 就是资源服务器;但在微服务体系里,服务提供者本身便是资源服务器;
  • 授权服务器 Authorization Server:一般是指服务提供商的授权服务,例如用 QQ 登录豆瓣网站,这里的 QQ 便是授权服务器;类似地,在微服务体系里,鉴权服务便是授权服务器。

3. IBCS 提供哪些功能

1)核心功能,以 API 形式暴露:

接口 描述 body 返回 权限
POST /image-classify 图像识别 { 图片内容, token } { 识别结果 } 受控接口

2)由 UIMS 提供的功能:

功能/API 描述 body 返回 权限
POST /accounts/ 注册接口 { username, password } { sign-up-result } 非受控接口
POST /accounts/login 登录接口 { username, password } { token } 受控接口
POST /accounts/logout 登出接口 { token } { logout-result } 受控接口
SignUp-Page 统一注册页面(UI) 非受控页面
Login-Page 统一登录页面(UI) 非受控页面

其中,注册接口、SignUp-Page 和 Login-Page 页面是非受控接口/页面,意味着无须鉴权即可访问。

SignUp-Page 和 Login-Page 页面是由 UIMS 提供的统一的注册和登录页面,当外部服务发起注册或登录请求时,有两种作法:一是统一跳转到 UIMS 的注册或登录页面,用户完成操作后调用 UIMS 的注册和登录 API 完成请求;二是在自己的注册和登录页面完成操作,然后以用户名和密码作为参数调用 UIMS 的注册和登录 API 完成请求。推荐采用第一种方法,类似于全网通行证,用户体验统一,不用重复开发注册登录页面。

3)成为开发者,获取 IBCS 的能力集:

  1. 第一步:申请成为开发者。开发者分为个人开发者和组织开发者两类,实名认证也分两种类型进行。成为开发者的前提是成为平台的注册用户;
  2. 第二步:创建应用,平台审核通过后,生成 AppId 和 AppSecret 给到开发者,每个应用对应一对 AppId 和 AppSecret。值得注意的是,每个 AppID 与用户账号是绑定的,因此每个 AppId 获取资源和能力的权限受到该用户账户权限的限制,典型的例子是对象存储服务(OSS/OBS);
  3. 第三步:获取 Access Token,开发者按照 OAuth2.0 的规范,采用客户端(client_credentials)模式,利用申请到的 AppId 和 AppSecret 向 IBCS 申请 token;
  4. 第三步:使用 Access Token 与平台进行交互,每次调用受控 API 时都携带此 token。

4)成为开发者,创建第三方应用:

一般来说,应当独立建设一个开放平台,开发平台作为整个云平台的一个子系统,同样依赖于 UIMS。在开放平台上,应当提供一套完善的界面和流程,以引导用户完成开发者认证和第三方应用接入的所有工作。此外,在前期阶段,也可以采用线下申请的方式,由管理员人工审核,在后台手动录入。

在开放平台上,创建第三方应用的流程和步骤,与上一步骤『成为开发者,获取 IBCS 的能力集』一致。所不同的是,上个步骤是获取 IBCS 的能力,而本步骤『创建第三方应用』,是基于开放平台开发应用,类似于微信小程序。

5)成为开发者,将异构系统(第三方应用)接入 IBCS:

应该允许开发者将异构系统(第三方应用)接入 IBCS,以增强 IBCS 的能力。例如,假设 IBCS 本身不具备识别特种汽车的能力,但允许接入其他开发者开发的基于图像的特种汽车识别应用。

第三方应用接入,归属于开放平台的范畴。接入的流程和步骤,与第三步骤『成为开发者,获取 IBCS 的能力集』一致,由开放平台提供标准的 API,第三方按照接入规范执行。

4. OAuth2.0 四种授权模式的应用场景

场景 描述 适用模式
用户注册(外部服务) 用户在 APP 提供的注册页面,完成注册请求 非受控接口,无须鉴权
用户登录(外部服务),返回 token 用户在 APP 提供的登录页面,完成登录请求,获得 token 密码模式(resource owner password credentials)
用户注册(UIMS) 用户跳转到 UIMS 的注册页面,完成注册请求,注册成功后,跳回到原服务 非受控接口,无须鉴权
用户登录(UIMS),返回 token 用户跳转到 UIMS 的登录页面,完成登录操作,获得授权码,然后携带授权码跳转到重定向URI,再获得 token 授权码模式(authorization code)
外部服务的鉴权 用户在 APP 上使用图像识别服务,APP 调用 IBCS 的图像识别 API 并返回结果给用户 密码模式(resource owner password credentials)
内部服务的鉴权 图像识别服务向配置服务获取配置信息 客户端模式(client credentials),或简单的 HTTP Basic 验证
开发者:获取 IBCS 能力集 客户端模式(client credentials)
开发者:创建第三方应用 客户端模式(client credentials)
开发者:接入第三方应用 客户端模式(client credentials)

5. 客户端鉴权和用户鉴权

服务鉴权,从形式上分为:

  1. 非受控服务/接口,无须鉴权;
  2. 客户端鉴权(服务自身鉴权):客户端(服务)在访问另一个服务时,必须先表明客户端自己的身份;
  3. 业务鉴权(用户鉴权):用户通过客户端(服务)访问某个资源时,必须验证用户自己的身份。

例如,用户通过 APP 登录 IBCS 后,访问图像识别服务的过程:用户登录后获得 token,由 APP 携带此 token 向图像识别服务发起请求,图像识别服务首先调用鉴权服务验证 APP 的身份(通过 ClientId 和 ClientSecret),然后再验证用户的身份(通过 token),如果 APP 和用户都获得授权,则请求通过,返回识别结果。

6. 跨域问题

浏览器的同源策略给 Web 应用划定了安全边界,是 Web 应用安全模型的重要基础。基于令牌的安全系统,在同源策略的约束下面临两个问题:

  1. 跨域请求;
  2. SSO 登录状态的跨域保持。

1)CORS 方案

第一个问题,一般采用 CORS 方案,在服务端的响应头声明 Access-Control-Allow-Origin 参数即可解决跨域请求的问题。

2)同域 SSO 方案

第二个问题,在同域环境下,传统方法是采用 Cookie 的方案。跨域环境下,也有几种方案,从安全性和简便性考虑,推荐采用这种方案:

  • 根据业务需求将应用切分为同域 SSO 应用和跨域 SSO 应用两类;
  • 将需要 SSO 状态保持的应用归到同域 SSO 应用中,将其他应用归到跨域 SSO 应用中;
  • 对于同域 SSO 应用,一般是企业内部应用,或相关性较高的应用,这些应用的域名采用相同的父级域名,继续使用 Cookie 方案;
  • 对于跨域 SSO 应用,不提供 SSO 状态保持。当用户首次打开此类应用时,由于 Cookie 无法跨域,因此服务端无法感知用户的登录状态,此时用户是处于未登录状态的;当用户访问受控页面或点击登录页面时,须重新执行登录操作。

7. 登出和关闭账户

OAuth2.0 是集中式的令牌安全系统,可以通过撤销令牌登出系统。关闭账户与此类似。

8. 软件授权

可在鉴权服务或 API 网关增加规则过滤器,将商业授权策略应用到授权规则中。

第二方案:JWT + API 网关

JWT 是一种自包含的客户端令牌系统技术规范,这是其与 OAuth2.0 最大的不同。除此之外,可以简单地将 JWT 当作 OAuth2.0 密码模式的半自动版本。在实施 JWT 方案时,大部分情况下与 OAuth2.0 基本一致,本文不重复阐述,只对几个要点和不同之处加以说明。

1. 搭配 API 网关实现令牌撤销

由于 JWT 属于自包含的客户端令牌系统,令牌发出后无须服务器验证,只需在客户端验证。客户端验证并解签后将得到必要的信息,例如用户基本信息和权限标识符。这种设计天然地存在无法撤销令牌的问题。解决方案是在 API 网关对 JWT 进行拦截,这里有多种方法:

  1. 令牌撤销由 UIMS 发出,经由消息队列、API 等手段通知到网关,网关维护一个已撤销令牌的黑名单,对所有经过网关的 JWT 进行比对,TRUE 则拒绝转发,并返回 401;
  2. 令牌撤销由 UIMS 执行后,网关每次收到 JWT 请求时,查询令牌数据库(如 Redis),比对该令牌是否已经撤销,如已撤销则返回 401。

不过此方案仍然存在两个问题:

  1. 将一定程度丧失 JWT 客户端解签的优势,但相较于传统的 Cookie + Session 方案,此方案更加轻巧,也更加符合微服务无状态 API 的风格;
  2. 对于已发出的令牌,客户端在下一次请求之前,服务端的令牌系统对此没有控制能力,例如 SSOff 将无法很好地实现。

2. 客户端鉴权和用户鉴权

与 OAuth2.0 方案一致,客户端同样需要使用 ClientId 和 ClientSecret 鉴权。

3. 公钥和密钥

JWT 规定采用非对称加密算法对 Header 和 Payload 进行签名。

1)非对称算法

非对称算法的重要特点是,使用密钥加密时,必须用公钥解密;用公钥加密时,必须用密钥解密。利用此特性,通常在服务端采用密钥加密信息,然后客户端采用公钥解密信息。由于密钥存储在服务端,因此安全性高;公钥本身可以公开,因此可以在客户端存储。

2)公钥解密

JWT 经由服务端用密钥加密后,发往客户端,客户端使用公钥进行解密,便得到了 JWT 的明文。JWT 包含了丰富的信息(通常是用户基本信息和权限标识符),只要解密成功,客户端完全可以信任此 JWT,因此不必再依赖于服务端重复鉴权。

4. 服务间鉴权

1)内部服务鉴权

以 IBCS 为例,当图像识别服务服务携带 JWT 向配置服务请求资源时,配置服务使用公钥解密,只要解密成功,配置服务完全可以信任图像识别服务,因此也不必再依赖于鉴权服务的重复鉴权。对于安全性要求不高的场景,也可以使用 HTTP Basic 验证进行简单鉴权。

2)外部服务鉴权

同样,当外部的 APP 携带 JWT 向内网的图像识别服务发起请求时,图像识别服务使用公钥解密,只要解密成功,图像识别服务完全可以信任该 APP,有所不同的是,外部服务向内网服务发起请求,必须经过 API 网关,由网关执行规则过滤,确保 JWT 是仍处于有效状态。

5. 跨域问题

与 OAuth2.0 的跨域解决方案一致。

五、总结

本文给出了微服务架构下的统一身份认证和授权的设计方案,从平台级 SAAS 模式下『统一身份治理』的概念出发,梳理了关键的需求点,提出了对应的解决方案。其中 OAuth2.0 是最佳解决方案,不过在实际运用中,应当遵循『合适原则』、『简单原则』和『演化原则』三个原则,不能盲目照搬。

六、参考链接

  1. SAAS,http://www.ruanyifeng.com/blog/2017/07/iaas-paas-saas.html
  2. SSO, https://www.cnblogs.com/EzrealLiu/p/5559255.html
  3. CAS, https://apereo.github.io/cas/
  4. UIMS, https://mtide.net/平台级SAAS架构的基础-统一身份管理系统.html