vlambda博客
学习文章列表

API网关对OAuth 2.0的实现调研

OAuth2

OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息。
举例说明:有这样一种场景,一个用户(假设是QQ),希望让一个第三方的应用(比如说某个论坛),能够得到关于自身的一些信息(唯一用户标识,比如说QQ号,用户个人信息,比如说是一些基础资料,昵称和头像等)。但是在获得这些资料的同时,却也不能提供用户名和密码之类的验证信息。比如说用户不可能将自身的用户名和密码给第三方让第三方到用户中心之类的地方去获取信息。要达到这样的结果肯定有许多的实现方式。而Oatuh2就是实现上述目标的一种规范,或者说是具体实现的指导方案。
根据RFC6749文档,Oatuh2大致的流程如下图所示

下面我们对istio,阿里云api网关、kong对OAuth2的实现方案做一个简单的调研,由于时间限制,没有实际操作过,很多资料都是找的,如有不当之处,欢迎批评指正。

istio

在Istio中,对于Authentication来说,它提供了两种身份验证模式:传输身份验证和原始身份验证。
传输身份验证:也称为服务到服务身份验证:验证建立直接连接的客户端。Istio提供双向TLS(mutual TLS)作为用于传输身份验证的完整堆栈解决方案。在Istion中可以轻松启用此功能,而无需更改服务代码。此解决方案:
1、为每个服务提供一个强大的标识,以表示其角色,以实现跨集群和云的互操作性。2、保证服务到服务和最终用户到服务通信的安全3、提供密钥管理系统,以自动进行密钥和证书的生成、分发和轮换。
在双向TLS身份验证过程中,Istio通过客户端和服务器端的Envoy代理建立服务到服务的通信通道,整个验证服务流程如下:
1、Istio将来自客户端的出站流量重新路由到客户端的本地Sidecar Envoy。2、客户端Envoy与服务器端Envoy开始双向TLS握手。在握手期间,   客户端Envoy还会进行安全的命名检查,以验证服务器证书中提   供的服务帐户是否有权运行目标服务。3、客户端Envoy和服务器端Envoy建立双向TLS连接,Istio将流量 从客户端Envoy转发到服务器端Envoy。4、授权后,服务器端Envoy通过本地TCP连接将流量转发到服务器服务。
原始身份验证:也称为终端用户身份验证:将发出请求的原始客户端验证为最终用户或设备。Istio通过JSON Web令牌(JWT)验证启用请求级身份验证,并为开发人员提供使用OpenID Connect提供者(ORY Hydra,Keycloak,Auth0, Firebase Auth, Google Auth)和自定义身份验证的简化经验 。一般来说,其身份验证策略使用.yaml 文件来指定,部署后,策略将被保存在Istio配置存储。Pilot作为Istio的控制器负责监视配置存储。在任何策略更改后,Pilot都会将新策略转换为适当的配置,以告诉Envoy Sidecar代理如何执行所需的身份验证机制。Pilot可以获取公共密钥,并将其附加到配置中以进行JWT验证。另外,Pilot提供了Istio系统管理的密钥和证书的路径,并将它们安装到应用程序容器中以实现相互TLS。Istio将配置异步的发送到目标端点。代理收到配置后,新的身份验证要求将立即在该容器上生效。

在上述两种情况下,Istio都会 通过自定义的Kubernetes API在Istio config store存储身份验证策略。 而在Istio 1.5 版中,所有安全策略,包括自动 mTLS、AuthenticationPolicy(对等身份验证和 RequestAuthentication)和授权现在都处于 Beta 版。 SDS 已经处于稳定版。 授权(Authorization)现在支持“拒绝”语义,以强制执行不可覆盖的强制性控件。 此外,Node 代理和 Istio 代理已组合到一个二进制文件中,这意味着不再需要配置 PodSecurityPolicy。 现在不再需要在每个 Pod 上安装证书,也不必在证书更改时重新启动 Envoy。 证书直接从 Istiod 交付到每个 pod。 而且,每个 pod 都有唯一的证书。  

但是从上面两种描述中,我们可以看到,其身份验证功能基本上只到端的验证。具体涉及细化到用户层面的auth验证则基本没有提及。要做用户层面的auth jwt验证的话,我们可以采用sidecar模式,即在k8s中专门起一个用来auth认证授权的系统作为service,随istio一起部署。这里我们可以找一个开源项目进行参考[HiAuth](https://github.com/bestaone/HiAuth),此项目基于Springboot项目更容易集成到多个平台(SpringCloud、K8S、Istio)。


阿里云api网关

阿里云API网关提供多种针对客户端请求的安全认证方式,包括阿里云APP认证方式、OpenID Connect等。


OpenID Connect

OpenID Connect上我们只做jwt的调研,即原有在API上配置的OpenId Connect功能通过JWT认证插件实现, 推荐使用JWT认证插件配置,原有API上的OpenId Connect的功能将被插件的配置覆盖。
相比配置在API上的OpenId Connect配置, JWT认证插件具备以下优势:
1、不再需要额外配置一个授权API,可以用任何方式来生成与分发JWT,API网关仅负责通过公钥JWK认证JWT。2、支持不含kid的jwk。3、支持配置多个jwk。4、支持直接从header或query读取Token,不需要每个API都设置Token参数。5、当JWT以Authorization bearer {token}方式传输时,可以通过parameter: Authorization,  parameterLocation: header方式配置已实现正确的Token读取。6、通过添加preventJtiReplay: true配置,可支持基于claim:jti的防重放检查7、通过添加bypassEmptyToken: true配置,可在Token不存在时跳过验证直接转发给后端8、通过添加ignoreExpirationCheck: true配置, 可忽略Token的exp超期校验
关于阿里云JWT认证插件的详细用法可以参考[阿里云API网关-JWT认证插件](https://help.aliyun.com/document_detail/103228.html?spm=a2c4g.11186623.6.597.243f6288kDTgEs)
针对JWT认证模式下,阿里云还提供了访问控制插件,可以根据请求参数或上下文,来执行条件判断,用于过滤不希望传递到后端的请求。这个功能可以说是更加细化了jwt的认证授权。


举例说明一下:
假设我们的API请求Path为/{userId}/..., API使用JWT认证,JWT中有userId和userType两个claim, 我们这个插件的校验条件为
1、当userType=admin时,允许所有的路径2、当userType=user时,仅允许/{userId}路径一致的请求
关于访问控制插件,详情可参考[阿里云API网关-访问控制插件](https://help.aliyun.com/document_detail/154200.html?spm=a2c4g.11186623.6.601.30b9167cDgXZZc)

阿里云APP认证方式

对于”阿里云APP“这种认证方式,目前用户可以设置两种认证形式:
1、签名认证。2、简单认证。


1、签名认证

签名认证采用签名密钥验证的方式进行,签名密钥是由用户创建的一对 Key 和 Secret,相当于给网关颁发了一个账号密码,网关向用户后端服务请求时会将密钥计算后一起传过去,后端获取相应的字符串做对称计算,就可以对网关做身份验证。
使用签名密钥时需要了解以下几点:
1、创建密钥时需要选择 Region,Region 一旦选定不能更改,而且密钥只能被绑定 到同一个 Region 下的API上。2、一个 API 仅能绑定一个密钥,密钥可以被替换和修改,可以与 API 绑定或者解绑。3、将密钥绑定到 API 之后,由网关抛向服务后端的该 API 的请求均会加上签名信息。  后端server需要做对称计算来解析签名信息,从而验证网关的身份。
具体实现方案可参考[请求签名说明文档](https://help.aliyun.com/document_detail/29475.html?spm=a2c4g.11186623.2.17.43d52d3ehVFMYA)

2、简单认证

简单认证,顾名思义,和签名认证方式相比要简单很多,省去了复杂的生成签名的过程。简单认证方式直接使用API网关颁发的AppCode进行身份认证,调用者将AppCode放到请求头中,或者放到请求的Query参数中进行身份认证,实现快速调用API的能力。

简单认证的整体流程描述如下:API提供者创建API的时候选择”阿里云APP“认证模式,且支持AppCode认证。创建APP时需要API提供者给调用者的APP进行API授权,API调用者到API网关控制台的”应用管理“找到AppCode/AppSecret进行签名认证的调用或者AppCode进行简单认证的API调用。

API提供者将API设置成允许AppCode调用之后,API调用者就可以使用简单认证方式进行调用了,不用再在客户端实现复杂的签名算法了。Appcode的使用方法主要有两种方式,一种是将AppCode放在Header中进行调用,一种是将AppCode放在Query参数中进行调用。
1、将AppCode放在Header中,请求Header中添加一个”Authorization“参数,Authorization字段的值的格式为“APPCODE + 半角空格 +APPCODE值”。即:Authorization:APPCODE AppCode值。举例说明:Authorization:APPCODE 3F2504E04F8911D39A0C0305E82C3301
2、将AppCode放在Query中,请求Query中添加的“AppCode”参数(同时支持”appcode” , “appCode” , “APPCODE” , “APPCode”四种写法);举例说明:http://www.aliyum.com?AppCode=3F2504E04F8911D39A0C0305E82C3301
关于使用AppCode认证,详情可参见[阿里云API网关-简单认证AppCode](https://help.aliyun.com/document_detail/115437.html?spm=a2c4g.11186623.6.584.19f9cea4cqatdD)

Kong

图中可见,kong的官方设计是需要我们自己开发一个backend(后端)项目,来做客户端与kong中间层,用于拼接完整的oauth请求信息并请求至kong。但是这里的webapp backend是没有被kong管理起来的,它也需要kong来提供管理服务。

关于如何以插件方式实现kong oauth认证,详情可以参见[Kong-Authentication指南](https://docs.konghq.com/2.0.x/auth/)


当然官方提供了很多强大的Authentication插件供我们使用,详情参见[Kong Hub AUTHENTICATION插件](https://docs.konghq.com/hub/)


参考资料

1、https://github.com/bestaone/HiAuth2、https://www.kubernetes.org.cn/6298.html3、https://help.aliyun.com/document_detail/29481.html?spm=a2c4g.11186623.6.564.4db31aedyXS3Gj4、https://help.aliyun.com/document_detail/115437.html?spm=a2c4g.11186623.6.584.19f9cea4cqatdD5、https://help.aliyun.com/document_detail/103228.html?spm=a2c4g.11186623.6.597.43e21945Xf35bI6、https://help.aliyun.com/document_detail/154200.html?spm=a2c4g.11186623.6.601.30b9167cDgXZZc7、https://www.jianshu.com/p/4ddb916d31958、https://docs.konghq.com/2.0.x/auth/9、https://docs.konghq.com/hub/