You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
21 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 12 | 架构案例基于OAuth 2.0/JWT的微服务参考架构
> 你好,我是王新栋。
> 在前面几讲我们一起学习了OAuth 2.0 在开放环境中的使用过程。那么OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权/鉴权的地方,包括微服务。
> 因此今天我特别邀请了我的朋友杨波来和你分享一个基于OAuth 2.0/JWT的微服务参考架构。杨波曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监在微服务和OAuth 2.0有非常丰富的实践经验。
> 其中在携程工作期间他负责过携程的API网关产品的研发工作包括它和携程的令牌服务的集成在拍拍贷工作期间他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和OAuth 2.0类似,但要更简单些。
> 接下来,我们就开始学习杨波老师给我们带来的内容吧。
你好,我是杨波。
从单体到微服务架构的演进,是当前企业数字化转型的一大趋势。[OAuth 2.0](https://oauth.net/2/)是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而[JWT](https://jwt.io/)是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。
据我目前了解到的情况虽然有不少企业已经部分或全部转型到微服务架构但是在授权认证机制方面它们一般都是定制自研的比方说携程和拍拍贷的令牌服务。之所以定制自研主要原因在于标准的OAuth 2.0协议相对比较复杂,门槛也比较高。定制自研固然可以暂时解决企业的问题,但是不具备通用性,也可能有很多潜在的安全风险。
那么到底应该如何将行业标准的OAuth 2.0/JWT和微服务集成起来呢又有没有可落地的参考架构呢
针对这个问题,今天我就和你分享一种可落地的参考架构。不过,我要提前说明的是,这个架构的思想源于[MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT PART 1 OVERVIEW](https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-1-overview/)这篇文章。根据原作者Thijs的描述他提出的架构已经在企业落地架构了。如果你还想获得关于原架构的更多细节建议进一步参考“[What is PKCE](https://dzone.com/articles/what-is-pkce)”这篇文章。
我认为Thijs给出的架构确实具有可落地性和参考价值但是他的架构里面对某些微服务层次的命名例如BFF和Facade层和目前主流的微服务架构不符还有他的架构应该是手绘不够清晰也不容易理解。为此我专门用今天这一讲来改进Thijs给出的架构并补充针对不同场景的流程。
为了方便理解在接下来的讲述中我会假定有这样一家叫ACME的新零售公司它已经实现了数字化转型微服务电商平台是支持业务运作的核心基础设施。
在业务架构方面ACME有近千家线下门店这些门店通过POS系统和电商平台对接。公司还有一些物流发货中心拣选Order Picking系统也要和电商平台对接。另外公司还有很多送货司机通过App和电商平台对接。当然ACME还有一些电商网站做线上营销和销售这些网站是电商平台的主要流量源。
虽然支持ACME公司业务运作的技术平台很复杂但是它的核心可以用一个简化的微服务架构图来描述
![](https://static001.geekbang.org/resource/image/22/ff/228199yya6051f1f62f23547a88be4ff.jpg)
可以看出这个微服务架构是运行在Kubernetes集群中的。当然了这个架构实际上并不一定需要Kubernetes环境用传统数据中心也可以。另外它的整体认证授权架构是基于OAuth 2.0/JWT实现的。
接下来,我按这个微服务架构的分层方式,依次和你分析下它的每一层,以及应用认证/授权和服务调用的相关流程。这样你不仅可以理解一个典型的微服务架构该如何分层还可以弄清楚OAuth 2.0/JWT该如何与微服务进行集成。
## 微服务分层架构
ACME公司的微服务架构大致可以分为Nginx反向代理层、Web应用层、Gateway网关层、BEF层和领域服务层还包括一个IDP服务。总体上讲这是一种目前主流的微服务架构分层方式每一层职责单一、清晰。
接下来,我们具体看看每一层的主要功能。
### Nginx反向代理层
首先Nginx集群是整个平台的流量入口。Nginx是7层HTTP反向代理主要功能是实现反向路由也就是将外部流量根据HOST主机头或者PATH路由到不同的后端比方说路由到Web应用或者直接到网关Gateway。
在Kubernetes体系中Nginx是和Ingress Controller入口控制器配合工作的总称为Nginx IngressIngress Controller支持通过Ingress Rules配置Nginx的路由规则。
### Web应用层
这一层主要是一些Web应用html/css/js等资源就住在这一层。
Web服务层通常采用传统的Web MVC + 模版引擎方式处理可以实现服务器端渲染也可以采用单页SPA方式。这一层主要由公司的前端团队负责通常会使用Node.js技术栈来实现也可以采用Spring MVC技术栈实现。具体怎么实现要看公司的前端团队更擅长哪种技术。当这一层需要后台数据时可以通过网关调用后台服务获取数据。
### Gateway网关层
这一层是微服务调用流量的入口。网关的主要职责是反向路由也就是将前端请求根据HOST主机头、或者PATH、或者查询参数路由到后端目标微服务比如图中的IDP/BFF或者直接到领域服务)。
另外,网关还承担两个重要的安全职责:
* 一个是令牌的校验和转换将前端传递过来的OAuth 2.0访问令牌通过调用IDP进行校验并转换为包含用户和权限信息的JWT令牌再将JWT令牌向后台微服务传递。
* 另外一个是权限校验网关的路由表可以和OAuth 2.0的Scope进行关联。这样网关根据请求令牌中的权限范围Scope就可以判断请求是否具有调用后台服务的权限。
关于安全相关的场景和流程,我会在下一章节做进一步解释。
另外网关还需承担集中式限流、日志监控以及支持CORS等功能。
对于网关层的技术选型当前主流的API网关产品像Netflix开源的Zuul、Spring Cloud Gateway等都可以考虑。
### IDP服务
IDP是Identity Provider的简称主要负责OAuth 2.0授权协议处理OAuth 2.0和JWT令牌颁发和管理以及用户认证等功能。IDP使用后台的Login-Service进行用户认证。
对于IDP的技术选型当前主流的Spring Security OAuth或者RedHat开源的KeyCloak都可以考虑。其中Spring Security OAuth是一个OAuth 2.0的开发框架适合企业定制。KeyCloak则是一个开箱即用的OAuth 2.0/OIDC产品。
### BFF层
BFF是Backend for Frontend的简称主要实现对后台领域服务的聚合Aggregation有点类似数据库的Join功能同时为不同的前端体验PC/Mobile/开放平台等提供更友好的API和数据格式。
BFF中可以包含一些业务逻辑甚至还可以有自己的数据库存储。通常BFF要调用两个或两个以上的领域服务甚至还可能调用其它的BFF当然一般并不建议这样调用因为这样会让调用关系变得错综复杂无法理解
如果BFF需要获取调用用户或者OAuth 2.0 Scope相关信息它可以从传递过来的JWT令牌中直接获取。
BFF服务可以用Node.js开发也可以用Java/Spring等框架开发。
### 领域服务层
领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。
根据微服务分层原则领域服务禁止调用其它的领域服务更不允许反向调用BFF服务。这样做是为了保持微服务职责单一Single Responsibility和有界上下文Bounded Context避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域常见的领域服务有用户服务、商品服务、订单服务和支付服务等。
和BFF一样如果领域服务需要获取调用用户或者OAuth 2.0 Scope相关信息它可以从传递过来的JWT令牌中直接获取。
可以看到领域服务和BFF服务都是无状态的它们本身并不存储用户状态而是通过传递过来的JWT数据获取用户信息。所以在整个架构中微服务都是无状态、可以按需水平扩展的状态要么存在用户端浏览器或者手机App中要么存在集中的数据库中。
## OAuth 2.0/JWT如何与微服务进行集成
以上就是ACME公司的整个微服务架构的层次了。这个分层架构对于大部分的互联网业务系统场景都适用。因此如果你是一家企业的架构师需要设计一套微服务架构完全可以参考它来设计。接下来我再演示几个典型的应用认证场景以及相应的服务调用流程来帮助你理解OAuth 2.0/JWT是如何和微服务进行集成的。
### 场景1第一方Web应用+资源拥有者凭据模式
这个场景是用户访问ACME公司自己的电商网站假设这个电商网站是用Spring MVC开发的。考虑到这是一个第一方场景也就是公司自己开发的网站应用我们可以选OAuth 2.0的资源拥有者凭据许可Resource Owner Password Credentials Grant也可以选更安全的授权码许可Authorization Code Grant。因为这里没有第三方的概念所以我们就选相对简单的资源拥有者凭据许可。
下面是一个认证授权流程样例。注意,这个只是突出了关键步骤,实际生产的话,还有很多需要完善和优化的地方。另外,为描述简单,这里假定一个成功流程。
![](https://static001.geekbang.org/resource/image/b6/bd/b658befe1da937fa3685b55522487dbd.jpg)
在上面的图中用户对应OAuth 2.0中的资源拥有者ACME IDP对应OAuth 2.0中的授权服务。另外前面架构图中的后台微服务包括BFF和基础领域服务对应OAuth 2.0中的受保护资源。
下面是流程说明:
1. 用户通过浏览器访问ACME公司的电商网站点击登录链接。
2. Web应用返回登录界面这个登录页可以是网站自己定制开发
3. 用户输入用户名、密码进行认证。
4. Web应用将用户名、密码通过网关转发到IDP的令牌获取端点POST /oauth2/tokengrant\_type=password
5. IDP通过Login Service对用户进行认证。
6. IDP认证通过返回有效访问令牌根据需要也可以返回刷新令牌
7. Web应用接收到访问令牌创建用户Session并将OAuth 2.0令牌保存其中,然后返回登录成功到用户端。
8. 用户浏览器中记录Session Cookie登录成功。
那接下来,我们再来看看认证授权之后的服务调用流程。同样,这里也只是突出了关键步骤,并假定是一个成功流程。
![](https://static001.geekbang.org/resource/image/c8/4a/c88e46dd26deb76d6yy8f42f83066f4a.jpg)
1. 用户登录后,在网站上点击查看自己的购物历史记录。
2. Web应用通过网关调用后台API查询用户的购物历史记录请求HTTP header中带上OAuth 2.0令牌来自用户Session
3. 网关截取OAuth 2.0令牌去IDP进行校验。
4. IDP校验令牌通过再通过令牌查询用户和Scope信息构建JWT令牌返回。
5. 网关获得JWT令牌校验Scope是否有权限调用API如果有就转发到后台API进行调用。
6. 后台BFF或者领域服务通过传递过来的JWT获取用户信息根据用户ID查询购物历史记录返回。
7. Web应用获得用户的购物历史数据可以根据需要缓存在Session中再返回用户端。
8. 购物历史数据返回到用户浏览器端。
注意,这个服务调用流程,也可以应用在其他场景中,比如我们接下来要学习的“第一方移动应用+授权码许可模式”和“第三方Web应用+授权码许可模式”。基本上只要你理解了这个流程原理,就可以根据实际场景灵活套用。
### 场景2第一方移动应用+授权码许可模式
第二个场景是用户通过手机访问ACME公司自己的电商App。这是第一方的原生应用Native App场景通常考虑选用OAuth 2.0的用户名密码模式,但是并不安全(参考[MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT PART 3 IDP](https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-3-idp/)的Security Consideration部分所以业界建议采用授权码模式而且是要支持[PKCE](https://dzone.com/articles/what-is-pkce)扩展的授权码模式。
那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假定是一个成功流程。
![](https://static001.geekbang.org/resource/image/44/93/443dab973274d8d13c76b2ef4cd1d393.jpg)
1. 用户访问电商App点击登录。
2. App生成PKCE相关的code verifier + challenge。
3. App以内嵌方式启动手机浏览器访问IDP的统一认证页(GET /authorize)请求带上PKCE的code challenge相关参数。
4. IDP返回统一认证页。
5. 用户认证和授权。
6. IDP通过Login Service对用户进行认证。
7. IDP返回授权码到App浏览器。
8. App截取浏览器带回的授权码将授权码+PKCE code verifer通过网关转发到IDP的令牌获取端点POST /oauth2/token, grant\_type=authorization-code
9. IDP校验PKCE和授权码校验通过则返回有效访问令牌。
10. App获取令牌本地存储登录成功。
之后App如果需要和后台交互可直接通过网关调用后台微服务请求HTTP header中带上OAuth 2.0访问令牌即可。后续的服务调用流程,和“第一方应用+资源拥有者凭据模式”类似。
### 场景3第三方Web应用+授权码模式
第三个场景是某第三方合作厂商开发了一个Web网站要访问ACME公司的电商开放平台API。这是一个第三方Web应用场景通常选用OAuth 2.0的授权码许可模式。
那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假设是一个成功流程。
![](https://static001.geekbang.org/resource/image/02/57/02affbdf32f005af65454f3acc4cd957.jpg)
1. 用户访问这个第三方Web应用点击登录链接。
2. Web应用后台向ACME公司的IDP服务发送申请授权码请求GET /authorize
3. 用户被重定向到ACME公司的IDP统一登录页面。
4. 用户进行认证和授权。
5. IDP通过Login Service对用户进行认证。
6. 认证和授权通过IDP返回授权码。
7. Web应用获得授权码再向IDP服务的令牌获取端点发起请求POST /oauth2/token, grant\_type=authorization-code
8. IDP校验授权码校验通过则返回有效OAuth 2.0令牌(根据需要也可以返回刷新令牌)。
9. Web应用创建用户Session将OAuth 2.0令牌保存在Session中然后返回登录成功到用户端。
10. 用户浏览器中记录Session Cookie登录成功。
之后第三方Web应用如果需要和ACME电商平台交互可直接通过网关调用微服务请求HTTP header中带上OAuth 2.0访问令牌即可。后续的服务调用流程,和前面的“第一方应用+资源拥有者凭据模式”类似。
### 额外说明
除了上面的三个主要场景和流程我还要和你分享6点。这6点是对上面基本流程的补充也是企业级的OAuth 2.0应用要额外考虑的。
**第一点是IDP的API要支持从OAuth 2.0访问令牌到JWT令牌的互转**。今天我们提到的集成架构采用OAuth 2.0 访问令牌 + JWT令牌的混合模式中间需要实现OAuth 2.0访问令牌到JWT令牌的互转。这个互转API并非OAuth 2.0的标准有些IDP产品比方Spring Security OAuth可能并不支持因此需要用户定制扩展。
**第二点是关于单页SPA应用场景**。关于单页SPA应用场景简单做法是采用隐式许可但是这个模式是OAuth 2.0中比较不安全的所以一般不建议采用。对于纯单页SPA应用业界推荐的做法是
* 如果浏览器支持Web Crypto for PKCE则可以考虑使用类似“第一方移动应用”场景下的授权码许可+PKCE扩展流程
* 否则考虑SPA+传统Web混合hybrid模式前端页面可以住在客户浏览器端中但登录认证还是由后台Web站点配合实现走类似“第一方Web应用”场景的资源拥有者凭据模式或者“第三方Web应用”场景下的授权码许可模式。
**第三点是关于SSO单点登录场景**。为了简化描述上面的流程没有考虑SSO单点登录场景。如果要支持Web SSO那么各种应用场景都必须通过浏览器+IDP登录页集中登录并且IDP要支持Session用于维护登录态。如果IDP以集群方式部署的话还要考虑粘性Sticky Session或者集中式Session。
这样当用户通过一个Web应用登录后后续如果再用其它Web应用登录的话只要IDP上的Session还存在那么这个登录就可以自动完成相当于单点登录。
当然如果要支持SSOIDP的Session Cookie要种在Web应用的根域上也就是说不同Web应用的根域必须相同否则会有跨域问题。
**第四点是关于IDP和网关的部署方式**。前面的几张架构图中IDP虽然躲在网关后面但实际上IDP可以直接通过Nginx对外暴露不经过网关。或者IDP的登录授权页面可以通过Nginx直接暴露API接口则走网关。
**第五点是关于刷新令牌**。为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。
**第六点是关于Web Session**。为了简化描述在上面的流程中Web应用登录成功后假设启用Web Session也就是服务器端Session。在实际场景中Web Session并非唯一选择也可以采用简单的客户端Session方式也称无状态Session也就是在客户端浏览器Cookie中保存OAuth 2.0访问令牌。
## 小结
好了以上就是今天的主要内容了。今天我和你分享了如何将行业标准的OAuth 2.0/JWT和微服务集成起来你需要记住以下四点。
第一目前主流的微服务架构大致可以分为5层分别是Nginx流量接入层->Web应用层->API网关层->BFF聚合层->领域服务层。这个架构可以住在云原生的Kubernetes环境中也可以住在传统数据中心里头。
第二API网关是微服务调用的入口承担重要的安全认证和鉴权功能。主要的安全操作包括通过IDP校验OAuth 2.0访问令牌并获取带用户和权限信息的JWT令牌基于OAuth 2.0的Scope对API调用进行鉴权。
第三在微服务架构体系下通常需要一个集中的IDP服务它相当于一个Authentication & Authorization as a Service角色负责令牌颁发/校验/管理,还有用户认证。
第四在今天这一讲提出的架构中Web应用层网关之前的安全机制主要基于OAuth 2.0访问令牌实现(它是一种**透明令牌**或者称**引用令牌**微服务层网关之后的安全机制主要基于JWT令牌实现它是一种**不透明**的**自包含令牌**。网关层在中间实现两种令牌的转换。这是一种OAuth 2.0访问令牌+JWT令牌的混合模式。
之所以这样设计是因为Web层靠近用户端如果采用JWT令牌会暴露用户信息有一定的安全风险所以采用OAuth 2.0访问令牌它是一个无意义随机字符串。而在网关之后安全风险相对低同时很多服务需要用户信息所以采用自包含用户信息的JWT令牌更合适。
当然如果企业内网没有特别的安全考量也可以直接传递完全透明的用户信息例如使用JSON格式
## 思考题
1. 除了今天我们讲到的OAuth 2.0访问令牌+JWT令牌的混合模式实践中也可以全程采用OAuth 2.0访问令牌或者全程采用JWT令牌。对比混合模式如果全程采用OAuth 2.0访问令牌或者全程采用JWT令牌你觉得有哪些利弊呢
2. 你可以说说自己对基于传统Web应用的认证授权机制的理解吗并对比今天讲到的现代微服务的认证授权机制你可以说说它们之间的本质差异和相似点吗
欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。