探索使用Nginx +Lua 构建 API 网关

    Nginx 是一种网络服务器,也可用作反向代理、负载平衡器、邮件代理和 HTTP 缓存。Nginx 可用于创建 API 网关,以事件驱动的方式处理请求,在请求进入服务器时以快速、低资源占用的方式处理查询。此外,它还能降低复杂性,并通过缩短 API 调用的平均响应时间最大限度地提高性能. 我们大多数人对 Kong 已经很熟悉了,但我想探索使用 OpenResty 构建 API Gateway 的可能性。


背景

NginxOpenResty之间的联系主要体现在以下几个方面:

  1. 基础与扩展
    • Nginx是一个高性能的HTTP和反向代理web服务器,同时也是IMAP/POP3/SMTP服务器。它以稳定性、丰富的功能集、简单的配置文件和低系统资源消耗而闻名。
    • OpenResty是基于Nginx的Web平台,它通过Lua脚本语言扩展了Nginx的功能,提供了一系列高级特性,包括但不限于Lua脚本支持、Web应用开发框架、流媒体处理等。
  2. 应用场景
    • Nginx适用于大多数Web服务器和服务能力的需求,特别是当对并发能力有较高要求时。它在中国有广泛的应用,如百度、京东、新浪、网易、腾讯、淘宝等网站都使用Nginx。
    • OpenResty则更适合需要快速开发和高性能处理的场景,尤其是当业务逻辑较为复杂时。它提供了更强大的Web应用开发能力,可以通过Lua脚本对Nginx核心和Nginx C模块进行编程。
  3. 性能考量
    • Nginx因其轻量级和并发能力强而著称,能够在连接高并发的情况下提供稳定的性能。
    • OpenResty虽然使用了LuaJIT作为解释器,在处理复杂逻辑时的性能可能不如纯Nginx,但在某些情况下,尤其是在业务逻辑复杂的环境中,OpenResty可以提供更高的开发效率和灵活性。
  4. 功能性与灵活性
    • Nginx提供了基本的Web服务器和反向代理服务,以及负载均衡、动静分离等功能。
    • OpenResty在Nginx的基础上,通过Lua脚本扩展了更多功能,包括Web应用开发框架、流媒体处理等,提供了更高的灵活性和定制性。
  5. 结构与维护
    • Nginx的代码完全用C语言编写,并已经移植到多个体系结构和操作系统上。
    • OpenResty打包了标准的Nginx核心、众多第三方模块以及它们的大多数依赖项。由于OpenResty的维护者也是其中打包的Nginx模块的作者,因此OpenResty可以确保所包含的所有组件可靠地协同工作。

API网关(API Gateway)是一个核心的服务架构组件,用于管理、路由和保护对后端服务的访问。它充当了系统内外的接口,负责接收来自客户端的请求,并将其路由到相应的后端服务,然后将服务的响应返回给客户端。API网关在现代软件架构中扮演着至关重要的角色,特别是在微服务架构中。

API网关的主要功能

  1. 安全性:负责保护后端服务免受恶意攻击和未经授权的访问。可以实施认证、授权、数据加密等安全策略,以确保只有授权的用户或应用程序可以访问服务。
  2. 路由和转发:负责将请求路由到正确的后端服务。可以根据请求的路径、方法、头部等条件进行路由,以确保请求被正确地转发到相应的服务。
  3. 协议转换和数据转换:通常需要处理不同的通信协议和数据格式。可以将来自客户端的请求转换成后端服务所需的格式,以实现不同系统之间的互操作性。
  4. 负载均衡和缓存:可以分发请求到多个后端服务实例,并且可以对请求进行缓存,以减轻后端服务的负载,提高系统的性能和可扩展性。
  5. 监控和分析:能够记录和监控所有传入和传出的请求,提供关于服务性能和使用情况的指标。这些监控数据对于系统的性能优化和故障排查非常重要。

Kong与API网关的关系

Kong是一个可扩展、开源的云原生API网关,它是API网关的一种实现。Kong可以在分布式环境中管理、监控和安全地发布API。Kong提供了流量控制、认证和授权等功能,与API网关的主要功能相契合。

Kong是基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的API Gateway项目。它基于NGINX和Apache Cassandra或PostgreSQL构建,提供了易于使用的RESTful API来操作和配置API管理系统。Kong可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,以应对大批量的网络请求。


Keycloak是一个开源的身份和访问管理解决方案,它为现代应用程序和服务提供了强大的身份验证和授权功能。以下是关于Keycloak的详细介绍:

  1. 定义与核心功能
    • Keycloak提供了单点登录(SSO)功能,允许用户使用单一的凭证访问多个相关但独立的系统或应用。
    • 它支持多种标准协议,包括OpenID Connect和OAuth 2.0,这使得Keycloak能够与各种服务进行集成,以提供身份验证和授权功能。
  2. 主要特点
    • 单点登录/登出:用户通过Keycloak验证身份后,可以访问多个应用程序而无需再次登录;同样,登出时只需注销一次即可从所有使用Keycloak的应用程序中登出。
    • 用户管理:Keycloak提供了用户管理、登录、注册、密码策略、安全问题、二步验证、密码重置等功能。这些功能可配置且自定义,满足不同应用的需求。
    • 角色与权限管理:Keycloak支持用户角色和权限管理,以及用户组功能,帮助管理员更精细地控制访问权限。
    • 身份提供商支持:Keycloak支持多种身份提供商,包括社交媒体平台和其他SSO解决方案,为用户提供灵活的登录选项。
    • 客户端适配器:Keycloak提供了丰富的客户端适配器,可以轻松集成到各种类型的应用程序中,包括Web应用、移动应用和后端服务等。
  3. 使用场景
    • 企业内部系统:Keycloak可用于企业内部系统,提供统一的身份验证和访问控制解决方案。
    • 云平台:在云平台上,Keycloak可以作为统一的身份和访问管理中心,为不同的应用和服务提供认证和授权机制。
    • 微服务架构:在微服务架构中,Keycloak可以作为中心化的身份验证和授权服务,保障微服务之间的安全通信。
    • 社交登录集成:Keycloak支持与各种社交登录提供商(如Google、Facebook、GitHub等)集成,简化用户注册和登录流程。
  4. 技术细节
    • Keycloak由Red Hat基金会开发和维护,已获得Apache License 2.0许可证。
    • Keycloak支持LDAP、Active Directory等用户信息存储方式,并提供了用户联盟功能,允许用户从外部身份提供商导入用户信息。
    • Keycloak的管理控制台允许管理员管理用户账户、角色、权限等,并配置与应用程序相关的设置。

基础篇


我们首先需要安装 OpenResty。OpenResty 可以让我们使用 Lua 编写 Nginx 脚本。安装完成后,在浏览器中输入 http://localhost。如果一切顺利,你将看到下面的截图:

这样,你就成功安装了 OpenResty 和 Nginx。现在,我们需要编辑 nginx.conf 文件。你可以在以下位置找到 nginx.conf 文件:

Ubuntu : /usr/local/openresty/nginx/conf
Mac /usr/local/Cellar/openresty/nginx/conf (使用自制软件)
注:也可以在新文件中添加配置,并将其包含在nginx.conf中。

对于基本的 API 网关来说,它应该执行两个重要操作:

  1. 路由
  2. 验证


路由选择
要将端点路由到各自的服务器,您只需要

location /api/test {
    proxy_pass http://localhost:8080/api/test;
}

身份验证
为了进行身份验证,我将使用 JWT。为此,我们需要使用 OPM(OpenResty 包管理器)安装
lua-resty-jwt 库。

opm get SkyLothar/lua-resty-jwt

然后,在 /usr/local/openresty/lualib/resty (Ubuntu) 中创建 jwt-auth.lua,并复制以下代码:

local jwt = require “resty.jwt”
local validators = require “resty.jwt-validators”
if ngx.var.request_method ~= “OPTIONS” and not string.match(ngx.var.uri, “login”) then
   local jwtToken = ngx.var.http_Authorization
if jwtToken == nil then
  ngx.status = ngx.HTTP_UNAUTHORIZED
  ngx.header.content_type = “application/json; charset=utf-8”
  ngx.say(“{\”error\”: \”Forbidden\”}”)
  ngx.exit(ngx.HTTP_UNAUTHORIZED)
  end
local claim_spec = {
  exp = validators.is_not_expired()// To check expiry
}
local jwt_obj = jwt:verify(‘secret’, jwtToken, claim_spec)
if not jwt_obj[“verified”] then
  ngx.status = ngx.HTTP_UNAUTHORIZED
  ngx.header.content_type = “application/json; charset=utf-8”
  ngx.say(“{\”error\”: \”INVALID_JWT\”}”)
  ngx.exit(ngx.HTTP_UNAUTHORIZED)
  end
end


上述 Lua 代码的作用如下:

  1. 如果是 OPTIONS 调用或登录 API,它会将请求转发到下一行执行。
  2. 否则,它会获取授权头中的 JWT 标记。如果令牌不存在,代码会返回 "FORBIDDEN"(禁止)。
  3. 然后会验证 JWT 令牌的真实性和有效期。
  4. 一旦令牌通过验证,它就会将请求转发给相应的服务。否则,代码将返回 "INVALID_JWT"。


在nginx.conf中加入上述文件:

location /api/test {
access_by_lua_file /usr/local/openresty/lualib/resty/jwt-auth.lua;
 proxy_pass http://localhost:8080/api/test;
}

就是这样。您已在 Nginx 上添加了 JWT 身份验证。

测试
要检查我们所做的更改,请使用以下命令重启 OpenResty

sudo service openresty restart

重新启动后,使用 Postman 或任何其他 REST 客户端访问 API 并验证响应。

提示:您可以使用以下网站生成 JWT 标记: http://jwtbuilder.jamiekurtz.com/

待办事项
我们可以通过以下方法改进我们的认证脚本:

  1. 添加不需要 JWT 标记的新 API 端点(如下载 API)的机制。
  2. 目前,脚本中硬编码了 JWT 秘密令牌。也许我们可以将其设置为环境变量,使其动态化。

使用Bearer承载器授权的反向代理(使用 Keycloak 身份服务器)

openresty-keycloak-gateway 是一个完整的反向代理示例,支持 JWT 身份验证。

使用的技术

Keycloak 提供身份验证、授权、用户管理等功能
OpenResty (使用 lua-resty-openidc 模块)、网络平台(如 nginx)
请注意,反向代理需要验证 JWT 令牌才能转发请求。在这种情况下,我们需要提供一个 Authorization: Bearer bearer_token_here 头信息。

此外,在每个请求中,网关都会用一个 X-Real-Name 头信息将授权替换为请求。

前提条件
正常运行的 Keycloak 身份服务器。请参见
此处
一个 Keycloak 领域。参见
此处
访问类型为公开的 Keycloak 客户端。
一个简单的 http echo 服务器(可选)


Dockerfile

FROM openresty/openresty:alpine-fat
RUN mkdir /var/log/nginx
RUN apk add --no-cache openssl-dev
RUN apk add --no-cache git
RUN apk add --no-cache gcc
RUN luarocks install lua-resty-openidc
ENTRYPOINT ["/usr/local/openresty/nginx/sbin/nginx", "-g", "daemon off;"]

执行

docker build -t authproxy .
docker run --name authproxy -d -it -p 8000:8000 -v $PWD/nginx.conf:/nginx.conf authproxy -c /nginx.conf

nginx.conf

events {
     worker_connections 1024;
}

http {

    lua_package_path '~/lua/?.lua;;';

    resolver 8.8.8.8;

    lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
     lua_ssl_verify_depth 5;

    # cache for JWT verification results
       lua_shared_dict introspection 10m;

    server {
        
         listen       8000 default_server;
         listen       [::]:8000 default_server;

        # disbled caching so the browser won't cache the site.
         expires           0;
         add_header        Cache-Control private;

        access_by_lua_block {

            -- perform base64 encoding
             local function toBase64(input)
                   local output = ngx.encode_base64(input, true)
                   return output:gsub('%+', '-'):gsub('/', '_')
             end

            -- serialize to json
             local function toJson(input)
                 return require("cjson").encode(input)
             end

            -- construct token (X-Real-Name header)
             local function toToken(res)

                local function roleOf(role)
                     return "ROLE_" .. role:upper()
                 end

                local function map(func, array)
                       local new_array = {}
                       for i,v in ipairs(array) do
                         new_array[i] = func(v)
                       end
                       return new_array
                 end

                local token = {
                       uuid = res.sub,
                       username = res.preferred_username,
                       email = res.email,
                       first_name = res.given_name,
                       last_name = res.family_name,
                       roles = map(roleOf, res.realm_access.roles)
                   }

                return toBase64(toJson(token))
             end


            local keycloak_base_url = "https://sso.keycloak.test"
             local client_id = "test"
             local realm = "test"


               local opts = {
                 client_id = client_id,
                 discovery = keycloak_base_url .. "/auth/realms/" .. realm .. "/.well-known/openid-configuration",
        
                 -- IN PRODUCTION SET TO YES
                 ssl_verify = "no",
               }

              -- call bearer_jwt_verify to verify JWT signature
               local res, err = require("resty.openidc").bearer_jwt_verify(opts)

              if err then
                 ngx.status = 401
                 ngx.say(err)
                 ngx.exit(ngx.HTTP_UNAUTHORIZED)
               end
              
               -- set X-Real-Name headers with user info
               ngx.req.set_header("X-Real-Name", toToken(res))
         }

        # proxy locations
         location / {
             proxy_set_header Host $host;
             proxy_set_header X-Real-IP $remote_addr;
             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
             proxy_set_header X-Forwarded-Proto $scheme;
            
             # reemove Authorization header
             proxy_set_header Authorization "";

            # http-echo-server (npm i -g http-echo-server)
             proxy_pass
http://host.docker.internal:3000/;
         }
     }
}

keycloak
更改 client_id
更改 keycloak_base_url 以定位 Keycloak 服务器。
代理密码
在位置部分,proxy_pass 用于将请求转发到另一个服务。在本例中,我们将请求转发到一个简单的 echo http 服务器。

测试 http 服务器,用于调试
注意:http-echo-server 会在每个请求中添加 2 秒超时。

npm i -g http-echo-server


测试

Original request

GET /api/v1/test HTTP/1.0
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer bearer_token_here
Accept-Language: en-us

Forwarded request

GET /api/v1/test HTTP/1.0
Host: localhost
X-Real-IP: 172.17.0.1
X-Forwarded-For: 172.17.0.1
X-Forwarded-Proto: http
Connection: close
Accept: */*
User-Agent: Rested/2009 CFNetwork/1128.0.1 Darwin/19.6.0 (x86_64)
Accept-Language: en-us
Accept-Encoding: gzip, deflate
X-Real-Name: ewoJImZpcnN0X25hbWUiOiAidXNlciIsCgkidXVpZCI6ICIwYmM5MTMyNS0xNjk3LTRiNmQtYjRiZi01MGYxZmNmZGMzZWIiLAoJInJvbGVzIjogWyJST0xFX1RFU1RfMSIsICJST0xFX1RFU1RfMiJdLAoJImxhc3RfbmFtZSI6ICJ1c2VyIiwKCSJ1c2VybmFtZSI6ICJ1c2VyIiwKCSJlbWFpbCI6ICJ1c2VyQHRlc3QudGVzdCIKfQ==

X-Real-Name header

是一个 base64 编码的 json 对象,其中包含用户信息。

{
“first_name”: “user”,
“uuid”: “0bc91325–1697–4b6d-b4bf-50f1fcfdc3eb”,
“roles”: [“ROLE_TEST_1”, “ROLE_TEST_2”],
“last_name”: “user”,
“username”: “user”,
“email”: “[email protected]
}



今天先到这儿,希望对云原生,技术领导力, 企业管理,系统架构设计与评估,团队管理, 项目管理, 产品管理,信息安全,团队建设 有参考作用 , 您可能感兴趣的文章:
构建创业公司突击小团队
国际化环境下系统架构演化
微服务架构设计
视频直播平台的系统架构演化
微服务与Docker介绍
Docker与CI持续集成/CD
互联网电商购物车架构演变案例
互联网业务场景下消息队列架构
互联网高效研发团队管理演进之一
消息系统架构设计演进
互联网电商搜索架构演化之一
企业信息化与软件工程的迷思
企业项目化管理介绍
软件项目成功之要素
人际沟通风格介绍一
精益IT组织与分享式领导
学习型组织与企业
企业创新文化与等级观念
组织目标与个人目标
初创公司人才招聘与管理
人才公司环境与企业文化
企业文化、团队文化与知识共享
高效能的团队建设
项目管理沟通计划
构建高效的研发与自动化运维
某大型电商云平台实践
互联网数据库架构设计思路
IT基础架构规划方案一(网络系统规划)
餐饮行业解决方案之客户分析流程
餐饮行业解决方案之采购战略制定与实施流程
餐饮行业解决方案之业务设计流程
供应链需求调研CheckList
企业应用之性能实时度量系统演变

如有想了解更多软件设计与架构, 系统IT,企业信息化, 团队管理 资讯,请关注我的微信订阅号:

作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 该文章也同时发布在我的独立博客中-Petter Liu Blog。

热门相关:大爱晚成,卯上天价老婆   外宿:朋友的女人   兵王传说   我的晋升技巧   特工皇后不好惹