开放API网关实践(一)
如何设计实现一个轻量的开放API网关.
文章地址: https://blog.piaoruiqing.com/2019/08/05/openapi-gateway-design/
前言
随着业务的发展, 所对接的第三方越来越多, 各个业务系统面临着同样一个问题: 如何让第三方安全快速接入
. 此时有一个集验签、鉴权、限流、降级等功能于一身的API网关服务变得尤为重要.
接下来将分享如何设计实现一个轻量级的API开放网关, 包括接口设计、数据库设计、签名验签方案、鉴权等. 本文侧重于总体设计, 具体实现细节将陆续在后续的文章中分享.
API网关简介
API网关在微服务中尤为重要, 其抽象了鉴权、限流、降级等各个业务系统通用的功能. 作为众多内部业务系统外的一层屏障.
基本需求
- 签名及验签
- 鉴权
- 路由
- 权限及资源管理
总体设计
验签、鉴权等功能以职责链的方式进行处理, 网关根据配置进行路由并附加参数用以配合业务系统进行处理(如数据过滤等). 简要请求处理流程如下:
graph TD
START((START)) --> A
A(调用方) -->|发起请求| B(开放API)
B --> C{验签通过?}
C -->|no| END((END))
C -->|yes| Cy(鉴权)
Cy --> D{鉴权通过?}
D -->|no| END
D -->|yes| E(附加参数)
E --> F(向内部服务发起请求)
F --> G(解析附加参数并处理请求)
G --> END
接口设计
网关最基本的功能是转发请求, 常见的方式是根据配置中的路由规则将请求转发给内部服务, 如:
将/order/*
的请求转发给内部的订单系统、/user/*
的请求转发给内部的用户系统, 这种做法常用于对整个业务系统负责的基础网关.
而本文所设计的是服务于第三方的开放API网关
, 并未使用上述做法, 而是将请求的资源作为参数放到请求体中, 其原因如下:
- 开放API服务于第三方, 屏蔽内部路径, 有利于提供命名统一且规范的接口.
- 请求接口的映射由网关的路由表维护, 内部接口升级甚至切换到新服务对外接口不变.
- 能够更细粒度地针对接口进行权限控制、限流、统计等.
地址
开放API网关对外提供唯一入口, 具体请求的资源作为参数传入.
公共参数
为了简化签名和验签的操作, 同时也提高灵活度, 唯一入口的约定了固定的公共参数和返回值, 如下:
公共请求参数
参数名称 | 是否必须 | 类型 | 示例 | 备注 |
---|---|---|---|---|
app_id | 是 | string | 应用ID | |
method | 是 | string | aaa.bbb.ccc | 请求方法 |
charset | 是 | string | UTF-8 | 编码 |
format | 是 | string | JSON | 业务参数格式 |
sign_type | 是 | string | RSA2 | 签名类型 |
sign | 是 | string | 签名 | |
timestamp | 是 | number | 1564929661796 | 时间戳, 单位: 毫秒 |
nonce | 是 | string | 63DCB93D270E44D49499F9E5D55705FE | 随机字串(建议使用UUID) |
version | 是 | string | 1.0 | 接口版本 |
biz_content | 是 | string | {"start_time":"1564929661796", …} | 请求业务参数 |
app_id
: 应用ID, 应用ID是授权的主体, 是调用方的身份标识method
: 请求方法, 与内部URL对应, 由网关的路由表维护.timestamp
和nonce
用来防重放攻击.biz_content
: 业务参数, 这个参数将转发给内部业务系统.
公共返回参数
参数名称 | 是否必须 | 类型 | 示例 | 备注 |
---|---|---|---|---|
code | 是 | number | 0 | 错误码 |
message | 否 | string | 错误信息 | |
charset | 是 | string | UTF-8 | 编码 |
format | 是 | string | JSON | 返回参数格式 |
sign_type | 是 | string | RSA2 | 签名类型 |
sign | 是 | string | 签名 | |
timestamp | 是 | number | 时间戳, 单位: 毫秒 | |
biz_content | 是 | string | {"id":"1564929661796", …} | 返回业务参数 |
biz_content
: 返回业务参数, 网关转发业务系统的返回值.
本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接:https://blog.piaoruiqing.com.
如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com.
签名方案
调用方和服务方均生成2048
位RSA
秘钥, 交换公钥. 私钥用于签名, 公钥用于验签, 开放API网关对外接口使用https
, 故暂不需额外做加密处理.
签名算法
签名算法名称 | 标准签名算法名称 | 备注 |
---|---|---|
RSA2 | SHA256WithRSA | 强制要求 RSA 密钥的长度至少为 2048 |
签名规则
签名参数内容
剔除sign
之后的全部参数.
签名参数排序
按照参数名的ASCII
码递增排序(字母升序排序).
签名生成方式
排序后的参数列表组合成参数名a=参数值a&参数名b=参数值b&...&参数名z=参数值z
的字符串, 并使用私钥
生成sign
.
数据库设计
数据库用于存储秘钥权限等配置, 程序和数据库之间有多级缓存用以提高访问速度. 简要ER图如下:
app
: 调用方主体, 用于标识请求方身份.group
: 组,app
分组, 可通过group
统一进行授权.subject
: 主体(app/group).resource
: 资源, 维护请求资源与内部接口的映射关系,url
+http_method
对应唯一的resource_id.
技术选型
网关除了满足功能上的需求外, 性能上的需求也需要着重考虑, 毕竟作为各个业务系统对外的唯一入口, 网关的性能可能会成为整个业务系统的瓶颈. 业务并不复杂, 性能要求高, 响应式编程正是一个不错的选择.
Spring WebFlux
+netty
: 响应式Web框架.Spring Data Reactive Redis
+Lettuce
: 响应式redis客户端.Guava
: Google工具包, 使用LoadingCache
作为进程内缓存.
结语
网关作为内部系统外面的一层屏障和入口, 除基本功能和性能上的需求外, 监控、统计、日志等都是需要考虑到的问题, 网关方面开源产品众多, 但选择时一定要考虑自身业务, 适合自身的前提下参考各个成熟的方案进行实践.
[…] 文章地址: https://blog.piaoruiqing.com/blog/2019/08/05/开放api网关实践/ […]
[…] 上一篇文章《开放API网关实践(一)》中的接口设计提到timestamp和nonce两个参数的作用是用来放重放. 先抛出两个问题: […]
看了您的文章感觉受益匪浅,谢谢。不过小弟有个疑问:如果使用springcloud技术栈的话,要如何在springcloud gateway去实现路由转发呢?如果使用webclient请求的话是不是意味着要自己手动实现客户端负载均衡和失败重连了。再次感觉您!
如果是常规的需求,springcloud-gateway已经提供了比较完善的解决方案,你可以参考官方文档进行配置(https://spring.io/projects/spring-cloud-gateway),比如通过配置进行路由等 https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#shortcut-configuration