diff --git a/actions/accountaction.go b/actions/accountaction.go index b06de01..dfea98c 100644 --- a/actions/accountaction.go +++ b/actions/accountaction.go @@ -3,6 +3,7 @@ package actions import ( "context" "fmt" + "pro2d/components/jwt" "pro2d/conf" "pro2d/models" "pro2d/protos/pb" @@ -52,7 +53,7 @@ func (s *LoginServer) CreateTokenHandler(ctx context.Context, in *pb.Account) (* return &pb.CreateTokenRsp{ Code: 0, Uid: m.Uid, - Token: utils.CreateToken(m.Account), + Token: jwt.CreateToken(m.Account.Uid), GameService: gameInfo, }, nil } diff --git a/actions/roleaction.go b/actions/roleaction.go index d1c5eae..ecf90ff 100644 --- a/actions/roleaction.go +++ b/actions/roleaction.go @@ -3,7 +3,6 @@ package actions import ( "context" "errors" - "fmt" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/types/known/emptypb" "pro2d/conf" @@ -26,16 +25,12 @@ func (s *GameServer) HeartBeatHandler(ctx context.Context, empty *emptypb.Empty) } func (s *GameServer) CreateRoleHandler(ctx context.Context, in *pb.LoginReq) (*pb.RoleRsp, error) { - account := utils.CheckAuth(ctx) - if account == nil { - return nil, fmt.Errorf("token error") - } - - ok, role := models.RoleExistByUid(account.Uid) + uid := ctx.Value("uid").(string) + ok, role := models.RoleExistByUid(uid) if !ok { role = models.NewRole(conf.SnowFlack.NextVal()) role.Role.Device = in.Device - role.Role.Uid = account.Uid + role.Role.Uid = uid role.Create() } return &pb.RoleRsp{ @@ -45,12 +40,8 @@ func (s *GameServer) CreateRoleHandler(ctx context.Context, in *pb.LoginReq) (* } func (s *GameServer) LoginHandler(ctx context.Context, in *pb.LoginReq) (*pb.RoleRsp, error) { - account := utils.CheckAuth(ctx) - if account == nil { - return nil, fmt.Errorf("token error") - } - - ok, role := models.RoleExistByUid(account.Uid) + uid := ctx.Value("uid").(string) + ok, role := models.RoleExistByUid(uid) if !ok { return &pb.RoleRsp{ Code: 1, diff --git a/actions/server.go b/actions/server.go index 8893e15..f60b236 100644 --- a/actions/server.go +++ b/actions/server.go @@ -2,9 +2,11 @@ package actions import ( "context" + "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" + "pro2d/components/jwt" "pro2d/conf" "pro2d/models" "pro2d/protos/pb" @@ -86,7 +88,11 @@ func GameServerInterceptor(ctx context.Context, req interface{}, info *grpc.Unar handler grpc.UnaryHandler) (interface{}, error) { //utils.Sugar.Debugf("gRPC method: %s, %v", info.FullMethod, req) - + uid := jwt.CheckAuth(ctx) + if uid == ""{ + return nil, fmt.Errorf("token error") + } + context.WithValue(ctx, "uid", uid) resp, err := handler(ctx, req) return resp, err } diff --git a/components/jwt/jwt.go b/components/jwt/jwt.go new file mode 100644 index 0000000..b90c211 --- /dev/null +++ b/components/jwt/jwt.go @@ -0,0 +1,96 @@ +package jwt + +import ( + "context" + "fmt" + "pro2d/conf" + "pro2d/utils" + "time" + + jwt "github.com/dgrijalva/jwt-go" + "google.golang.org/grpc/metadata" +) + +func CreateToken(uid string) (tokenString string) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "iss": "pro2d-app-server", + "aud": "pro2d-app-server", + "nbf": time.Now().Unix(), + "exp": time.Now().Add(time.Hour).Unix(), + "sub": "pro2d", + "uid": uid, + }) + tokenString, err := token.SignedString([]byte(utils.Pro2DTokenSignedString)) + if err != nil { + panic(err) + } + return tokenString +} + +func ParseToken(tokenStr string) string { + var clientClaims Claims + token, err := jwt.ParseWithClaims(tokenStr, &clientClaims, func(token *jwt.Token) (interface{}, error) { + if token.Header["alg"] != "HS256" { + //panic("ErrInvalidAlgorithm") + utils.Sugar.Error("ErrInvalidAlgorithm") + return nil, nil + } + return []byte(utils.Pro2DTokenSignedString), nil + }) + if err != nil { + utils.Sugar.Error("jwt parse error") + return "" + } + + if !token.Valid { + utils.Sugar.Error("ErrInvalidToken") + return "" + } + return clientClaims.Uid +} + + +// Claims defines the struct containing the token claims. +type Claims struct { + jwt.StandardClaims + Uid string +} + +// 从 context 的 metadata 中,取出 token +func getTokenFromContext(ctx context.Context) (string, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "", fmt.Errorf("ErrNoMetadataInContext") + } + // md 的类型是 type MD map[string][]string + token, ok := md["authorization"] + if !ok || len(token) == 0 { + return "", fmt.Errorf("ErrNoAuthorizationInMetadata") + } + // 因此,token 是一个字符串数组,我们只用了 token[0] + return token[0], nil +} + +func CheckAuth(ctx context.Context) string { + tokenStr, err := getTokenFromContext(ctx) + if err != nil { + utils.Sugar.Errorf("get token from context error") + return "" + } + return ParseToken(tokenStr) +} + +// AuthToken 自定义认证 客户端使用 +type AuthToken struct { + Token string +} + +func (c AuthToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "authorization": c.Token, + }, nil +} + +func (c AuthToken) RequireTransportSecurity() bool { + return conf.GlobalConf.TLS.Status +} \ No newline at end of file diff --git a/components/jwt/jwt_test.go b/components/jwt/jwt_test.go new file mode 100644 index 0000000..ecc2eb7 --- /dev/null +++ b/components/jwt/jwt_test.go @@ -0,0 +1,18 @@ +package jwt + +import ( + "fmt" + "pro2d/protos/pb" + "testing" +) + +func TestCreateToken(t *testing.T) { + account := &pb.Account{ + Phone: "17683852936", + Password: "123456", + Uid: "12312", + } + token := CreateToken(account.Uid) + ac := ParseToken(token) + fmt.Println("token: ", token, "\nac: ", ac) +} diff --git a/conf/conf.yaml b/conf/conf.yaml index 5cb6edb..57c1ae2 100644 --- a/conf/conf.yaml +++ b/conf/conf.yaml @@ -16,7 +16,7 @@ etcd: endpoints: - "192.168.0.206:2379" -TLS: +tls: status: true key: "keys/server.key" pem: "keys/server.pem" diff --git a/protos b/protos index 5644415..cee7537 160000 --- a/protos +++ b/protos @@ -1 +1 @@ -Subproject commit 564441523f2a69a4efeb2116addae2ed926d6e53 +Subproject commit cee7537250bcee3ddd04e03d2d142d6d1ac6417e diff --git a/test/client.go b/test/client.go index 4fcf435..1cd76b8 100644 --- a/test/client.go +++ b/test/client.go @@ -7,6 +7,7 @@ import ( "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "pro2d/components/jwt" _ "pro2d/conf" "pro2d/protos/pb" "pro2d/utils" @@ -32,7 +33,7 @@ func Register(c pb.LoginClient, phone, password string) error { func Login(loginUri, token, uid string) { var opts []grpc.DialOption // 指定自定义认证 - opts = append(opts, grpc.WithPerRPCCredentials(&utils.AuthToken{Token: token})) + opts = append(opts, grpc.WithPerRPCCredentials(&jwt.AuthToken{Token: token})) if TLS { // TLS连接 creds, err := credentials.NewClientTLSFromFile("keys/server.pem", ServerName) diff --git a/test/tlsclient.go b/test/tlsclient.go index e4b7185..f5a4e6e 100644 --- a/test/tlsclient.go +++ b/test/tlsclient.go @@ -4,9 +4,27 @@ import ( "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/types/known/emptypb" "log" + "pro2d/components/jwt" + _ "pro2d/conf" "pro2d/protos/pb" + "pro2d/utils" ) +// AuthToken 自定义认证 客户端使用 +type CustomToken struct { +} + +func (c CustomToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "appId": "100", + "appKey": "token", + }, nil +} + +func (c CustomToken) RequireTransportSecurity() bool { + return true +} func main() { var opts []grpc.DialOption @@ -19,7 +37,25 @@ func main() { conn, err := grpc.Dial("localhost:8948", opts...) helloClient := pb.NewHelloClient(conn) - rsp, err := helloClient.SayHello(context.TODO(), &pb.HelloWorld{Msg: "hello world"}) + token, err := helloClient.CreateToken(context.TODO(), &pb.Login{ + Login: "login", + Password: "123456", + }) + if err != nil { + log.Fatal(err) + return + } + utils.Sugar.Debugf("token: %s", token.Token) + + opts = append(opts, grpc.WithPerRPCCredentials(&jwt.AuthToken{Token: token.Token})) + conn2, err := grpc.Dial("localhost:8948",opts...) + if err != nil { + log.Fatal(err) + return + } + + helloClient2 := pb.NewHelloClient(conn2) + rsp, err := helloClient2.SayHello(context.TODO(), &emptypb.Empty{}) if err != nil { log.Fatal(err) } diff --git a/test/tlsserver.go b/test/tlsserver.go index fc0ab5f..bc719ae 100644 --- a/test/tlsserver.go +++ b/test/tlsserver.go @@ -2,10 +2,16 @@ package main import ( "context" + "fmt" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" "log" "net" + "pro2d/components/jwt" "pro2d/protos/pb" ) @@ -13,10 +19,50 @@ type Server struct { pb.UnimplementedHelloServer } -func (s *Server) SayHello(ctx context.Context, in *pb.HelloWorld) (*pb.HelloWorld, error) { - return in, nil +func (s *Server) CreateToken(ctx context.Context, in *pb.Login) (*pb.TokenInfo, error) { + if in.Login == "login" && in.Password == "123456" { + return &pb.TokenInfo{Token: jwt.CreateToken(in.Login)}, nil + } + return nil, fmt.Errorf("login error") } +//func (s *Server) SayHello(ctx context.Context, empty *emptypb.Empty) (*pb.HelloWorld, error) { +// md, ok := metadata.FromIncomingContext(ctx) +// if !ok { +// return nil, status.Errorf(codes.Unauthenticated,"ErrNoMetadataInContext") +// } +// // md 的类型是 type MD map[string][]string +// token, ok := md["authorization"] +// if !ok || len(token) == 0 { +// return nil, status.Errorf(codes.Unauthenticated,"ErrNoAuthorizationInMetadata") +// } +// login := jwt.ParseToken(token[0]) +// return &pb.HelloWorld{Msg: "Hello world: " + login}, nil +//} + +func (s *Server) SayHello(ctx context.Context, empty *emptypb.Empty) (*pb.HelloWorld, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Errorf(codes.Unauthenticated, "无Token认证信息") + } + var ( + appId string + appKey string + ) + if val, ok := md["appid"]; ok { + appId = val[0] + } + + if val, ok := md["appkey"]; ok { + appKey = val[0] + } + + if appId != "100" || appKey != "token" { + return nil, status.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appId, appKey) + } + + return &pb.HelloWorld{Msg: "Hello world"}, nil +} func main() { // 监听本地端口 listener, err := net.Listen("tcp", ":8948") diff --git a/utils/jwt.go b/utils/jwt.go deleted file mode 100644 index 4874bc1..0000000 --- a/utils/jwt.go +++ /dev/null @@ -1,100 +0,0 @@ -package utils - -import ( - "context" - "fmt" - "pro2d/conf" - "pro2d/protos/pb" - "time" - - jwt "github.com/dgrijalva/jwt-go" - "google.golang.org/grpc/metadata" -) - -func CreateToken(account *pb.Account) (tokenString string) { - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "iss": "pro2d-app-server", - "aud": "pro2d-app-server", - "nbf": time.Now().Unix(), - "exp": time.Now().Add(time.Hour).Unix(), - "sub": "pro2d", - "phone": account.Phone, - "uid": account.Uid, - }) - tokenString, err := token.SignedString([]byte(Pro2DTokenSignedString)) - if err != nil { - panic(err) - } - return tokenString -} - -func ParseToken(tokenStr string)*pb.Account{ - var clientClaims Claims - token, err := jwt.ParseWithClaims(tokenStr, &clientClaims, func(token *jwt.Token) (interface{}, error) { - if token.Header["alg"] != "HS256" { - //panic("ErrInvalidAlgorithm") - Sugar.Error("ErrInvalidAlgorithm") - return nil, nil - } - return []byte(Pro2DTokenSignedString), nil - }) - if err != nil { - Sugar.Error("jwt parse error") - return nil - } - - if !token.Valid { - Sugar.Error("ErrInvalidToken") - return nil - } - return &clientClaims.Account -} - - -// Claims defines the struct containing the token claims. -type Claims struct { - jwt.StandardClaims - //phone string `json:"phone"` - //uid int64 `json:"uid"` - //device string `json:"device"` - pb.Account -} - -// 从 context 的 metadata 中,取出 token -func getTokenFromContext(ctx context.Context) (string, error) { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "", fmt.Errorf("ErrNoMetadataInContext") - } - // md 的类型是 type MD map[string][]string - token, ok := md["authorization"] - if !ok || len(token) == 0 { - return "", fmt.Errorf("ErrNoAuthorizationInMetadata") - } - // 因此,token 是一个字符串数组,我们只用了 token[0] - return token[0], nil -} - -func CheckAuth(ctx context.Context) *pb.Account{ - tokenStr, err := getTokenFromContext(ctx) - if err != nil { - Sugar.Errorf("get token from context error") - return nil - } - return ParseToken(tokenStr) -} - -// AuthToken 自定义认证 客户端使用 -type AuthToken struct { - Token string -} - -func (c AuthToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { - return map[string]string{ - "authorization": c.Token, - }, nil -} - -func (c AuthToken) RequireTransportSecurity() bool { - return conf.GlobalConf.TLS.Status -} \ No newline at end of file diff --git a/utils/jwt_test.go b/utils/jwt_test.go deleted file mode 100644 index e947f4d..0000000 --- a/utils/jwt_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "fmt" - "pro2d/protos/pb" - "testing" -) - -func TestCreateToken(t *testing.T) { - account := &pb.Account{ - Phone: "17683852936", - Password: "123456", - Uid: "12312", - } - token := CreateToken(account) - ac := ParseToken(token) - fmt.Println("token: ", token, "\nac: ", ac) -} -- libgit2 0.21.2