微服务网关(Microservices Gateway)是微服务架构中的一种关键组件,它作为一个入口点,接收客户端的请求并将其路由到相应的微服务上。它起到了前端与后端微服务之间的“门户”的作用,协调整个微服务系统的请求流量和服务访问。具备的功能如下:
微服务网关简化了客户端与后端微服务之间的交互过程,减少了客户端需要处理的逻辑,并且提供了一层中间件,可以更好地管理和维护整个微服务系统。常见的微服务网关有Nginx、Spring Cloud Gateway、Kong、INgress 、Istio等。
正因为微服务API网关地位位如此重要,所以它一直处于兵家必争之地,传统的 IT 巨头在这个领域很早就都有布局。根据 2018 年 Gartner 发布的 API 全生命周期报告,谷歌、CA、IBM、红帽、Salesforce 都是处于领导地位的厂商,开发者更熟悉的 Kong 则处于远见者的区间内。
那么,问题就来了,为什么我们还要新造一个轮子呢?简单来说,这是因为当前的微服务 API 网关都不足以满足我们的需求。我们首先来看闭源的商业产品,它们的功能都很完善,覆盖了 API 的设计、多语言 SDK、文档、测试和发布等全生命周期管理,并且提供 SaaS 服务,有些还与公有云做了集成,使用起来非常方便。但同时,它们也带来了两个痛点。
这也是为什么开源的 API 网关方案开始流行的一个原因。不过,现有的开源产品也不是万能的,自身也有很多不足。
OpenResty(开放式网络架构)是一个基于Nginx和LuaJIT的开源Web应用服务器,它将Nginx与Lua脚本语言的强大功能相结合,为开发者提供了一种高性能、可扩展、灵活的方式来构建Web应用和微服务。OpenResty利用Nginx作为服务器和代理,通过嵌入Lua脚本引擎(LuaJIT)来扩展Nginx的功能。Lua是一种轻量级的脚本语言,具有简洁的语法和强大的扩展性,使得OpenResty能够实现更复杂的逻辑和定制化的功能,而不需要修改Nginx的源代码。主要特点和优势:
OpenResty常用于构建高性能的Web应用、API服务和微服务网关。它可以代替传统的Web服务器,同时提供更多的灵活性和自定义功能。在大规模应用和高并发环境下,OpenResty展现出其强大的优势,被广泛应用于互联网公司、在线游戏、视频流媒体等领域。
言归正传,现在我们采用基于Openresty+Lua自己实现一个具备token解析认证功能的微服务Api Gateway(在Openresty中实现token解析认证,如果token无效直接拒绝)。带大家熟悉一下Openresty+Lua开发过程,流程图如下:
流程说明:
package main
import (
"fmt"
jwt "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
const secretKey = "Dav7kfq3iA8S!JUj8&CUkdnQe72E@Cw6" // Replace this with a strong secret key
// User struct represents a user.
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}
var users = []User{
{1, "kubesre", "123456"},
}
func GenerateToken(user User) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": user.ID,
"username": user.Username,
"exp": time.Now().Add(time.Hour * 2).Unix(), // Token expires in 2 hours
})
return token.SignedString([]byte(secretKey))
}
func LoginHandler(c *gin.Context) {
var input struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
return
}
for _, user := range users {
if user.Username == input.Username && user.Password == input.Password {
token, err := GenerateToken(user)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
return
}
}
c.JSON(http.StatusUnauthorized, gin.H{"error": "登陆失败,请确认账号密码"})
}
func UserInfo(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "云原生运维圈!"})
}
func main() {
r := gin.Default()
// Route to handle user login
r.POST("/login", LoginHandler)
// Routes protected by JWT authentication middleware
// You need to include the JWT token in the "Authorization" header for these routes
r.GET("/user/info", UserInfo)
fmt.Println("Server started at http://localhost:8080")
r.Run(":8080")
}
运行服务
$ go mod tidy
$ go run main.go
# 安装仓库管理工具包:
$ yum install yum-utils
# 添加仓库地址:
$ yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
# 安装resty:
$ yum install openresty-resty
# 安装opm:
$ yum install openresty-opm
# 安装Jwt组件:
$opm get SkyLothar/lua-resty-jwt
此时lua-resty-jwt安装好了,可以直接使用了。
Auth-JWT插件:
$ cat /usr/local/openresty/lualib/resty/jwt.lua;
local auth_token = ngx.var.http_token
--引入json库
local secret= "Dav7kfq3iA8S!JUj8&CUkdnQe72E@Cw6"
local cjson = require "cjson"
local jwt = require("resty.jwt")
--ngx.say(auth_token)
if auth_token == nil then
local response = {}
response["code"]=401
response["message"]="口令不存在"
ngx.say(cjson.encode(response))
ngx.exit(response.code)
else
local jwt_obj = jwt:verify(secret, auth_token)
if jwt_obj.verified == false then
local response = {}
response["code"]=401
response["message"]="令牌无效"
ngx.say(cjson.encode(response))
ngx.exit(response.code)
else
ngx.exec('@user')
end
end
配置Openresty:
$ cat /usr/local/openresty/nginx/conf/nginx.conf
...
# 需要认证
location /user/info {
# 引入jwt.lua扩展
content_by_lua_file /usr/local/openresty/lualib/resty/jwt.lua;
}
# 不需要认证
location /login {
proxy_pass http://192.168.40.125:8080;
}
location @user {
proxy_pass http://192.168.40.125:8080;
}
...
重新加载Openresty生效:
$ /usr/local/openresty/bin/openresty -t
nginx: the configuration file /usr/local/openresty/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty/nginx/conf/nginx.conf test is successful
$ /usr/local/openresty/bin/openresty -s reload
用户登陆成功,返回token(输出账户密码登陆并返回Token):
如果不传入token,请求192.168.1.102/user/info(会返回口令不存在):
如果传入错误token,请求192.168.1.102/user/info(会返回口令无效):
传入正确的token,请求192.168.1.102/user/info(正常返回内容)
本文介绍了微服务Gateway Api以及我们为什么要造轮子等问题,并通过openresty+lua实现了JWT解析认证功能的小案例,并对openresty扩展功能有了一定了解,接下来文章内容中会分享更多企业级实战案例,请敬请期待!