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

相关推荐
zhuyasen3 小时前
当Go框架拥有“大脑”,Sponge框架集成AI开发项目,从“手写”到一键“生成”业务逻辑代码
后端·go·ai编程
chenqianghqu6 小时前
goland编译过程加载dll路径时出现失败
go
马里嗷9 小时前
Go 1.25 标准库更新
后端·go·github
郭京京10 小时前
go语言redis中使用lua脚本
redis·go·lua
心月狐的流火号13 小时前
分布式锁技术详解与Go语言实现
分布式·微服务·go
一个热爱生活的普通人15 小时前
使用 Makefile 和 Docker 简化你的 Go 服务部署流程
后端·go
HyggeBest1 天前
Golang 并发原语 Sync Pool
后端·go
来杯咖啡1 天前
使用 Go 语言别在反向优化 MD5
后端·go
郭京京2 天前
redis基本操作
redis·go