🐕

Go言語でgRPCに入門してみた2 認証処理を追加してみた

2020/09/23に公開

以前の記事で、gRPCでclientもserverもGoで開発し、超簡単なメッセージ送信受信を実装し、gRPCの感触を掴みました。
その時はこちら。
以前の記事

この記事では、上記の記事にさらに手を加えて通信時に簡単な認証処理を加えてみます。
具体的には以下のようなことをやります。

  • クライアント側では通信時にメタデータに認証トークンを載せます
  • サーバ側では通信を受けた際にメタデータの認証トークンが妥当かどうか検証します
  • 認証トークンは"testtoken"とします。Bearerトークンと同じようにprefixとしてBearerを付けます

ちなみにメタデータというのは通信メッセージとは別に送信されるデータでHTTPヘッダーみたいなものなのかと理解しています。

クライアント側の処理

Bearerトークンを設定したメタデータを送信contextに追加したら完了です。
あとはこのcontextを使用して送信処理を行えば、メッセージと一緒にメタデータも送信されます。

// Bearer tokenを設定
md := metadata.New(map[string]string{"authorization": "Bearer testtoken"})
ctx := metadata.NewOutgoingContext(context.Background(), md)

こちらはクライアント処理の全コードとなります。

package main

import (
	"context"
	"fmt"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	hello "test/hello"
)

func main() {
	con, err := grpc.Dial("127.0.0.1:19003", grpc.WithInsecure())
	if err != nil {
		log.Fatal("client connection error:", err)
	}
	defer con.Close()

  // Bearer tokenを設定
	md := metadata.New(map[string]string{"authorization": "Bearer testtoken"})
	ctx := metadata.NewOutgoingContext(context.Background(), md)

	client := hello.NewHelloClient(con)
	message := &hello.HelloMessage{Name: "world"}
	res, err := client.Hello(ctx, message)
	if err != nil {
		log.Fatal(err)
		return
	}
	fmt.Printf("%s\n", res.Msg)
}

サーバー側の処理

実はgrpc_authというインターセプタがすでに用意されており、これを利用すれば認証処理を簡単に実装できます。
インターセプタというのは処理の前後に差し挟むことができる処理のことで、今回であれば、認証用の処理を差し挟みます。

まずは下のようなパッケージをimportします。

import (
 …
 grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth")

次はgRPCサーバがメッセージを受信する度に処理するインターセプタを指定します。

authInterceptor := grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(authenticate))
server := grpc.NewServer(authInterceptor)

最後に実際の認証処理の中身を実装します。
受け取ったメタデータ内のトークンが"testtoken"かどうか、この処理内で判定します。

func authenticate(ctx context.Context) (context.Context, error) {
	token, err := grpc_auth.AuthFromMD(ctx, "Bearer")
	if err != nil {
		return nil, err
	}
	if token != "testtoken" {
		return nil, errors.New("unauthorized")
	}
	return ctx, nil
}

下はサーバー側の全コードです。

package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"test/hello"

	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
	"google.golang.org/grpc"
)

func main() {
	listenPort, err := net.Listen("tcp", ":19003")
	if err != nil {
		log.Fatal(err)
	}
	authInterceptor := grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(authenticate))
	server := grpc.NewServer(authInterceptor)
	hello.RegisterHelloServer(server, &Hello{})
	server.Serve(listenPort)
}

type Hello struct{}

func (h *Hello) Hello(cts context.Context, message *hello.HelloMessage) (*hello.HelloResponse, error) {
	res := hello.HelloResponse{Msg: fmt.Sprintf("hello %s\n", message.Name)}
	return &res, nil
}

func authenticate(ctx context.Context) (context.Context, error) {
	token, err := grpc_auth.AuthFromMD(ctx, "Bearer")
	if err != nil {
		return nil, err
	}
	if token != "testtoken" {
		return nil, errors.New("unauthorized")
	}
	return ctx, nil
}

参考

https://note.com/keyem/n/n52b755137c3b
https://stackoverflow.com/questions/49243846/how-to-send-grpc-meta-data-from-client-side

Discussion