Browse Source

jwt: token优化

Yi 1 year ago
parent
commit
4e737009d8

+ 1 - 0
.gitignore

@@ -47,3 +47,4 @@ _testmain.go
 bin/
 .vscode/
 logger/
+config/private.key

+ 3 - 4
config/app.develop.yaml

@@ -3,14 +3,13 @@ app_environment: test
 debug: true
 http_server_addr: ':8090'
 http_metrics_addr: ':23332'
+jwt_expire_time: 7200
 
 store:
   show_sql: true
   driver_name: mysql
-  #kpt_tmr_group_rw: "root:123456@tcp(127.0.0.1:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s"
-  #kpt_tmr_group_migr: "root:123456@tcp(127.0.0.1:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s"
-  kpt_tmr_group_rw: "root:123456@tcp(192.168.1.70:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s"
-  kpt_tmr_group_migr: "root:123456@tcp(192.168.1.70:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s"
+  kpt_tmr_group_rw: "root:123456@tcp(127.0.0.1:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s"
+  kpt_tmr_group_migr: "root:123456@tcp(127.0.0.1:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s"
 
 redis_setting:
   sso_cache:

+ 10 - 1
config/app.go

@@ -1,6 +1,7 @@
 package config
 
 import (
+	"crypto/rsa"
 	"kpt-tmr-group/pkg/di"
 	"os"
 	"strings"
@@ -29,7 +30,14 @@ type AppConfig struct {
 
 	ExcelSetting ExcelSetting `json:"excelSetting" yaml:"excel_setting"`
 
-	WechatSetting WechatSetting `json:"wechatSetting" yaml:"wechat_setting"`
+	WechatSetting     WechatSetting     `json:"wechatSetting" yaml:"wechat_setting"`
+	JwtTokenKeyConfig JwtTokenKeyConfig `json:"jwtTokenKeyConfig"`
+	JwtExpireTime     int               `json:"jwtExpireTime" yaml:"jwt_expire_time"`
+}
+
+type JwtTokenKeyConfig struct {
+	PrivateKey *rsa.PrivateKey `json:"privateKey"`
+	PublicKey  *rsa.PublicKey  `json:"publicKey"`
 }
 
 type WechatSetting struct {
@@ -83,6 +91,7 @@ func init() {
 		if err != nil {
 			panic(err)
 		}
+		cfg.JwtTokenKeyConfig = openPrivateKey()
 		options = cfg
 	})
 }

+ 1 - 0
config/app.test.yaml

@@ -3,6 +3,7 @@ app_environment: test
 debug: true
 http_server_addr: ':8090'
 http_metrics_addr: ':23332'
+jwt_expire_time: 7200
 
 store:
   show_sql: true

+ 39 - 1
config/load_config.go

@@ -2,14 +2,18 @@ package config
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 
+	"github.com/dgrijalva/jwt-go"
+
 	"github.com/mitchellh/mapstructure"
 	"github.com/spf13/viper"
 )
 
+var workDir = os.Getenv("GO_WORK_DIR_TMR_GROUP")
+
 func Initialize(path string, cfgStruct interface{}) error {
-	workDir := os.Getenv("GO_WORK_DIR_TMR_GROUP")
 	if workDir == "" {
 		workDir = "."
 	}
@@ -26,3 +30,37 @@ func Initialize(path string, cfgStruct interface{}) error {
 	}
 	return nil
 }
+
+func openPrivateKey() JwtTokenKeyConfig {
+	pathList := []string{fmt.Sprintf("%s/config/private.key", workDir), fmt.Sprintf("%s/config/public.key", workDir)}
+	res := JwtTokenKeyConfig{PublicKey: nil, PrivateKey: nil}
+	for i, path := range pathList {
+		keyConfig, err := os.Open(path)
+		if err != nil {
+			panic(err)
+		}
+
+		b, err := ioutil.ReadAll(keyConfig)
+		if err != nil {
+			panic(err)
+		}
+
+		if i == 0 {
+			parivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(b)
+			if err != nil {
+				panic(err)
+			}
+			res.PrivateKey = parivateKey
+		}
+
+		if i == 1 {
+			publicKey, err := jwt.ParseRSAPublicKeyFromPEM(b)
+			if err != nil {
+				panic(err)
+			}
+			res.PublicKey = publicKey
+		}
+	}
+
+	return res
+}

+ 27 - 0
config/private.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAokQEiPqzrL22jrKqWN3a9XXYtXLhJOolmYoPRfBpWFr5ml/v
+cEsMMPmVc9EUhDSXyo2tTgiCBhqyLZ+C/9obJJv92W4sPaPjeVHhvpFdC+a0RDQy
+b9X8s5xuUBVCyh7UlAs2dOHQ3kqAhhybATHlOvOksc8GFp9hxQZtqeVhmjMKjdJY
+McIi8HLL3v/CvZO43ClohSRVB7mxNYm4ddTgc1jFmtBAUuu3ha7qm8CXQFAEYwf8
+9PkjfhKXaALAVS09lpy8Qx3rCGjeuFbKHtVedy/CZmjq3xHrJVNcKwaRFCXUroyu
+WjFlITyQCHjugh+6MiDnz3oP/4Y3rXaMag+E9QIDAQABAoIBAAbBHg81oFUIcJjw
+Bvc8BOlCxoqHBagrtvT1Mj2PDFOJNeqfI7Bg71j6iqYPhfMa3VapHxWodQEcC16q
+zSEpSwfzFlX+qAjI0aczKVptYpokOEy+f3r4RVSUpmLe/7C6J88hyFqFK9SU9VSf
+zDP4h7o0eh6Mp4w5hjBVXCs+SRnKBRuV/CGOX/i6tzyFxuA7pzhPcU3HSIwJX9tY
+/wTx8vFmfvtra+IHj3Ls9LvGrauxSOhQ5yTktPQpi4s6nPJP6NCpJbASWNhKafKt
+uKIMMxls+sYhjXJ8UwMoTZV2h/Zjy6NSo+tEXiAwfsgXJxCqbUQx6qkY3qICNso0
+4KQdfAECgYEA2ual2bCDHAiRwDW6vSmKOwqz1X95X0tHZFl52o5rt888TGT5NoH7
+PRSRJ+sI1wiI/NcWL9IECvsNFOE/rZkDeLraVFRuBoN0alXY83cxIjd7ILHs9TKM
+UdLFFWNMQ5xNjp2oI0a+7WZPBUM7X3E664oVcfXwR94X9Fed3QYFjpUCgYEAvcQp
+hI3GA/ylvagMZdvewELyC2Z1EHNx71NZN28z3gERLTTdHBKuRmhx3+ZzTGHqogwm
+Zz5fhsm7NwFN8T2VlMIM4i//YmUeSR10hNi8hstVpSqxrTl+U42NdMjX0qMI4Yzs
+31q9aQniYKyo/jEJCVfLATQLrxYXGmGDlcXPZOECgYBcjRpgQPYeGy1spArxQaf1
+MKA6S2xIsy5bKk2P75lxnMg00JhhyHEuMQzyYLhbGnu400PJMjWNYvnz8wyWXFSQ
+bggzkYL3dAr7jpJs1d9LRUPk3fV+8kFLsx6q3VH4YBSWkOfvClFdVF1irztiLiCo
+R7irdz2BVVCxeX4AXjRpRQKBgCgnGI3Wnb58N+mgZDctRb2yrJpi7IQ1mTcFkMwc
++IGSAjtpgxuMjFmTgfzZOy4/TbFKFtojpXzUtItX+FECMeNPCADYHRaJxNW9qMjv
+PzzaS13uYbgAgU1Wl0bNtHT6UEfBVOGRxqAyGjdQcVWPtJTIgcjYZVx/tRW86mXH
+fDGhAoGARY2zWpaDhzzXii7PJH2s1wxabkROjG+7zEwCCItB5S1BfDso4P7VOII6
+n9QYLQw8tMs6Zfu7tGeIWDywRHvYFfPaqLuljqQT+NuKx4/nOLyGVn1Hum7FvYaU
+wvQD8RM51zA4vzT6lmMLsXDen7c9WA2LO47OelsdS+qT3VoMWS4=
+-----END RSA PRIVATE KEY-----

+ 9 - 0
config/public.key

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAokQEiPqzrL22jrKqWN3a
+9XXYtXLhJOolmYoPRfBpWFr5ml/vcEsMMPmVc9EUhDSXyo2tTgiCBhqyLZ+C/9ob
+JJv92W4sPaPjeVHhvpFdC+a0RDQyb9X8s5xuUBVCyh7UlAs2dOHQ3kqAhhybATHl
+OvOksc8GFp9hxQZtqeVhmjMKjdJYMcIi8HLL3v/CvZO43ClohSRVB7mxNYm4ddTg
+c1jFmtBAUuu3ha7qm8CXQFAEYwf89PkjfhKXaALAVS09lpy8Qx3rCGjeuFbKHtVe
+dy/CZmjq3xHrJVNcKwaRFCXUroyuWjFlITyQCHjugh+6MiDnz3oP/4Y3rXaMag+E
+9QIDAQAB
+-----END PUBLIC KEY-----

+ 1 - 0
http/handler/system/user.go

@@ -108,6 +108,7 @@ func GetUserInfo(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
+
 	res, err := middleware.BackendOperation(c).OpsService.GetUserInfo(c, token)
 	if err != nil {
 		apierr.ClassifiedAbort(c, err)

+ 5 - 3
http/middleware/sso.go

@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"kpt-tmr-group/config"
 	"kpt-tmr-group/pkg/apierr"
 	"kpt-tmr-group/pkg/jwt"
 	commonPb "kpt-tmr-group/proto/go/backend/common"
@@ -44,13 +45,14 @@ func RequireAdmin() gin.HandlerFunc {
 			return
 		}
 
-		claims, err := jwt.ParseToken(token)
-		if err != nil || claims == nil || claims.Username == "" {
+		tokenVerifier := jwt.JWTTokenVerifier{PublicKey: config.Options().JwtTokenKeyConfig.PublicKey}
+		userName, err := tokenVerifier.ParseToken(token)
+		if err != nil {
 			unauthorized(c)
 			return
 		}
 
-		c.Set(UserName, claims.Username)
+		c.Set(UserName, userName)
 		c.Set(XRequestId, GetXRequestId(c))
 		c.Next()
 	}

+ 1 - 0
module/backend/interface.go

@@ -103,6 +103,7 @@ type PastureService interface {
 type SystemService interface {
 	// Auth 系统用户相关
 	Auth(ctx context.Context, auth *operationPb.UserAuthData) (*operationPb.SystemToken, error)
+	GetUserName(ctx context.Context) (string, error)
 	GetUserInfo(ctx context.Context, token string) (*operationPb.UserAuth, error)
 	CreateSystemUser(ctx context.Context, req *operationPb.AddSystemUser) error
 	SearchSystemUserList(ctx context.Context, req *operationPb.SearchUserRequest) (*operationPb.SearchUserResponse, error)

+ 20 - 9
module/backend/system_service.go

@@ -33,7 +33,8 @@ func (s *StoreEntry) Auth(ctx context.Context, auth *operationPb.UserAuthData) (
 		return nil, xerr.Customf("该账号已被禁用,请联系管理员")
 	}
 
-	token, err := jwt.GenerateToken(systemUser.Name, systemUser.Password)
+	jwtToken := jwt.NewJWTTokenGen(s.Cfg.AppName, s.Cfg.JwtTokenKeyConfig.PrivateKey)
+	token, err := jwtToken.GenerateToken(systemUser.Name, s.Cfg.JwtExpireTime)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -48,18 +49,28 @@ func (s *StoreEntry) Auth(ctx context.Context, auth *operationPb.UserAuthData) (
 	}, nil
 }
 
+func (s *StoreEntry) GetUserName(ctx context.Context) (string, error) {
+	userNameInter := ctx.Value("userName")
+	if userNameInter == nil {
+		return "", xerr.Customf("cannot userName")
+	}
+
+	if userName, ok := userNameInter.(string); ok {
+		return userName, nil
+	} else {
+		return "", xerr.Customf("waring userName")
+	}
+}
+
 // GetUserInfo 获取用户信息
 func (s *StoreEntry) GetUserInfo(ctx context.Context, token string) (*operationPb.UserAuth, error) {
 	systemUser := &model.SystemUser{}
-	claims, err := jwt.ParseToken(token)
+	userName, err := s.GetUserName(ctx)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	if claims.Username == "" {
-		return nil, xerr.Custom("token解析失败")
-	}
 
-	if err = s.DB.Where("name = ?", claims.Username).First(systemUser).Error; err != nil {
+	if err = s.DB.Where("name = ?", userName).First(systemUser).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
@@ -251,7 +262,7 @@ func (s *StoreEntry) IsShowSystemUser(ctx context.Context, req *operationPb.IsSh
 // GetSystemUserPermissions 返回系统用户相关菜单权限
 func (s *StoreEntry) GetSystemUserPermissions(ctx context.Context, token string) (*operationPb.SystemUserMenuPermissions, error) {
 	// 解析token
-	claims, err := jwt.ParseToken(token)
+	/*claims, err := jwt.ParseToken(token)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -274,8 +285,8 @@ func (s *StoreEntry) GetSystemUserPermissions(ctx context.Context, token string)
 	systemRoles := make([]*model.SystemRole, 0)
 	if err = s.DB.Where("is_show = ?", operationPb.IsShow_OK).Find(&systemRoles, roleIds).Error; err != nil {
 		return nil, xerr.WithStack(err)
-	}
-
+	}*/
+	systemRoles := make([]*model.SystemRole, 0)
 	systemAllPermissionsList := &SystemAllPermissionsList{
 		PastureList: make([]*model.SystemGroupPasturePermissions, 0),
 		MenuList:    make([]*model.SystemMenuPermissions, 0),

+ 43 - 42
pkg/jwt/jwt.go

@@ -1,63 +1,64 @@
 package jwt
 
 import (
+	"crypto/rsa"
 	"fmt"
-	"kpt-tmr-group/config"
-	"reflect"
 	"time"
 
 	"github.com/dgrijalva/jwt-go"
 )
 
-var jwtSecret = []byte(config.Options().JwtSecret)
-
-type Claims struct {
-	Username string `json:"username"`
-	Password string `json:"password"`
-	jwt.StandardClaims
+type JWTTokenGenerate struct {
+	privateKey *rsa.PrivateKey
+	issuer     string
+	nowFunc    func() time.Time
 }
 
-func GenerateToken(username, password string) (string, error) {
-	nowTime := time.Now()
-	expireTime := nowTime.Add(4 * time.Hour)
-
-	claims := Claims{
-		username,
-		password,
-		jwt.StandardClaims{
-			ExpiresAt: expireTime.Unix(),
-			Issuer:    "https://github.com/kptyun/go-admin/",
-		},
+func NewJWTTokenGen(issuer string, privateKey *rsa.PrivateKey) *JWTTokenGenerate {
+	return &JWTTokenGenerate{
+		privateKey: privateKey,
+		issuer:     issuer,
+		nowFunc:    time.Now,
 	}
-
-	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-	return tokenClaims.SignedString(jwtSecret)
 }
 
-func ParseToken(token string) (*Claims, error) {
-	tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
-		return jwtSecret, nil
+func (j *JWTTokenGenerate) GenerateToken(username string, expireTime int) (string, error) {
+	nowTime := j.nowFunc().Unix()
+	token := jwt.NewWithClaims(jwt.SigningMethodRS512, jwt.StandardClaims{
+		Issuer:    j.issuer,
+		IssuedAt:  nowTime,
+		ExpiresAt: nowTime + int64(expireTime),
+		Subject:   username,
 	})
+	return token.SignedString(j.privateKey)
+}
 
-	if tokenClaims != nil {
-		if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
-			return claims, nil
-		}
-	}
-
-	return nil, err
+// JWTTokenVerifier 校验token
+type JWTTokenVerifier struct {
+	PublicKey *rsa.PublicKey
 }
 
-func GetIdFromClaims(key string, claims jwt.Claims) string {
-	v := reflect.ValueOf(claims)
-	if v.Kind() == reflect.Map {
-		for _, k := range v.MapKeys() {
-			value := v.MapIndex(k)
+func (v *JWTTokenVerifier) ParseToken(token string) (string, error) {
+	// 自定义字段许使用  jwt.MapClaims{},这里没有定义其他字段,就用jwt.StandardClaims
+	jwtToken, err := jwt.ParseWithClaims(token, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
+		return v.PublicKey, nil
+	})
 
-			if fmt.Sprintf("%s", k.Interface()) == key {
-				return fmt.Sprintf("%v", value.Interface())
-			}
-		}
+	if err != nil {
+		return "", err
 	}
-	return ""
+	if !jwtToken.Valid {
+		return "", fmt.Errorf("token not valid")
+	}
+	// 判断类型
+	clm, ok := jwtToken.Claims.(*jwt.StandardClaims)
+	if !ok {
+		return "", fmt.Errorf("token claim is not StandardClaims")
+	}
+
+	if err = clm.Valid(); err != nil {
+		return "", fmt.Errorf("claim not valid: %v", err)
+	}
+
+	return clm.Subject, nil
 }

+ 88 - 0
pkg/jwt/jwt_test.go

@@ -0,0 +1,88 @@
+package jwt
+
+import (
+	"testing"
+	"time"
+
+	"github.com/dgrijalva/jwt-go"
+)
+
+const privateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1aC2ROAGNjpmpevOlg/NTVtgd8fdve3cM1J69uS6UHoZJWR2
+pmIzOr5EQkQjr0ldnS4zP9feHPTeGebcvekqAzrGwEgKTMe5Scu64f+y77l1p5RV
+FI2WLZhCw1zaYI3GiJMzYx5YeMRT2aKizIe2ORIooXdypx+xY8E4vTERsAr+w1Zb
+U2P+HSc65u7FYxjNx5y4m4lsE4oRHcaNK9DxtdccRQmugVRHINdZFtcNmtArZK2p
+sexpdmkRDL3tJtQD3O1GcLR7JEMQR1UGrjvvd/ZQmQIVgz+D9R3Nd/B8mSHFwV3z
+Mt3I1y7Pd1ZMYve06iTzqxBBDs4ot60BQj85awIDAQABAoIBAQDCZis3Zf2BH4gF
+eR5XZC3ZNUwF9DUO/wMhGO+Y+O+rCFEVu+RU6Ivh6v7GpqcqfcLYpBAzCseu5g2u
+2G0LzMBUjAoNNAFbeWxdgS2hN0Sn7cbpJox91ZJSD3rBuR9oOdvusLVUf2rXCyGa
+rOsk4GKscJ/Uv5PwDWEpe82cLKkB3nW9iXDUNv6+QQyLf9Rfikxzzcm9PW3qOCBE
+FFUGgFiC4vXL7mjodwQ4uxS8Krpn1poyBDkHGOv5yMbsOouaR6ShsJHPXSeeTfu5
+y8THhRJDzN2L39ecKdLwv1fKcQgLDqT69TwOJ//78k777yy3tDhU+0S/nDQQdEXF
+Hx5s7DPxAoGBAPFqJyLpRj93HiyS5LCQv93thQ7tc+fnMCBembVh5ErTTfQPREKT
+B6+42UWWch8xjcL1JQKcyBbgGeA5kLzMKmi+JEtHXpoqznkkJLBLXmuNvmwbXB3x
+uuAj904fOdntDoMsoExIt+GA0sGyx825TxMNoSiC7lMnQUtS/2JTPlE9AoGBAOKI
+yuJmzZb/QMC7x83/PqhqJCxd9myxxc1KEBYqLM3hwb0dhG22ep84QxyLf0ScrhUf
+d5KGLIZ/lwCW3kZsSE6GzlK9GTzhbWeNEBSk8hhDITI+P+9ca+j1jNRzrQA3Ilx1
+U7CiuDPHkgh4QFU8Vxlg35zad6UH9Sojazd/8Y/HAoGBAJ67UF5JCXJjTQi70Pgz
+RKSULr/A60vYm7E84k37vpJgW0oU26n2aMBmhx2VLRZLi81bSGluUrWPxhPDZeJt
+T5ktJEoG9DHj4XyPgjwUYlHPkhwc5TWfImOL4miQaYZbswYWypM94QG5pVnMxkkD
+BfYeKQ+s/yzXi7wOJ3MsDrZ1AoGBAN/b4/nlM0cDCP8s0Z7cnBObiBGb7ReueVSL
+2ue8V7hAXs6+q44rpHNRCujbZTHtTw4qoWPXWYqz7Qm1DQQ0nyOSjbMvkRAbiJyq
+xnhQI7kcJWLcJxwnja+Rb9DGWvB6i2covkIwGq14ivP5uUFBtqSqmW8MaO1Ztskk
+ZSBcO98jAoGAbHMD0WmQqnKpVHbchOfldluzzWkWZMBQlX23Fr7iHfzXiikjgCqK
+6HtRq1m6AGIZsbM+Dd53knurbTo49GfGjneN0ZtCFbiBOwFx0OX69UZcxmd+3Cqz
+14QDSlFpfXTbugpZFYC9KTakY7nDgsj1Ei2WjTE0gVSlUtuiw1XykCM=
+-----END RSA PRIVATE KEY-----`
+
+const publicKey = `-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAokQEiPqzrL22jrKqWN3a
+9XXYtXLhJOolmYoPRfBpWFr5ml/vcEsMMPmVc9EUhDSXyo2tTgiCBhqyLZ+C/9ob
+JJv92W4sPaPjeVHhvpFdC+a0RDQyb9X8s5xuUBVCyh7UlAs2dOHQ3kqAhhybATHl
+OvOksc8GFp9hxQZtqeVhmjMKjdJYMcIi8HLL3v/CvZO43ClohSRVB7mxNYm4ddTg
+c1jFmtBAUuu3ha7qm8CXQFAEYwf89PkjfhKXaALAVS09lpy8Qx3rCGjeuFbKHtVe
+dy/CZmjq3xHrJVNcKwaRFCXUroyuWjFlITyQCHjugh+6MiDnz3oP/4Y3rXaMag+E
+9QIDAQAB
+-----END PUBLIC KEY-----`
+
+var (
+	userName = "1234567890"
+	Password = "123"
+)
+
+func TestJWTTokenGenerate_GenerateToken(t *testing.T) {
+	key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
+	if err != nil {
+		t.Fatalf("cannot parse private key: %v", err)
+	}
+	g := NewJWTTokenGen("kpt-tmr-group", key)
+	g.nowFunc = func() time.Time {
+		return time.Unix(1516239022, 0)
+	}
+	token, err := g.GenerateToken(userName, 7200)
+	if err != nil {
+		t.Fatalf("cannot generate token: %v", err)
+	}
+	want := `eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTYyNDYyMjIsImlhdCI6MTUxNjIzOTAyMiwiaXNzIjoia3B0LXRtci1ncm91cCIsInN1YiI6IjEyMzQ1Njc4OTAtMTIzIn0.fNKcmzjsghdCVn0ZhPHt3mg_I22IZvTQyMaN8xt9GHoZdUpml1sqZ24-mamCaIK1T-pP0USqc69ZkeUBjOz1hi-p0u3Z8u8J8uaaTh0RD7cYxhT6ey5-KzctiuiSutFH8G5Y3pZ3ffRO-b_8hKpTICFET9-3_4pWgtQ4_M76JnLZEm_0-3e4MA3GRba0S6ntZ19TiOeNkF30LQjQ3jCSp3xQb-NoDRdlQKWoYDAQI33RxAjtR5ZsLwee278qbzWjwtd_GyqJJuaokfFsc0JZXFSIOffK1S6qHijSsK85U6PciX05M6NkE9LMyAi990Q6iLk4NUrVE5gmEXA9MeEm0w`
+	if want != token {
+		t.Fatalf("wrong token generated. got: %q", token)
+	}
+}
+
+func TestJWTTokenVerifier_ParseToken(t *testing.T) {
+	publicKeyFromPEM, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
+	if err != nil {
+		t.Fatalf("cannot parse public key: %v", err)
+	}
+	v := &JWTTokenVerifier{PublicKey: publicKeyFromPEM}
+
+	token := `eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODk4Mjc3OTMsImlhdCI6MTY4OTgyMDU5MywiaXNzIjoia3B0LXRtci1ncm91cCIsInN1YiI6ImR1YW54aWFvZHVhbi1lMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSJ9.cdtoNMAU5WEO-OTqHS9EQXvRBhanbB_B591LBxDeFkOe1QM7m1lZpLpV-BniK785ZMNmfjqTfSpXT2N2E53mjZn1MTmRhi2tsBhrlvm6VpvQES8_TSL8bOUhDOYjhnFYB_qpua_ykrmm1DAf8wDWBapx8gYRN8WmbeAxY6Q8bnU9CsBM_jeEzr-CDP73DuFZDX0cpf_AFYBYL7DNU_ju6ufP4HwGSfuGJXcTXqfQh5eUoAZuG_YgSC2DPoKPtKl1QD_oy1k-g6StkKWjGe8pfmx4FyqfTTV3bo-Uthr5RQ0CjLKseOnwuHt6J5GOcpttgA3GnSUxETUq5qMOenWnDA`
+	userNameVerifier, err := v.ParseToken(token)
+	if err != nil {
+		t.Fatalf("Verifier token : %v", err)
+	}
+	want := "duanxiaoduan"
+	if want != userNameVerifier {
+		t.Fatalf("token err")
+	}
+}