diff --git a/skills/rpc/protobuf/hello_service/client_auther.go b/skills/rpc/protobuf/hello_service/client_auther.go new file mode 100644 index 0000000..e270eca --- /dev/null +++ b/skills/rpc/protobuf/hello_service/client_auther.go @@ -0,0 +1,56 @@ +package hello_service + +import context "context" + +// PerRPCCredentials defines the common interface for the credentials which need to +// attach security information to every RPC (e.g., oauth2). +// type PerRPCCredentials interface { +// // GetRequestMetadata gets the current request metadata, refreshing tokens +// // if required. This should be called by the transport layer on each +// // request, and the data should be populated in headers or other +// // context. If a status code is returned, it will be used as the status for +// // the RPC (restricted to an allowable set of codes as defined by gRFC +// // A54). uri is the URI of the entry point for the request. When supported +// // by the underlying implementation, ctx can be used for timeout and +// // cancellation. Additionally, RequestInfo data will be available via ctx +// // to this call. TODO(zhaoq): Define the set of the qualified keys instead +// // of leaving it as an arbitrary string. +// GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) +// // RequireTransportSecurity indicates whether the credentials requires +// // transport security. +// RequireTransportSecurity() bool +// } + +func NewClientAuthentication(clientId, clientSecret string) *Authentication { + return &Authentication{ + clientID: clientId, + clientSecret: clientSecret, + } +} + +// Authentication todo +type Authentication struct { + clientID string + clientSecret string +} + +// WithClientCredentials todo +func (a *Authentication) WithClientCredentials(clientID, clientSecret string) { + a.clientID = clientID + a.clientSecret = clientSecret +} + +// GetRequestMetadata todo +func (a *Authentication) GetRequestMetadata(context.Context, ...string) ( + map[string]string, error, +) { + return map[string]string{ + ClientHeaderKey: a.clientID, + ClientSecretKey: a.clientSecret, + }, nil +} + +// RequireTransportSecurity todo +func (a *Authentication) RequireTransportSecurity() bool { + return false +} diff --git a/skills/rpc/protobuf/hello_service/middleware.go b/skills/rpc/protobuf/hello_service/middleware.go new file mode 100644 index 0000000..4e77de7 --- /dev/null +++ b/skills/rpc/protobuf/hello_service/middleware.go @@ -0,0 +1,90 @@ +package hello_service + +import ( + context "context" + "fmt" + + "github.com/infraboard/mcube/v2/ioc/config/log" + "github.com/rs/zerolog" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + status "google.golang.org/grpc/status" +) + +const ( + ClientHeaderKey = "client-id" + ClientSecretKey = "client-secret" +) + +// GrpcAuthUnaryServerInterceptor returns a new unary server interceptor for auth. +func GrpcAuthUnaryServerInterceptor() grpc.UnaryServerInterceptor { + return newGrpcAuther().Auth +} + +func newGrpcAuther() *grpcAuther { + return &grpcAuther{ + log: log.Sub("Grpc Auther"), + } +} + +// UnaryServerInterceptor provides a hook to intercept the execution of a unary RPC on the server. info +// contains all the information of this RPC the interceptor can operate on. And handler is the wrapper +// of the service method implementation. It is the responsibility of the interceptor to invoke handler +// to complete the RPC. +// type UnaryServerInterceptor func(ctx context.Context, req any, info *UnaryServerInfo, handler UnaryHandler) (resp any, err error) +// internal todo +type grpcAuther struct { + log *zerolog.Logger +} + +func (a *grpcAuther) Auth( + ctx context.Context, req any, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, +) (resp any, err error) { + // http2 header -> metadata + // 重上下文中获取认证信息 + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, fmt.Errorf("ctx is not an grpc incoming context") + } + + fmt.Println("gprc header info: ", md) + + clientId, clientSecret := a.GetClientCredentialsFromMeta(md) + + // 校验调用的客户端凭证是否有效 + if err := a.validateServiceCredential(clientId, clientSecret); err != nil { + return nil, err + } + + // 把请求交给后续的Handler处理 + resp, err = handler(ctx, req) + return resp, err +} + +func (a *grpcAuther) GetClientCredentialsFromMeta(md metadata.MD) ( + clientId, clientSecret string) { + cids := md.Get(ClientHeaderKey) + sids := md.Get(ClientSecretKey) + if len(cids) > 0 { + clientId = cids[0] + } + if len(sids) > 0 { + clientSecret = sids[0] + } + return +} + +func (a *grpcAuther) validateServiceCredential(clientId, clientSecret string) error { + if clientId == "" && clientSecret == "" { + return status.Errorf(codes.Unauthenticated, "client_id or client_secret is \"\"") + } + + if !(clientId == "admin" && clientSecret == "123456") { + return status.Errorf(codes.Unauthenticated, "client_id or client_secret invalidate") + } + + return nil +} diff --git a/skills/rpc/protobuf/service_a/main.go b/skills/rpc/protobuf/service_a/main.go index 6ac3dff..e76e9ea 100644 --- a/skills/rpc/protobuf/service_a/main.go +++ b/skills/rpc/protobuf/service_a/main.go @@ -12,7 +12,8 @@ import ( func main() { // 首先是通过grpc.NewServer()构造一个gRPC服务对象 - grpcServer := grpc.NewServer() + // 补充了全局的认证中间件 + grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(hello_service.GrpcAuthUnaryServerInterceptor())) // SDK 提供 服务实现对象的注册 hello_service.RegisterHelloServiceServer(grpcServer, &HelloService{}) diff --git a/skills/rpc/protobuf/service_b/main.go b/skills/rpc/protobuf/service_b/main.go index 6c7cb49..639ec6e 100644 --- a/skills/rpc/protobuf/service_b/main.go +++ b/skills/rpc/protobuf/service_b/main.go @@ -14,7 +14,12 @@ import ( func main() { // grpc.Dial负责和gRPC服务建立链接 - conn, err := grpc.NewClient("localhost:1234", grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.NewClient("localhost:1234", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithPerRPCCredentials(hello_service.NewClientAuthentication( + "admin", + "123456", + ))) if err != nil { log.Fatal(err) }