Kratos 对接口进行加密转发处理的两个方法

最近在一个 Go Web 项目工作,接到一个需求,对接口进行加密转发,也就是将原本的所有接口的请求路径、请求参数、请求和 Header 和 Body 都进行加密,通过请求 body 统一提交给一个固定的接口,然后在服务端请求解开数据进行逻辑调用,最后将相应结果再加密返回。以增加追踪和分析接口的难度。项目使用的 Web 开发框架是 Kratos。

在不改动原有接口的情况下,实现这类需求一般有两种方法,一种是通过拦截器或者中间件来修改请求信息,第二种是在接口中根据请求数据进行请求转发。下面分别介绍两种方法的实现方式。

第一种,中间件方案。

在 Kratos 中,类似拦截器、过滤器的 Web 组件,被称为中间件或 Middleware,处理请求的逻辑叫 Handler。逻辑关系如下:

vbscript 复制代码
         ┌───────────────────┐
         │MIDDLEWARE 1       │
         │ ┌────────────────┐│
         │ │MIDDLEWARE 2    ││
         │ │ ┌─────────────┐││
         │ │ │MIDDLEWARE 3 │││
         │ │ │ ┌─────────┐ │││
REQUEST  │ │ │ │  YOUR   │ │││  RESPONSE
   ──────┼─┼─┼─▷ HANDLER ○─┼┼┼───▷
         │ │ │ └─────────┘ │││
         │ │ └─────────────┘││
         │ └────────────────┘│
         └───────────────────┘

Kratos 中有很多内置的中间件,这里我们要实现的功能,则需要自定义中间件。我们需要在中间件中完成以下几件事儿:

  1. 对请求的 body 信息进行解密,并解析成 JSON 对象,方便获取其中的信息。
  2. 根据解析出的 JSON 对象,获取要请求的接口的路径、请求参数、请求头(其中包含认证信息,如 jwt)、请求体。
  3. 根据解析出的以上信息,对当前的请求对象进行修改
  4. 进入下一个中间件或者请求处理的逻辑

在 Kratos 中自定义中间件需要一个返回 middleware.Middleware 的函数,它的类型定义如下:

go 复制代码
type Middleware func(Handler) Handler

其中的 Handler 的类型定义如下:

go 复制代码
type Handler func(ctx context.Context, req any) (any, error)

也就是通过参数传入一个 Handler,增加一些中间件的处理逻辑,然后返回一个新的 Handler。要实现需求,需要先定义一个结构体,用来约定前端请求时组织请求信息的结构,比如下面这样:

go 复制代码
type InnerRequest struct {
    Path    string      `json:"path"`
    Method  string      `json:"method"`
    Headers http.Header `json:"headers"`
    Body    string      `json:"body"` // base64 encoded body
}

前端将真实的请求意图,封装成 InnerRequest 结构并加密,通过请求 body 给到服务端,在中间件中,服务端通过解密和解析,得到这些信息,根据这些信息对请求(Request)进行修改。

首先,在中间件中从 Context 中获取到请求对象:

go 复制代码
tr, ok := transport.FromServerContext(ctx)
if !ok {
    // For non-HTTP transports, skip this middleware.
    return handler(ctx, req)
}

httpReq, ok := tr.(kratoshttp.Transporter)
if !ok {
    return handler(ctx, req)
}

然后对原始的请求和上下文进行修改,并调用下一层中间件调用:

go 复制代码
httpReq.Request().Method = innerReq.Method
parsedURL, err := url.Parse(innerReq.Path)
if err != nil {
    return nil, kerrors.BadRequest("INVALID_URL", "failed to parse inner request URL")
}
httpReq.Request().URL.Path = parsedURL.Path
httpReq.Request().URL.RawQuery = parsedURL.RawQuery // URL 参数
httpReq.Request().Body = io.NopCloser(bytes.NewBufferString(innerReq.Body))
// Also copy the headers from the inner request to the actual request
for key, values := range innerReq.Headers {
    httpReq.Request().Header[key] = values
}

其中有一步比较重要,就是 URL 中的请求参数这里需要专门处理一下,否则根据带参数的 URL 查找请求处理逻辑的话会报 404。

最后就是把中间件配置到 HTTPServer 中就可以了。

第二种,接口转发方案

这种方案是配置一个单独的接口,用来接受请求,并在处理时构建新的请求,在内部调用原有接口的请求逻辑。

这类特殊的接口,可以直接根据路径分配一个自定义的 Handler:

go 复制代码
entrypointHandler := &EntryPointHandler{
    kratosServer: srv,
    apiConf:      apiConf,
}
srv.Handle("/api/entrypoint", entrypointHandler)

EntryPointHandler 的定义如下:

go 复制代码
type EntryPointHandler struct {
    kratosServer stdhttp.Handler // The Kratos server itself
    apiConf      *conf.Api
}

其中,kratosServer 是 Kratos 本身的 server,便于处理完请求之后构建新的请求再交给 Kratos 处理后续的逻辑,apiConf 是一些配置信息,如加密的密钥等。EntryPointHandler 需要实现一个接口来处理请求:

go 复制代码
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

这个接口中可以操作 request 和 responseWriter,这样,就能够对请求进行各种自定义处理,到此,剩下的主要逻辑其实跟中间件方案的差不多了,只是这里不是要修改原请求,而是构建新的请求对象,并交给 Kratos 处理。以下是关键部分的代码(删除了错误处理和不重要的逻辑):

go 复制代码
func (h *EntryPointHandler) ServeHTTP(w stdhttp.ResponseWriter, r *stdhttp.Request) {
    // 1. 获取请求内容
    body, err := io.ReadAll(r.Body)
    // 2. 解析请求内容 JSON
    // code
    // 3. 解密
    // code
    // 4. 解析解密后的请求内容
    var innerReq pkgMiddleware.InnerRequest
    // code
    // 5. 封装真实的请求信息
    newReq := r.Clone(r.Context())

    // 请求 URL 和 URL 参数
    parsedURL, err := url.Parse(innerReq.Path)
    newReq.URL.Path = parsedURL.Path
    newReq.URL.RawQuery = parsedURL.RawQuery
    // 请求方法
    newReq.Method = innerReq.Method
    // 请求体
    newReq.Body = io.NopCloser(bytes.NewReader([]byte(innerReq.Body)))
    newReq.ContentLength = int64(len(innerReq.Body))
    // 请求头
    for key, values := range innerReq.Headers {
       if values != nil {
          newReq.Header[key] = values
       }
    }

    // 6. Context
    newReq = newReq.WithContext(ctx)
    // 7. 转发请求
    h.kratosServer.ServeHTTP(w, newReq)
}

最后一步就是把 responseWriter 和新构建的请求 newReq 交给 Kratos 处理了。

返回信息的处理

除了处理请求信息,返回的 response 也需要加密处理,这部分交给 Kratos 的 EncoderResponse 来处理,EncoderResponse 的专门用来处理 Kratos 的返回信息。在 EncoderResponse 中只需要将处理结果对象序列化成 JSON 然后再进行加密,写入 responseWriter 中就可以了,这部分比较简单就补贴代码了。

相关推荐
tachibana2几秒前
与其他语言相比,使用 Go 有什么好处?
开发语言·后端·golang·go
JWASX14 小时前
Java 转 go 学习 - 项目管理
go
喵个咪18 小时前
Go 写业务,Rust 扛底盘:一套可落地的混合架构
后端·rust·go
EatFan18 小时前
Go语言全栈实战:基于 Gin + Vue + JWT + RBAC 从零搭建前后端分离权限管理系统
vue.js·golang·go·vue·gin·jwt·rbac
ZealSinger18 小时前
Go slog生产落地LevelVar与共存
开发语言·后端·golang·go
福兮说20 小时前
Go encoding/json 的八个默认行为,以及 Go 1.25 的 json/v2 改了哪几个(附实测输出)
go
合橱瑰20 小时前
踩坑实录:包是好的,代码却报错?一次“数字ID”引发的插件启动血案
架构·go
NetX行者1 天前
五大主流语言:Go、Python、Rust、Java、C# 的比较
go
Thinker QAQ2 天前
并发编程(六):Atomic 的实现——从 Runtime 到 CPU
java·go·cas·并发编程·atomic·cpython
喵个咪2 天前
GoWind Admin|风行 — 开箱即用的企业级全栈中后台框架:多租户与行级数据隔离
后端·go