func StreamServerInterceptor(authFunc AuthFunc) grpc.StreamServerInterceptor
func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor
type AuthFunc
type AuthFunc func(ctx context.Context) (context.Context, error) 验证方法
客户端请求添加bearer token
实现和上篇的自定义认证方法大同小异。gRPC 中默认定义了 PerRPCCredentials,是提供用于自定义认证的接口,它的作用是将所需的安全认证信息添加到每个RPC方法的上下文中。其包含 2 个方法:
GetRequestMetadata:获取当前请求认证所需的元数据RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输
接下来我们实现这两个方法
// Token token认证type Token struct {Value string}//这里要以authorization为头部,和服务端对应const headerAuthorize string = "authorization"// GetRequestMetadata 获取当前请求认证所需的元数据func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {return map[string]string{headerAuthorize: t.Value}, nil}// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输func (t *Token) RequireTransportSecurity() bool {return true}
发送请求时添加token,注意:Token中的Value的形式要以bearer token值形式。因为我们服务端使用了bearer token验证方式。
//从输入的证书文件中为客户端构造TLS凭证creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")if err != nil {log.Fatalf("Failed to create TLS credentials %v", err)}//构建Tokentoken := auth.Token{Value: "bearer grpc.auth.token",}// 连接服务器conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))
新建grpc_auth服务端拦截器
// TokenInfo 用户信息type TokenInfo struct {ID stringRoles []string}// AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证func AuthInterceptor(ctx context.Context) (context.Context, error) {token, err := grpc_auth.AuthFromMD(ctx, "bearer")if err != nil {return nil, err}tokenInfo, err := parseToken(token)if err != nil {return nil, grpc.Errorf(codes.Unauthenticated, " %v", err)}//使用context.WithValue添加了值后,可以用Value(key)方法获取值newCtx := context.WithValue(ctx, tokenInfo.ID, tokenInfo)//log.Println(newCtx.Value(tokenInfo.ID))return newCtx, nil}//解析token,并进行验证func parseToken(token string) (TokenInfo, error) {var tokenInfo TokenInfoif token == "grpc.auth.token" {tokenInfo.ID = "1"tokenInfo.Roles = []string{"admin"}return tokenInfo, nil}return tokenInfo, errors.New("Token无效: bearer " + token)}//从token中获取用户唯一标识func userClaimFromToken(tokenInfo TokenInfo) string {return tokenInfo.ID}
GetRequestMetadata:获取当前请求认证所需的元数据RequireTransportSecurity:是否需要基于 TLS 认证进行安全传输
接下来我们实现这两个方法
// Token token认证type Token struct {Value string}//这里要以authorization为头部,和服务端对应const headerAuthorize string = "authorization"// GetRequestMetadata 获取当前请求认证所需的元数据func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {return map[string]string{headerAuthorize: t.Value}, nil}// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输func (t *Token) RequireTransportSecurity() bool {return true}
发送请求时添加token,注意:Token中的Value的形式要以bearer token值形式。因为我们服务端使用了bearer token验证方式。
//从输入的证书文件中为客户端构造TLS凭证creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")if err != nil {log.Fatalf("Failed to create TLS credentials %v", err)}//构建Tokentoken := auth.Token{Value: "bearer grpc.auth.token",}// 连接服务器conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(&token))
把grpc_auth拦截器添加到服务端
grpcServer := grpc.NewServer(cred.TLSInterceptor(),grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(grpc_auth.StreamServerInterceptor(auth.AuthInterceptor),grpc_zap.StreamServerInterceptor(zap.ZapInterceptor()),)),grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(grpc_auth.UnaryServerInterceptor(auth.AuthInterceptor),grpc_zap.UnaryServerInterceptor(zap.ZapInterceptor()),)),)
例子:
client
package mainimport ("context""fmt""google.golang.org/grpc/credentials""time""github.com/sirupsen/logrus""google.golang.org/grpc"pb "test/grpcLB/protos")// Token token认证type Token struct {Value string}const headerAuthorize string = "authorization"// GetRequestMetadata 获取当前请求认证所需的元数据func (t *Token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {return map[string]string{headerAuthorize: t.Value}, nil}// RequireTransportSecurity 是否需要基于 TLS 认证进行安全传输func (t *Token) RequireTransportSecurity() bool {return true}func main() {ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()//TLS连接creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "go-grpc-example")if err != nil {panic(err.Error())}//构建Tokentoken := Token{Value: "bearer grpc.auth.toke",}grpc.WithInsecure()// authority是自己随便起的,不是必须的,但是r.Scheme()+"://authority/"+*svc这种格式是必须的conn, err := grpc.DialContext(ctx, "localhost:50000", grpc.WithTransportCredentials(creds),grpc.WithPerRPCCredentials(&token))if err != nil {panic(err)}client := pb.NewGreeterClient(conn)resp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "world "})if err == nil {logrus.Infof("Reply is %s\n", resp.Message)}else {fmt.Println(err)}}
service
package mainimport ("context""fmt""github.com/grpc-ecosystem/go-grpc-middleware""google.golang.org/grpc/codes""google.golang.org/grpc/credentials""google.golang.org/grpc/grpclog""google.golang.org/grpc/status""net""time""google.golang.org/grpc""github.com/sirupsen/logrus""github.com/grpc-ecosystem/go-grpc-middleware/auth"pb "test/grpcLB/protos""errors")const DialTimeout = time.Second*5// TokenInfo 用户信息type TokenInfo struct {ID stringRoles []string}// AuthInterceptor 认证拦截器,对以authorization为头部,形式为`bearer token`的Token进行验证// 返回值 context.Context 会传到具体的servicefunc AuthInterceptor(ctx context.Context) (context.Context, error) {token, err := grpc_auth.AuthFromMD(ctx, "bearer")if err != nil {return ctx, err}tokenInfo, err := parseToken(token)if err != nil {return ctx, status.Errorf(codes.Unauthenticated, " %v", err)}//使用context.WithValue添加了值后,可以用Value(key)方法获取值newCtx := context.WithValue(ctx, "id",tokenInfo.ID)newCtx = context.WithValue(newCtx, "role",tokenInfo.Roles)//log.Println(newCtx.Value(tokenInfo.ID))return newCtx, nil}//解析token,并进行验证func parseToken(token string) (TokenInfo, error) {var tokenInfo TokenInfoif token == "grpc.auth.token" {tokenInfo.ID = "1"tokenInfo.Roles = []string{"admin"}return tokenInfo, nil}return tokenInfo, errors.New("Token无效: bearer " + token)}//从token中获取用户唯一标识func userClaimFromToken(tokenInfo TokenInfo) string {return tokenInfo.ID}func main() {// 端口监听lis, err := net.Listen("tcp", ":50000")if err != nil {panic(err)}defer lis.Close()//TLS认证creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")if err != nil {grpclog.Fatal("加载在证书文件失败", err)}s := grpc.NewServer(grpc.Creds(creds),grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(grpc_auth.StreamServerInterceptor(AuthInterceptor),)),grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(grpc_auth.UnaryServerInterceptor(AuthInterceptor),)),)pb.RegisterGreeterServer(s, &server{})s.Serve(lis)}// server is used to implement helloworld.GreeterServer.type server struct{}// SayHello implements helloworld.GreeterServerfunc (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {fmt.Println(ctx.Value("id"))fmt.Println(ctx.Value("role"))logrus.Infof("%v: Receive is %s\n", time.Now(), in.Name)return &pb.HelloReply{Message: "Hello " + in.Name }, nil}
成功:

失败:
client不携带token,或token值给个错的
