记录第一次使用golang开发kong网关插件

[TOC]

记录第一次使用golang开发kong网关插件

1.安装kong网关

安装过程

2.开始开发

2.1 下载kong官方的PDK库

go get github.com/Kong/go-pdk

2.2 编写逻辑代码

2.2.1 定义一个Sturct用于储存配置

type Config struct {
	Message string
}

2.2.2 定义插件版本、执行优先级

const Version = "1.0.0"
const Priority = 1

2.2.3 定义构造函数,返回Config对象

func New() interface{} {
	return &Config{}
}

2.2.4 开始写自定义逻辑

必须在以下几个阶段定义

  • Certificate
  • Rewrite
  • Access
  • Response
  • Preread
  • Log

比如在响应返回阶段定义自定义逻辑:

func (c Config) Response(kong *pdk.PDK) {
	//开始编写逻辑
}

******函数签名中的函数名必须以阶段名命名,否则Kong无法识别

PDK方法文档在go储存库里:PDK文档

2.2.4 编写启动函数

func main() {
	err := server.StartServer(New, Version, Priority)
	if err != nil {
		return
	}
}

2.2.5 开始编译

cd [进入插件项目根目录]

go build -buildmode=plugin main.go

以插件形式编译允许kong网关动态加载该插件

编译成功会输出二进制文件main.so

3. 加载插件

3.1 创建插件存放目录

mkdir /etc/kong-plugins //可以自定义位置和目录名

3.2 进入Kong网关容器

或者提前将对应的容器目录映射到主机就不用进入容器了

docker exec -it [容器名/容器ID] /bin/bash

3.3 注册插件

编辑 /usr/local/share/lua/5.1/kong/constants.lua

nano /usr/local/share/lua/5.1/kong/constants.lua

找到 local plugins 字段,写入插件名

local plugins = [
	//添加插件名
	"xxxx",
	"xxxx",
	"xxxx",
]

然后编辑Kong配置文件

cd /etc/kong/

mv kong.conf.default kong.conf

nano kong.conf

pluginserver_names = set-token //插件名称,对应go.mod中的module,否则无法识别插件

pluginserver_set_token_socket = /usr/local/kong/set-token.socket //插件启动时会在此目录创建一个sock连接
pluginserver_set_token_start_cmd = /etc/kong-plugins/set-token //插件位置
pluginserver_set_token_query_cmd = /etc/kong-plugins/set-token -dump //插件位置

3.4 重载生效配置

将编译好的main.so插件重命名set-token复制到上面创建的插件目录

然后执行

kong prepare && kong reload

3.5 验证插件加载状态

查看kong网关日志

出现 [kong] process.lua:223 Starting set-token, context: ngx.timer 表示插件加载成功

4.启用插件

使用 Kong Admin API 启用插件 或使用**Kong Manager**可视化控制面板开启插件

//使用Kong Admin API
//这里没有定义Service,Route,Consumer范围,全局生效
curl -X POST http://172.28.14.226:8001/plugins/ \
      --data "name=set-token" \
      --data "config.message=set-token" 

成功启用会输出一下json信息

{"protocols":["grpc","grpcs","http","https"],"id":"d5336e66-1ff3-47ea-9b2a-31f8b52c9b05","consumer":null,"instance_name":null,"config":{"message":"set-token"},"tags":null,"updated_at":1717924637,"name":"set-token","ordering":null,"service":null,"enabled":true,"route":null,"created_at":1717924637}

5. 测试插件

因为这个插件功能是把token信息通过响应头发送给客户端自动设置cookie,所以使用ApiPost向登录服务发起一次请求

![image-20240609172249432](C:\Users\FOB_B\AppData\Roaming\Typora\typora-user-images\image-20240609172249432.png

image-20240609172339710

image-20240609172423256

响应头成功添加授权cookie信息

记录此插件完整代码:

package main

import (
	"encoding/json"
	"fmt"
	"github.com/Kong/go-pdk"
	"github.com/Kong/go-pdk/server"
	"strconv"
)

type Config struct {
	Message string
}

type LoginServiceRes struct {
	AccessToken     string `json:"AccessToken"`
	AccessTokenExp  int    `json:"AccessTokenExp"`
	RefreshToken    string `json:"RefreshToken"`
	RefreshTokenExp int    `json:"RefreshTokenExp"`
}

type LoginServiceTarsRet struct {
	Code    int    `json:"Code"`
	Msg     string `json:"Msg"`
	Status  int    `json:"Status"`
	Desc    string `json:"Desc"`
	TraceID string `json:"TraceID"`
}

type ServiceLoginBody struct {
	Response LoginServiceRes     `json:"response"`
	TarsRet  LoginServiceTarsRet `json:"tars_ret"`
}

const Version = "1.0.0"
const Priority = 1

func New() interface{} {
	return &Config{}
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	err := server.StartServer(New, Version, Priority)
	if err != nil {
		return
	}
}

func (c Config) Response(kong *pdk.PDK) {
	defer func() {
		if err := recover(); err != nil {
			kong.Log.Err(err)
		}
	}()
	res, err := kong.ServiceResponse.GetRawBody()
	if err != nil {
		return
	}
	SLR := ServiceLoginBody{}
	err = json.Unmarshal(res, &SLR)
	if err != nil {
		kong.Log.Err(err.Error())
		return
	}
	if SLR.Response.AccessToken == "" || SLR.Response.RefreshToken == "" {
		kong.Log.Err("获取授权信息失败,AccessToken或RefreshToken为空")
		return
	}
	if SLR.TarsRet.TraceID == "" {
		kong.Log.Warn("警告:tars服务没有返回traceid!")
	}
	if err = kong.Response.SetHeader("Set-Cookie", "Authorization="+SLR.Response.AccessToken+"; Path=/; Max-Age="+strconv.Itoa(SLR.Response.AccessTokenExp)+"; HttpOnly"); err != nil {
		kong.Log.Err("SetToken插件设置响应头失败")
		return
	}
	if err = kong.Response.AddHeader("Set-Cookie", "RefreshToken="+SLR.Response.RefreshToken+"; Path=/; Max-Age="+strconv.Itoa(SLR.Response.RefreshTokenExp)+"; HttpOnly"); err != nil {
		kong.Log.Err("SetToken插件追加响应头失败")
		return
	}
}