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

相关推荐
程序员爱钓鱼18 小时前
Go语言实战案例 — 项目实战篇:简易博客系统(支持评论)
前端·后端·go
郭京京1 天前
go框架gin(中)
后端·go
郭京京1 天前
go框架gin(下)
后端·go
一直_在路上1 天前
Go 语言微服务演进路径:从小型项目到企业级架构
架构·go
程序员爱钓鱼2 天前
Go语言实战案例 — 项目实战篇:任务待办清单 Web 应用
后端·google·go
lypzcgf2 天前
Coze源码分析-资源库-创建知识库-后端源码-应用/领域/数据访问
后端·go·coze·coze源码分析·智能体平台·ai应用平台·agent平台
lypzcgf3 天前
Coze源码分析-资源库-创建知识库-基础设施/存储/安全
安全·go·coze·coze源码分析·智能体平台·ai应用平台·agent开发平台
程序员爱钓鱼3 天前
Go语言实战案例 — 项目实战篇:图书管理系统(文件存储)
后端·google·go
郭京京3 天前
goweb原生实现HTTP文件上传功能
go
郭京京3 天前
goweb中间件
go