Micro-services Authentication and Authorization

在微服务架构下,要考虑外部应用接入的场景、
用户 – 服务的鉴权、
服务 – 服务的鉴权
等多种鉴权场景。

统一身份管理(UIM)是整个平台帐号和权限管控的基础,由此构建的系统称为UIMS(Unified Identity Management System),平台下所有系统的账户管理、身份认证、用户授权、权限控制等行为都经由 UIMS 处理,提供帐号密码管理、基本资料管理、角色权限管理等功能。UIMS 基于『统一身份治理』的概念,可划分为两级账户体系、基础权限模块和基础信息模块三大模块。其中两级账户体系将账户分为组织实体帐号和个人实体账户两大类,个人实体从属于组织实体,也可以不从属任何组织实体,且个人实体可同时从属于多个组织实体;基础权限模块将各业务系统的资源权限进行统一管理和授权;基础信息模块用于描述组织实体和个人实体的基本信息,如组织实体名称、地址、法人,个人实体姓名、电话号码、性别等基础信息。UIMS 提供统一的 API 与各子系统连接。
问题:
目前,接入各个业务线的用户系统数量多类型复杂,且数据分散,比如有公司的员工系统(LDAP系统),公司的销售人员系统,公司的外包人员系统,外部互联网用户系统(使用APP的客户)。
不同类型的用户系统都有可能接入某些微服务,那么如何用权限系统去控制不通用户对同一个微服务的调用,对我们来说,又是一个挑战。
解决方案:
1. 权限系统微服务化。所有的业务微服务都将接入到权限微服务中,做统一控制。
2. 高可用,分布式的权限缓存:基本权限/角色数据我们放在MySQL的数据库中,权限验证结果数据(或者说鉴权数据),则通过Redis集群进行缓存;那么意味着,对于足够多的权限验证数据缓存Redis集群后,权限微服务全部崩溃也没关系;反之,当Redis集群崩溃,只要权限微服务运行正常,也不影响权限验证,只是性能会稍差而已。
3. 支持多类型权限,多调用方式:对于业务服务的,主要是支持接口加注解进行权限拦截验证,即API权限;对于其他系统,一般主要是体现在界面元素的权限校验(例如Web页面上按钮的Enabled/Disabled,通过权限系统来控制),即界面权限。
对于API权限,我们实现基于注解(Annotation)的扫描入库和拦截,不需要业务服务自行在权限Web界面上录入。
///此处待搜索和完善。。。。
比如用户A访问User Service,A如果未登录,则首先需要登录,请求获取授权token。获取token之后,A将携带着token去请求访问某个文件,这样就需要对A的身份进行校验,并且A可以访问该文件。
为了适应架构的变化、需求的变化,auth权限模块被单独出来作为一个基础的微服务系统,为其他业务service提供服务。
微服务也带来了很多的权限相关的问题。
比如完成一个业务操作,需要跨很多个微服务的调用,那么如何用权限系统去控制用户对不同微服务的调用,对我们来说是个挑战。当业务微服务的调用接入权限系统后,不能拖累它们的吞吐量,当权限系统出现问题后,不能阻塞它们的业务调用进度,当然更不能改变业务逻辑。新的业务微服务快速接入权限系统相对容易把控,那么对于公司已有的微服务,如何能不改动它们的架构方式的前提下,快速接入,对我们来说,也是一大挑战。
Authentication:
SSO:
这种方案意味着每个面向用户的服务都必须与认证服务交互,这会产生大量非常琐碎的网络流量和重复的工作,当动辄数十个微应用时,这种方案的弊端会更加明显。
新建一个系统就需要进行一次SSO对接。。。。。。
企业平台涉及众多子系统,为简化各子系统的用户管理,提升用户体验,因此实现 SSO 是统一身份认证的重要目标:一次登录,全部访问。对于企业内部应用来说,SSO 是必须的选项,例如企业 OA、HR、CRM 等内部系统;对于外部应用来说,SSO 是可选项,具体哪个应用应当加入 SSO 系统,由该业务系统决定,例如外部商城、物业系统等对外服务系统。无论何种应用是否采用 SSO,UIMS 在技术上应当具备 SSO 的能力。
CAS 是时下最成熟的开源单点登录方案,包含 CAS Server 和 CAS Client 两部分。CAS Server 是一个 war 包需要独立部署,负责用户认证;CAS Client 负责处理对客户端受保护资源的访问请求,需要认证时,重定向到 CAS Server。值得注意的是,CAS 是一个认证框架,其本身定义了一套灵活完整的认证流程,但其兼容主流的认证和授权协议如 OAuth2、SAML、OpenID 等,因此一般采用 CAS + OAuth2 的方案实现 SSO 和授权登录。
共享存储的Session:
分布式会话方案原理主要是将关于用户认证的信息存储在共享存储中,且通常由用户会话作为 key 来实现的简单分布式哈希映射。当用户访问微服务时,用户数据可以从共享存储中获取。在某些场景下,这种方案很不错,用户登录状态是不透明的。同时也是一个高可用且可扩展的解决方案。这种方案的缺点在于共享存储需要一定保护机制,因此需要通过安全链接来访问,这时解决方案的实现就通常具有相当高的复杂性了。
在微服务架构下,每个微服务拆分的粒度会很细,并且不只有用户和微服务打交道,更多还有微服务间的调用。这个时候就要求必须要将 Session 从应用服务器中剥离出来,存放在外部进行集中管理。可以是数据库,也可以是分布式缓存,如 Memchached、Redis 等。这正是 David Borsos 建议的第二种方案,分布式 Session 方案。
客户端 Token 方案:

令牌在客户端生成,由身份验证服务进行签名,并且必须包含足够的信息,以便可以在所有微服务中建立用户身份。令牌会附加到每个请求上,为微服务提供用户身份验证,这种解决方案的安全性相对较好,但身份验证注销是一个大问题,缓解这种情况的方法可以使用短期令牌和频繁检查认证服务等。对于客户端令牌的编码方案,Borsos 更喜欢使用 JSON Web Tokens(JWT),它足够简单且库支持程度也比较好。
Token 和 Session ID 不同,并非只是一个 key。Token 一般会包含用户的相关信息,通过验证 Token 就可以完成身份校验。像 Twitter、微信、QQ、GitHub 等公有服务的 API 都是基于这种方式进行认证的,一些开发框架如 OpenStack、Kubernetes 内部 API 调用也是基于 Token 的认证。基于 Token 认证的一个典型流程如下:
1. 用户输入登录信息(或者调用 Token 接口,传入用户信息),发送到身份认证服务进行认证(身份认证服务可以和服务端在一起,也可以分离,看微服务拆分情况了)。
2. 身份验证服务验证登录信息是否正确,返回接口(一般接口中会包含用户基础信息、权限范围、有效时间等信息),客户端存储接口,可以存储在 Session 或者数据库中。
3. 用户将 Token 放在 HTTP 请求头中,发起相关 API 调用。
4. 被调用的微服务,验证 Token 权限。
5. 服务端返回相关资源和数据。
客户端 Token 与 API 网关结合:
这个方案意味着所有请求都通过网关,从而有效地隐藏了微服务。在请求时,网关将原始用户令牌转换为内部会话 ID 令牌。在这种情况下,注销就不是问题,因为网关可以在注销时撤销用户的令牌。
JWT:
- JWT(JSON Web Token)是一种简洁的自包含的 JSON 声明规范,因其分散存储的特点而归属于客户端授权模式,广泛用于短期授权和单点登录。由于 JWT 信息是经过签名的,可以确保发送方的真实性,确保信息未经篡改和伪造。但由于其自包含的客户端验签特性,令牌一经签发,即无法撤销,因此单纯采用 JWT 作为统一身份认证和授权方案无法满足帐号统一登出和销毁、帐号封禁和解除这几种类型的需求。(RFC 7519)

JWT 结构
JWT 是由三段信息构成的,第一段为头部(Header),第二段为载荷(Payload),第三段为签名(Signature)。每一段内容都是一个 JSON 对象,将每一段 JSON 对象采用 BASE64 编码,将编码后的内容用。链接一起就构成了 JWT 字符串。如下:
header.payload.signature
1. 头部(Header)
头部用于描述关于该 JWT 的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个 JSON 对象。
{
"type" : "JWT",
"ALG" : "HS256"
}
在头部指明了签名算法是 HS256 算法。
2. 载荷(payload)
载荷就是存放有效信息的地方。有效信息包含三个部分:
标准中注册的声明
公共的声明
私有的声明
标准中注册的声明(建议但不强制使用):
iss:JWT 签发者
sub:JWT 所面向的用户
aud:接收 JWT 的一方
exp:JWT 的过期时间,这个过期时间必须要大于签发时间
nbf:定义在什么时间之前,该 JWT 都是不可用的
iat:JWT 的签发时间
jti:JWT 的唯一身份标识,主要用来作为一次性 token, 从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密。
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。
案例
微服务架构下的鉴权,怎么做更优雅
3. 签名(signature)
创建签名需要使用 Base64 编码后的 header 和 payload 以及一个秘钥。将 base64 加密后的 header 和 base64 加密后的 payload 使用。连接组成的字符串,通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了 jwt 的第三部分。
比如:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 的优点:
跨语言,JSON 的格式保证了跨语言的支撑
基于 Token,无状态
占用字节小,便于传输
关于 Token 注销:
Token 的注销,由于 Token 不存储在服务端,由客户端存储,当用户注销时,Token 的有效时间还没有到,还是有效的。所以如何在用户注销登录时让 Token 注销是一个要关注的点。一般有如下几种方式:
Token 存储在 Cookie 中,这样客户端注销时,自然可以清空掉
注销时,将 Token 存放到分布式缓存中,每次校验 Token 时去检查下该 Token 是否已注销。不过这样也就失去了快速校验 Token 的优点。
多采用短期令牌,比如令牌有效期是 20 分钟,这样可以一定程度上降低注销后 Token 可用性的风险。
Oauth2.0:
OAuth 2.0 是 OAuth 协议的下一版本,但不向后兼容 OAuth 1.0,即完全废止了 OAuth 1.0。
2012 年 10 月,OAuth 2.0 协议正式发布为 RFC 6749。

(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。


四大角色
由授权流程图中可以看到 OAuth2.0 有四个角色:客户端、资源拥有者、资源服务器、授权服务器。
客户端:客户端是代表资源所有者对资源服务器发出访问受保护资源请求的应用程序。
资源拥有者:资源拥有者是对资源具有授权能力的人。
资源服务器:资源所在的服务器。
授权服务器:为客户端应用程序提供不同的 Token,可以和资源服务器在统一服务器上,也可以独立出去。
客户端必须得到用户的授权(Authorization Grant),才能获得令牌(access token)。OAuth 2.0 定义了四种授权方式:
authorization code、
implicit、
resource owner password credentials、
client credentials。
对于上述OAuth2四种模式,常使用的是password模式和client模式。
其中密码模式常用于自己开发的app、spa等外部服务程序的鉴权,客户端模式常用于内部服务鉴权和开放平台应用的授权,授权码模式常用于社会化登录和 SSO,因此 OAuth2.0 可作为完整的统一身份认证和授权方案。

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与 "服务提供商" 的认证服务器进行互动。流程如下:
用户访问客户端,后者将前者导向认证服务器。
用户选择是否给予客户端授权。
假设用户给予授权,认证服务器将用户导向客户端事先指定的 "重定向 URI"(redirection URI),同时附上一个授权码。
客户端收到授权码,附上早先的 "重定向 URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
认证服务器核对了授权码和重定向 URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
简化模式(Implicit Grant Type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了 "授权码" 这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。流程如下:
客户端将用户导向认证服务器。
用户决定是否给于客户端授权。
假设用户给予授权,认证服务器将用户导向客户端指定的 "重定向 URI",并在 URI 的 Hash 部分包含了访问令牌。
浏览器向资源服务器发出请求,其中不包括上一步收到的 Hash 值。
资源服务器返回一个网页,其中包含的代码可以获取 Hash 值中的令牌。
浏览器执行上一步获得的脚本,提取出令牌。
浏览器将令牌发给客户端。
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 "服务商提供商" 索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。流程如下:
用户向客户端提供用户名和密码。
客户端将用户名和密码发给认证服务器,向后者请求令牌。
认证服务器确认无误后,向客户端提供访问令牌。

注意:这个流程适用于资源服务器、授权服务器相分离的情况,否则,流程中的第5,6步不是必须的,甚至第4,7步都是显而易见的事情而不必说明。现在大部分有关OAuth2.0的介绍文章都没有4,5,6,7步骤的说明,可能为了表述方便,默认都是将授权服务器跟资源服务器合在一起部署的。
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向 "服务提供商" 进行认证。严格地说,客户端模式并不属于 OAuth 框架所要解决的问题。
在这种模式中,用户直接向客户端注册,客户端以自己的名义要求 "服务提供商" 提供服务,其实不存在授权问题。流程如下:
客户端向认证服务器进行身份认证,并要求一个访问令牌。
认证服务器确认无误后,向客户端提供访问令牌。
正如 David Borsos 所建议的一种方案,在微服务架构下,我们更倾向于将 Oauth 和 JWT 结合使用,Oauth 一般用于第三方接入的场景,管理对外的权限,所以比较适合和 API 网关结合,针对于外部的访问进行鉴权(当然,底层 Token 标准采用 JWT 也是可以的)。
JWT 更加轻巧,在微服务之间进行访问鉴权已然足够,并且可以避免在流转过程中和身份认证服务打交道。当然,从能力实现角度来说,类似于分布式 Session 在很多场景下也是完全能满足需求,具体怎么去选择鉴权方案,还是要结合实际的需求来。
Authorization:
操作权限控制: 当鉴定完用户身份合法之后,对于该用户的某个具体请求是否具有该操作执行权限进行校验。
Java下的方案:
Spring Security和Shiro
Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。Shiro很容易入手,上手快控制粒度可糙可细。自由度高,Shiro既能配合Spring使用也可以单独使用。
Spring Security:
Spring社区生态很强大。除了不能脱离Spring,Spring Security具有Shiro所有的功能。而且Spring Security对Oauth、OpenID也有支持,Shiro则需要自己手动实现。Spring Security的权限细粒度更高。但是Spring Security太过复杂。
看了下网上的评论,貌似一边倒向Shiro。大部分人提出的Spring Security问题就是比较复杂难懂,文档太长。笔者综合评估了下复杂性与所要实现的权限需求,以及上一个需求调研的结果,最终选择了Spring Security。
Authorization的JWT的方案:
1. JWT的认证思路是:首先通信双方(比如IdP和API)必须提前约定一个secret,这个secret用来验证这个token的真伪(注意这里的secret不是来解密JWT,而是仅仅验证,因为这个JWT数据不是加密的,在上图中的左边,它看起来像一段加密后的字符,但只是通过base64编码的)。因为JWT的本质是一段JSON数据而已,任何人都可以随意填写其中的数据,比如username之类的,但是由于只有通信的双方知道secret,那么也只有通信的双方可以正确的验证发送过来的JWT是不是真的,亦或是伪造的。
具体的在REST API中的应用流程大致就是:
首先,有一个专门的Identity Provider Service来给每一个客户端发放JWT,REST API Service和Identity Provider Service事先要约定一个secret用来验证JWT。
客户端每次请求,首先先向Identity Provider Service发出请求,比如给其用户名密码,或者用微博账户来登录,如果Identity Provider Service确认客户端的信息是正确的,那么返回其一个JWT。
客户端使用该JWT发送请求(一般放在http header中)给REST API服务,REST API使用事先约定好的secret来验证此JWT,如果验证通过,说明此JWT是由自己的Identity Provider Service发放给客户端的,而非客户端自己随意制造的一个JWT。否则返回403.
REST API服务会使用secret来解码JWT,获取有用的信息,比如用户名,权限等等,来进一步的授权Authorization。
上面提到的Identity Provider Service,自己可以开发,或者使用第三方的服务(比如Auth0)。多说一句,使用JWT做认证时,很多人会直接把权限的信息(比如ROLE_ADMIN)也写入到JWT的payload中,这样做没错,但是不推荐。因为ROLE_ADMIN之类的信息也算敏感信息,还有就是会暴露里后端系统的user role的管理方式。最安全的方法:在JWT中放入尽可能少的权限信息,一个用户id足矣。API后端要自己存储用户id所对应的权限信息。

Authorization的API key方案:
3. API Keys
API Keys的认证思路也很简单,事先REST API服务会给客户发送一个随机的唯一的字符串作为API Key,并记录在自己的数据库里。并且一般情况下,这个全局唯一的API Key,代表了这个客户的权限信息。
具体的在REST API中的应用流程大致就是:
客户要使用我的API,我跟客户商量好价格,权限后,发送一个API Key给客户,并在自己的数据库中存入这个API Key,并附带其权限信息
客户端使用API Key(一般放在http header中)向API发送请求,API查看这个API Key是否已经存在,如果不存在,返回403
如果API Key是有效的,那么根据其对应的权限信息,做进一步的authorization。
Authorization的api网关集中式处理式权限控制方案:
用户获取token:
携带账号密码访问认证中心,认证中心校验合法后通过jwt生成token(消息体包含用户ID,过期时间)
并且以用户ID为key,token为value缓存到Redis中
用户退出、修改密码或后台强制用户退出:
通过用户ID删除Redis缓存中对应的token
用户访问资源:
用户携带token访问统一API网关,网关通过过滤器解析token获取用户ID,判断过期时间,
通过用户ID查询Redis缓存是否存在,再生成新的token(消息体包含用户ID,过期时间,用于服务与服务之间调用使用,过期时间可以设置1分钟左右),将新的token添加到请求头中带去资源服务器
资源访问资源:
需要携带网关生成的token去访问
说明:
认证中心生成的token与网关生成的token使用的签名不能相同,这样可以确保用户无法直接访问资源服务器,只能通过网关去访问。
怎么处理用户角色权限(RBAC的)?
在认证中心生成的token消息体中包含用户拥有的角色ID集合,访问网关时从缓存中获取角色与权限的关系,然后校验是否有权限访问。
后台编辑角色与权限的关系只需要更新下缓存
后台编辑用户与角色的关系时要根据用户ID移除Redis中对应的token,用户需要刷新token或重新登录。
后台删除角色时通过角色与用户的关系数据获取拥有对应角色的用户集合,再根据用户集合移除token
Authorization的api权限控制方案(各业务服务子系统独立处理方式):
5.1 存在的不足
API级别操作权限校验的通用性
(1). 对于API级别操作权限校验,需要在网关处调用时构造相应的上下文信息。上下文信息基本依赖于 token中的payload,如果信息太多引起token太长,导致每次客户端的请求头部长度变长。
(2). 并不是所有的操作接口都能覆盖到,这个问题是比较严重的,根据上下文集合很可能出现好多接口 的权限没法鉴定,最后的结果就是API级别操作权限校验失败的是绝对没有权限访问该接口,而通过不一定能访问,因为该接口涉及到的上下文根本没法完全得到。我们的项目在现阶段,定义的最小上下文集合能勉强覆盖到,但是对于后面扩增的服务接口真的是不乐观。
(3). 每个服务的每个接口都在Auth服务注册其所需要的权限,太过麻烦,Auth服务需要额外维护这样的信息。
网关处调用Auth服务带来的系统吞吐量瓶颈
(1). 这个其实很容易理解,Auth服务作为公共的基础服务,大多数服务接口都会需要鉴权,Auth服务需要经过复杂。
(2). 网关调用Auth服务,阻塞调用,只有等Auth服务返回校验结果,才会做进一步处理。虽说Auth服务可以多实例部署,但是并发量大了之后,其瓶颈明显可见,严重可能会造成整个系统的不可用。
5.2 后续工作
从整个系统设计角度来讲,API级别操作权限后期将会分散在各个服务的接口上,由各个接口负责其所需要的权限、身份等。Spring Security对于接口级别的权限校验也是支持的,之所以采用这样的做法,也是为了兼容新服务和遗留的服务,主要是针对遗留服务,新的服务采用的是分散在各个接口之上。
将API级别操作权限分散到各个服务接口之后,相应的能提升Auth服务的响应。网关能够及时的对请求进行转发或者拒绝。
API级别操作权限所需要的上下文信息对各个接口真的设计的很复杂,这边我们确实花了时间,同时管理移动服务的好几百操作接口所对应的权限,非常烦。
Authentication的实际案例:

为便于理解统一认证和授权方案的细节,假定一种场景:团队准备构建一套基于图像的物品识别系统,允许用户通过 H5 应用或 APP 上传图像,系统分析后返回识别结果;同时期望将此系统开放给社区和行业用户以便商用;最后允许第三方应用将自身的功能接入 IBCS 以增强后者的能力。
在微服务架构下,IBCS 分为内外两层,内层处于物理内网环境下,也称为内部服务,外层处于物理外网环境下,也称为外部服务,内外通过 API 网关连接。
内部服务:鉴权服务、配置服务、图像识别服务属于内部服务,通常内部服务之间是相互信任的,但在安全要求较高的场景下,内部服务也不能互信。
外部服务:桌面 APP、无线 APP、H5、第三方应用属于外部服务,外部服务分为两种类型:一种是 IBCS 的一部分,如 APP、H5 这些前端应用;另一种是 IBCS 之外的第三方应用。两种类型的授权方式是不一样的,前者一般采用 OAuth2.0 的密码模式,后者则采用客户端模式。
下文将以物品识别系统为例子,介绍这两种推荐方案:
二)最佳方案: 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网关解决方案:
假设其他所有的资源服务器
都将持有一个RSA公钥
,当资源服务器接收到这个在Http Header
中存有Token
的请求,资源服务器就可以拿到这个Token
,并验证它是否使用正确的私钥签名(是否经过授权服务器签名,也就是验签)。验签通过,反序列化后就拿到Toekn
中包含的有效验证信息。

三)第二方案: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 的跨域解决方案一致。
问题:微服务的调用安全主要关心调用者是谁, 调用者能干什么, 以及如何传播这个信息,也就是常说的服务间的认证和授权。