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 中就可以了,这部分比较简单就补贴代码了。

相关推荐
用户40158233246213 小时前
对话一长,AI 就忘了你说过的话?Claude Code、Codex、Aider、WES Code 怎么压缩上下文
人工智能·go·ai编程
我的div丢了肿么办17 小时前
go语言抽离路由文件以及对路由进行分组
后端·go
孔令飞18 小时前
【OneX】OneX AI 工具安装、配置及功能介绍
云原生·go·ai编程
小满zs20 小时前
Go语言第十四章(文件操作)
后端·go
vipxieliang21 小时前
Go 1.24 工具链改进实战:工具依赖管理与 go vet 新分析器
后端·go
程序员爱钓鱼1 天前
Go 编程实战:Benchmark——基准测试、testing.B、benchmem、性能对比与代码优化实战
go·排序算法
Thinker QAQ2 天前
并发编程(十一):Shared Memory 与 Message Passing——关系与组合使用
java·python·go·并发编程·shared memory·message passing
喵个咪2 天前
GoWind Kanban 拆解:看板拖拽背后——LexoRank、乐观锁与尽力而为广播
后端·架构·go
喵个咪2 天前
GoWind OA 拆解:用 3000 行 Go 写一个够用的审批工作流引擎
后端·架构·go
喵个咪2 天前
GoWind Ledger 拆解:单式记账的余额一致性——confirm、补偿,以及没进事务的那些缝隙
后端·架构·go