验证请求

在本节以及接下来的部分,我们将简化当前的中间件。首先,我们将从简化身份验证过程开始。我们在上一章中看到,我们可以轻松地为检查请求头中的身份验证令牌创建拦截器。在本节中,我们将更进一步,使其变得更简单。

gRPC 支持通过 RBAC 策略重试请求的身份验证,而无需第三方库。然而,配置相当冗长且文档不太完善。如果你有兴趣尝试,可以查看以下示例: gRPC RBAC 示例

之前,当我们编写拦截器时,我们需要为单次拦截器创建如下的函数:

func unaryAuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error)

以及为流式拦截器创建类似如下的函数:

func streamAuthInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error

虽然这给我们提供了很多关于调用、上下文等信息,但这也使得代码变得简洁,并且我们需要考虑如何在流式拦截器和单次拦截器之间共享公共业务逻辑。

通过我们将要添加的中间件,我们将能够专注于我们的逻辑,并像以前一样轻松地注册拦截器。这个中间件是来自 GitHub 仓库 go-grpc-middleware 的 auth 中间件( go-grpc-middleware )。它将让我们摆脱为拦截器编写复杂身份验证函数定义的烦恼,并允许我们通过预定义的拦截器直接注册我们的 validateAuthToken 函数。

为了开始,我们将在 server 文件夹中获取依赖项:

$ go get github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth

然后,我们将在 server/interceptors.go 文件中移除 unaryAuthInterceptorstreamAuthInterceptor,因为新的身份验证中间件将为我们处理所有内容。

最后,我们将前往 server/main.go,在这里我们将用 auth.UnaryServerInterceptorauth.StreamServerInterceptor 替换旧的拦截器。这两个拦截器需要一个 AuthFunc,它基本上代表了身份验证逻辑。在我们的例子中,我们将传递给它们我们的 validateAuthToken

AuthFunc 类型如下所示:

type AuthFunc func(ctx context.Context) (context.Context, error)

因此,我们需要稍微修改 validateAuthToken 函数,以便返回一个上下文和/或错误。我们的新函数将如下所示:

func validateAuthToken(ctx context.Context) (context.Context, error) {
    md, ok := metadata.FromIncomingContext(ctx)

    if !ok {
        return nil, status.Errorf(/*...*/)
    }

    if t, ok := md["auth_token"]; ok {
        switch {
        case len(t) != 1:
            return nil, status.Errorf(/*...*/)
        case t[0] != "authd":
            return nil, status.Errorf(/*...*/)
        }
    } else {
        return nil, status.Errorf(/*...*/)
    }

    return ctx, nil
}

这让我们能够在 gRPC 服务器中注册 validateAuthToken。我们的新 main 函数将如下所示:

import (
    //...
    "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
)

func main() {
    //...
    opts := []grpc.ServerOption{
        //...
        grpc.ChainUnaryInterceptor(
            auth.UnaryServerInterceptor(validateAuthToken),
            unaryLogInterceptor),
        grpc.ChainStreamInterceptor(
            auth.StreamServerInterceptor(validateAuthToken),
            streamLogInterceptor),
    }
    //...
}

现在,我们应该能够运行服务器:

$ go run ./server 0.0.0.0:50051
listening at 0.0.0.0:50051

然后运行客户端:

$ go run ./client 0.0.0.0:50051

我们没有遇到任何错误,程序也能像以前一样工作。然而,为了测试中间件是否正常工作,我们可以暂时修改客户端拦截器中的身份验证头部(client/interceptors.go):

const authTokenValue string = "notauthd"

如果我们重新运行客户端,应该会得到以下错误:

$ go run ./client 0.0.0.0:50051
--------ADD--------
rpc error: code = Unauthenticated desc = incorrect auth_token

这证明了我们的中间件按预期工作,并且我们可以依赖 validateAuthToken 来进行身份验证检查。

Bazel

为了使用 Bazel 运行该程序,我们需要更新我们的依赖并将新的依赖链接到 server/BUILD.bazel 中的 server_lib 目标。因此,我们首先运行 gazelle-updaterepos 命令,这将获取 go-grpc-middleware 依赖:

$ bazel run //:gazelle-update-repos

完成后,我们现在可以让 gazelle 命令将 go-grpc-middleware 依赖包括到目标中:

$ bazel run //:gazelle

最后,我们将能够运行我们的服务器:

$ bazel run //server:server 0.0.0.0:50051
listening at 0.0.0.0:50051

如果客户端使用错误的身份验证令牌,应该会显示以下消息:

$ bazel run //client:client 0.0.0.0:50051
--------ADD--------
rpc error: code = Unauthenticated desc = incorrect auth_token

总结来说,在本节中,我们看到通过使用 go-grpc-middleware 包,我们可以简化身份验证拦截器。它让我们专注于实际的业务逻辑,而不必担心如何编写可以与 gRPC 注册的拦截器。