12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构
架构图
- 令牌的校验和转换,将前端传递过来的 OAuth 2.0 访问令牌,通过调用 IDP 进行校验,并转换为包含用户和权限信息的 JWT 令牌,再将 JWT 令牌向后台微服务传递。
- 权限校验,网关的路由表可以和 OAuth 2.0 的 Scope 进行关联。这样,网关根据请求令牌中的权限范围 Scope,就可以判断请求是否具有调用后台服务的权限。
IDP 服务:IDP 是 Identity Provider 的简称,主要负责 OAuth 2.0 授权协议处理,OAuth 2.0 和 JWT 令牌颁发和管理,以及用户认证等功能。IDP 使用后台的 Login-Service 进行用户认证。
BFF 层:主要实现对后台领域服务的聚合(Aggregation,有点类似数据库的 Join)功能,同时为不同的前端体验(PC/Mobile/ 开放平台等)提供更友好的 API 和数据格式。
领域服务层:领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。
场景 1:第一方 Web 应用 + 资源拥有者凭据模式
- 用户通过浏览器访问 ACME 公司的电商网站,点击登录链接。
- Web 应用返回登录界面(这个登录页可以是网站自己定制开发)。
- 用户输入用户名、密码进行认证。
- Web 应用将用户名、密码,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token,grant_type=password)。
- IDP 通过 Login Service 对用户进行认证。
- IDP 认证通过,返回有效访问令牌(根据需要也可以返回刷新令牌)。
- Web 应用接收到访问令牌,创建用户 Session,并将 OAuth 2.0 令牌保存其中,然后返回登录成功到用户端。
- 用户浏览器中记录 Session Cookie,登录成功。
认证授权之后的服务调用流程
- 用户登录后,在网站上点击查看自己的购物历史记录。
- Web 应用通过网关调用后台 API(查询用户的购物历史记录),请求 HTTP header 中带上 OAuth 2.0 令牌(来自用户 Session)。
- 网关截取 OAuth 2.0 令牌,去 IDP 进行校验。
- IDP 校验令牌通过,再通过令牌查询用户和 Scope 信息,构建 JWT 令牌,返回。
- 网关获得 JWT 令牌,校验 Scope 是否有权限调用 API,如果有就转发到后台 API 进行调用。
- 后台 BFF(或者领域服务)通过传递过来的 JWT 获取用户信息,根据用户 ID 查询购物历史记录,返回。
- Web 应用获得用户的购物历史数据,可以根据需要缓存在 Session 中,再返回用户端。
- 购物历史数据返回到用户浏览器端。
场景 2:第一方移动应用 + 授权码许可模式
- 用户访问电商 App,点击登录。
- App 生成 PKCE 相关的 code verifier + challenge。
- App 以内嵌方式启动手机浏览器,访问 IDP 的统一认证页 (GET /authorize),请求带上 PKCE 的 code challenge 相关参数。
- IDP 返回统一认证页。
- 用户认证和授权。
- IDP 通过 Login Service 对用户进行认证。
- IDP 返回授权码到 App 浏览器。
- App 截取浏览器带回的授权码,将授权码 +PKCE code verifer,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token, grant_type=authorization-code)。
- IDP 校验 PKCE 和授权码,校验通过则返回有效访问令牌。
- App 获取令牌,本地存储,登录成功。
场景 3:第三方 Web 应用 + 授权码模式
- 用户访问这个第三方 Web 应用,点击登录链接。
- Web 应用后台向 ACME 公司的 IDP 服务发送申请授权码请求(GET /authorize)。
- 用户被重定向到 ACME 公司的 IDP 统一登录页面。
- 用户进行认证和授权。
- IDP 通过 Login Service 对用户进行认证。
- 认证和授权通过,IDP 返回授权码。
- Web 应用获得授权码,再向 IDP 服务的令牌获取端点发起请求(POST /oauth2/token, grant_type=authorization-code)。
- IDP 校验授权码,校验通过则返回有效 OAuth 2.0 令牌(根据需要也可以返回刷新令牌)。
- Web 应用创建用户 Session,将 OAuth 2.0 令牌保存在 Session 中,然后返回登录成功到用户端。
- 用户浏览器中记录 Session Cookie,登录成功。
额外说几点
第一点是,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 中比较不安全的,
第三点是,关于 SSO 单点登录场景。为了简化描述,上面的流程没有考虑 SSO 单点登录场景。如果要支持 Web SSO,那么各种应用场景都必须通过浏览器 +IDP 登录页集中登录,并且 IDP 要支持 Session,用于维护登录态。如果 IDP 以集群方式部署的话,还要考虑粘性 Sticky Session 或者集中式 Session。
第四点是关于 IDP 和网关的部署方式。前面的几张架构图中,IDP 虽然躲在网关后面,但实际上 IDP 可以直接通过 Nginx 对外暴露,不经过网关。或者,IDP 的登录授权页面,可以通过 Nginx 直接暴露,API 接口则走网关。
第五点是关于刷新令牌。为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。
第六点是关于 Web Session。为了简化描述,在上面的流程中,Web 应用登录成功后假设启用 Web Session,也就是服务器端 Session。在实际场景中,Web Session 并非唯一选择,也可以采用简单的客户端 Session 方式,也称无状态 Session,也就是在客户端浏览器 Cookie 中保存 OAuth 2.0 访问令牌。
全程使用oauth2令牌,那么网关之后的每个微服务都需要自己根据令牌token去idp或者redis或者mysql中去查询用户信息,如果全程使用jwt令牌,主要是还是由于jwt自包含用户信息,存在暴露用户信息的安全风险。(如果jwt中只存在用户名,不存在其他相关信息也可以考虑全程使用jwt令牌)
原文