解决方案
随着 Iass(基础设施)、Pass(平台)、Sass(软件)蓬勃发展,企业产品平台化演变,由平台提供的基础能力打造出中台级企业服务。而作为平台的基础设施,应用架构转而拥抱微服务,前端迅猛发展使得我们开发模式转为前后端分离,前端开发人员承担了更多的职能,后端负责业务逻辑提供数据接口,控制层的逻辑则由前端路由去管理。
那么在这种大环境之下,一般的小型微服务项目数量达到几十,那么我们这么多的服务就会遇到以下几个问题:
- 前端请求鉴权
- 微服务之间通信鉴权
- 用户在产品不同服务之间切换时的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 的能力集:
- 第一步:申请成为开发者。开发者分为个人开发者和组织开发者两类,实名认证也分两种类型进行。成为开发者的前提是成为平台的注册用户;
- 第二步:创建应用,平台审核通过后,生成 AppId 和 AppSecret 给到开发者,每个应用对应一对 AppId 和 AppSecret。值得注意的是,每个 AppID 与用户账号是绑定的,因此每个 AppId 获取资源和能力的权限受到该用户账户权限的限制,典型的例子是对象存储服务(OSS/OBS);
- 第三步:获取 Access Token,开发者按照 OAuth2.0 的规范,采用客户端(client_credentials)模式,利用申请到的 AppId 和 AppSecret 向 IBCS 申请 token;
- 第三步:使用 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. 客户端鉴权和用户鉴权
服务鉴权,从形式上分为:
- 非受控服务/接口,无须鉴权;
- 客户端鉴权(服务自身鉴权):客户端(服务)在访问另一个服务时,必须先表明客户端自己的身份;
- 业务鉴权(用户鉴权):用户通过客户端(服务)访问某个资源时,必须验证用户自己的身份。
例如,用户通过 APP 登录 IBCS 后,访问图像识别服务的过程:用户登录后获得 token,由 APP 携带此 token 向图像识别服务发起请求,图像识别服务首先调用鉴权服务验证 APP 的身份(通过 ClientId 和 ClientSecret),然后再验证用户的身份(通过 token),如果 APP 和用户都获得授权,则请求通过,返回识别结果。
6. 跨域问题
浏览器的同源策略给 Web 应用划定了安全边界,是 Web 应用安全模型的重要基础。基于令牌的安全系统,在同源策略的约束下面临两个问题:
- 跨域请求;
- 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 进行拦截,这里有多种方法:
- 令牌撤销由 UIMS 发出,经由消息队列、API 等手段通知到网关,网关维护一个已撤销令牌的黑名单,对所有经过网关的 JWT 进行比对,TRUE 则拒绝转发,并返回 401;
- 令牌撤销由 UIMS 执行后,网关每次收到 JWT 请求时,查询令牌数据库(如 Redis),比对该令牌是否已经撤销,如已撤销则返回 401。
不过此方案仍然存在两个问题:
- 将一定程度丧失 JWT 客户端解签的优势,但相较于传统的 Cookie + Session 方案,此方案更加轻巧,也更加符合微服务无状态 API 的风格;
- 对于已发出的令牌,客户端在下一次请求之前,服务端的令牌系统对此没有控制能力,例如 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 是最佳解决方案,不过在实际运用中,应当遵循『合适原则』、『简单原则』和『演化原则』三个原则,不能盲目照搬。
六、参考链接
- SAAS,http://www.ruanyifeng.com/blog/2017/07/iaas-paas-saas.html
- SSO, https://www.cnblogs.com/EzrealLiu/p/5559255.html
- CAS, https://apereo.github.io/cas/
- UIMS, https://mtide.net/平台级SAAS架构的基础-统一身份管理系统.html