1、自定义 grpc interceptor
因UnaryServerInterceptor 实为函数类型,因此只实现此函数即可。
// 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 interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
const (
authorizationHeader = "authorization"
bearerPrefix = "Bearer "
)
//Interceptor creates a grpc auth interceptor.
func Interceptor(publicKeyFile string) (grpc.UnaryServerInterceptor, error) {
f, err := os.Open(publicKeyFile)
if err != nil {
return nil, fmt.Errorf("cannot open public key file: %v", err)
}
b, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("cannot read public key file:%v", err)
}
pubKey, err := jwt.ParseRSAPublicKeyFromPEM(b)
if err != nil {
return nil, fmt.Errorf("cannot parse public key: %v", err)
}
i := &interceptor{
publicKey: pubKey,
verifier: &token.JWTTokenVerifier{
PublicKey: pubKey,
},
}
return i.HandleReq, nil
}
// 设计为不与jwt绑定
type tokenVerifier interface {
Verify(token string) (string, error)
}
//自定义拦截器
type interceptor struct {
publicKey *rsa.PublicKey //公钥
verifier tokenVerifier // Token验证器
}
func (i *interceptor) HandleReq(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
tkn, err := tokenFromContext(ctx)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "")
}
aid, err := i.verifier.Verify(tkn)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "token not valid: %v", err)
}
return handler(ContextWithAccountID(ctx, AccountID(aid)), req)
}
func tokenFromContext(ctx context.Context) (string, error) {
unauthenticated := status.Error(codes.Unauthenticated, "")
m, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", unauthenticated
}
tkn := ""
for _, v := range m[authorizationHeader] {
if strings.HasPrefix(v, bearerPrefix) {
tkn = v[len(bearerPrefix):]
}
}
if tkn == "" {
return "", unauthenticated
}
return tkn, nil
}
type accountIDKey struct{}
// AccountID defines account id object.
type AccountID string
func (a AccountID) String() string {
return string(a)
}
// ContextWithAccountID creates a context with given account id.
func ContextWithAccountID(c context.Context, aid AccountID) context.Context {
return context.WithValue(c, accountIDKey{}, aid)
}
// AccountIDFromContext gets account id from context.
// Returns unauthenticated error if no account id is available.
func AccountIDFromContext(c context.Context) (AccountID, error) {
v := c.Value((accountIDKey{}))
aid, ok := v.(AccountID)
if !ok {
return "", status.Error(codes.Unauthenticated, "")
}
return aid, nil
}
2、通用GRPC Server注册方法
//GRPCConfig defines a grpc server
type GRPCConfig struct {
Name string
Addr string
AuthPublicKeyFile string
RegisterFunc func(*grpc.Server)
Logger *zap.Logger
}
// RunGRPCServer runs a grpc server
func RunGRPCServer(c *GRPCConfig) error {
nameField := zap.String("name", c.Name)
lis, err := net.Listen("tcp", c.Addr)
if err != nil {
c.Logger.Fatal("cannot listen", nameField, zap.Error(err))
}
var opts []grpc.ServerOption
if c.AuthPublicKeyFile != "" {
in, err := auth.Interceptor(c.AuthPublicKeyFile)
if err != nil {
c.Logger.Fatal("cannot create auth interceptor", nameField, zap.Error(err))
}
opts = append(opts, grpc.UnaryInterceptor(in))
}
s := grpc.NewServer(opts...)
c.RegisterFunc(s)
c.Logger.Info("server started", nameField, zap.String("addr", c.Addr))
return s.Serve(lis)
}
3、简单使用
logger.Sugar().Fatal(server.RunGRPCServer(&server.GRPCConfig{
Name: "auth",
Addr: ":8081",
Logger: logger,
RegisterFunc: func(s *grpc.Server) {
authpb.RegisterAuthServiceServer(s, &auth.Service{
OpenIDResolver: &wechat.Service{
AppID: "your app id",
AppSecret: "your app secret",
},
Mongo: dao.NewMongo(mongoClient.Database("coolcar")),
Logger: logger,
TokenExpire: 10 * time.Second,
TokenGenerator: token.NewJWTTokenGen("coolcar/auth", privKey),
})
},
}))