LoginSignup
7
6

More than 3 years have passed since last update.

gRPC で認証処理を実装する

Last updated at Posted at 2020-03-29

grpc で認証処理を実装する

  • interceptorを使って実装すると、良さそう
    • interceptor 自体は middleware 的な立ち回りをしてくれる

実装

  • grpc-ecosystem にライブラリが用意されているのでそれを使っていく
    • go-grpc-middleware/auth
    • 基本的には Client に PW を付与して、それをリクエスト時に投げてもらい、interceptor 内でチェックします
    • 実際に書くのは以下のようなコード

grpcServer = grpc.NewServer(
            grpc_middleware.WithUnaryServerChain(
                grpcMetrics.UnaryServerInterceptor(),
                grpc_recovery.UnaryServerInterceptor(opts...),
                                // ここでChain
                grpc_auth.UnaryServerInterceptor(AuthFunc),
            ),
        )

// AuthFunc は go-grpc-middleware/auth で使用する認証用の関数
// この関数自体は別のパッケージに切り出しても良い
func AuthFunc(ctx context.Context) (context.Context, error) {
    // ここでClientから投げられた認証ヘッダの値を取得
    key, err := grpc_auth.AuthFromMD(ctx, "bearer")
    if err != nil {
        return nil, err
    }

    // ここでは"password"ハードコーディングだけど、この手前でDBアクセスしたりして任意のパスを取得したりしても良い
    if key != "password" {
        return nil, grpc.Errorf(codes.Unauthenticated, "invalid api password")
    }

    // context に Client の情報を詰めて返す
    newCtx := context.WithValue(ctx, "result", "ok")
    return newCtx, nil
}
  • grpc_auth.UnaryServerInterceptor(AuthFunc)に渡す AuthFunc は以下の型を返す関数なら中身はどうでもいいっぽい
  • AuthFuncの中で、やりたいように認証処理を実装していく
    • AuthFromMDは指定した文字列をキーにリクエスト認証ヘッダからバリューを取得してくれる
      • MD は MetaData かと思われる
    • 認証ヘッダを使わない場合は、普通にhttpリクエストヘッダを使ってバリューを取得する
    • よければ context を返し、ダメならエラーを返すとライブラリ側でうまいことUnaryServerInterceptorかエラーを返してくれる
github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.go
func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
        var newCtx context.Context
        var err error
        if overrideSrv, ok := info.Server.(ServiceAuthFuncOverride); ok {
            newCtx, err = overrideSrv.AuthFuncOverride(ctx, info.FullMethod)
        } else {
            newCtx, err = authFunc(ctx)
        }
        if err != nil {
            return nil, err
        }
        return handler(newCtx, req)
    }
}

使ってみる

  • grpcurl してみて、ちゃんと認証処理が挟まるかチェック
    • grpcurl -plaintext -import-path . -proto proto/hello.proto -d '{"name": "hoge"}' -H "Authorization: bearer password" localhost:5000 hello.HelloService/Hello

所感

  • grpc.UnaryServerInterceptor を返す部分と、認証ヘッダからバリューを取得する処理を自前で書かなくていい分楽かな
  • とはいえ全部自分で実装してもそんなに手間でもない気がする

特定メソッドだけ認証をスキップしたい

  • 例えばヘルスチェックは認証をスキップしたい、みたいな場合はServiceAuthFuncOverrideが使える
    • 以下の interface の実装が grpc server に実装されていれば、AuthFuncより優先的にこちらがコールされる(オーバーライドと書いてあるが、実際には実装があるか否かの分岐が入っているっぽい)

type ServiceAuthFuncOverride interface {
    AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error)
}
  • fullMethodNameが grpc のサービスとメソッドの情報を持っているので、少なくともメソッドの粒度で何かしらの分岐が可能

func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
    // HealthCheck がコールされた場合は認証のチェックを skip する
    if fullMethodName == "/hoge_proto.HogeService/HealthCheck" {
        return ctx, nil
    }

    // 認証チェック処理
    // ~~省略~~

    return ctx, nil
    }
7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6