- 在之前的教程中,已经介绍了
grpc
的metadata
机制和拦截器
- 接下来,我们将使用
metadata
机制和拦截器
编写身份验证
示例代码 - 由于身份验证的处理不适合侵入业务逻辑,所以使用
metadata
机制和拦截器
再适合不过
1.protoc文件
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
2.客户端实现
grpc内置了客户端的方法和接口,我们只需实现PerRPCCredentials
接口的两个方法即可
// 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
}
// WithPerRPCCredentials returns a DialOption which sets credentials and places
// auth state on each outbound RPC.
func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.PerRPCCredentials = append(o.copts.PerRPCCredentials, creds)
})
}
2.1接口实现
我们通过customCredential
结构体重写PerRPCCredentials
接口的方法
type customCredential struct{}
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010", //模拟传输的id和key
"appkey": "i am key",
}, nil
}
func (c customCredential) RequireTransportSecurity() bool {
return false//不启用安全传输
}
2.2实现代码
将实现了PerRPCCredentials
接口的customCredential
结构体对象传入grpc.WithPerRPCCredentials
函数,将函数的返回值(也就是grpc.DialOption
对象传入grpc.Dial
函数即可)
package main
import (
"OldPackageTest/grpc_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
)
type customCredential struct{}
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
func (c customCredential) RequireTransportSecurity() bool {
return false
}
func main() {
conn, err := grpc.Dial("127.0.0.1:50051",
grpc.WithInsecure(),
grpc.WithPerRPCCredentials(customCredential{}))
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
3.服务端实现
-
通过
grpc.UnaryInterceptor
函数传入UnaryServerInterceptor
函数实现对象,生成ServerOption
拦截器对象,将拦截器传入grpc.NewServer
即可-
type UnaryServerInterceptor func(ctx context.Context, req any, info *UnaryServerInfo, handler UnaryHandler) (resp any, err error) func UnaryInterceptor(i UnaryServerInterceptor) ServerOption
-
-
通过
metadata.FromIncomingContext(ctx)
函数解析context.Context
,返回值就是map[string]string
类型,即为元数据,可以通过解析获取相应的数据
package main
import (
"context"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"net"
"google.golang.org/grpc"
"OldPackageTest/grpc_test/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("接收到了一个新的请求")
md, ok := metadata.FromIncomingContext(ctx)
fmt.Println(md)
if !ok {
//已经开始接触到grpc的错误处理了
return resp, status.Error(codes.Unauthenticated, "无token认证信息")
}
var (
appid string
appkey string
)
if va1, ok := md["appid"]; ok {
appid = va1[0]
}
if va1, ok := md["appkey"]; ok {
appkey = va1[0]
}
if appid != "101010" || appkey != "i am key" {
return resp, status.Error(codes.Unauthenticated, "无token认证信息")
}
res, err := handler(ctx, req)//调用实际业务处理模块
fmt.Println("请求已经完成")
return res, err
}
opt := grpc.UnaryInterceptor(interceptor)
g := grpc.NewServer(opt)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}