소스 검색

增加令牌自动续期功能

pixel 4 년 전
부모
커밋
01d8a3d14b
7개의 변경된 파일62개의 추가작업 그리고 55개의 파일을 삭제
  1. 5 5
      server/api/v1/sys_user.go
  2. 37 21
      server/middleware/jwt.go
  3. 2 0
      server/model/request/jwt.go
  4. 7 4
      server/service/jwt_black_list.go
  5. 0 8
      web/src/permission.js
  6. 1 10
      web/src/store/module/user.js
  7. 10 7
      web/src/utils/request.js

+ 5 - 5
server/api/v1/sys_user.go

@@ -88,10 +88,12 @@ func tokenNext(c *gin.Context, user model.SysUser) {
 		UUID:        user.UUID,
 		ID:          user.ID,
 		NickName:    user.NickName,
+		Username:    user.Username,
 		AuthorityId: user.AuthorityId,
+		BufferTime:  60*60*24, // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失
 		StandardClaims: jwt.StandardClaims{
 			NotBefore: time.Now().Unix() - 1000,       // 签名生效时间
-			ExpiresAt: time.Now().Unix() + 60*60*24*7, // 过期时间 一周
+			ExpiresAt: time.Now().Unix() + 60*60*24*7, // 过期时间 7天
 			Issuer:    "qmPlus",                       // 签名的发行者
 		},
 	}
@@ -108,11 +110,9 @@ func tokenNext(c *gin.Context, user model.SysUser) {
 		}, c)
 		return
 	}
-	var loginJwt model.JwtBlacklist
-	loginJwt.Jwt = token
 	err, jwtStr := service.GetRedisJWT(user.Username)
 	if err == redis.Nil {
-		if err := service.SetRedisJWT(loginJwt, user.Username); err != nil {
+		if err := service.SetRedisJWT(token, user.Username); err != nil {
 			response.FailWithMessage("设置登录状态失败", c)
 			return
 		}
@@ -130,7 +130,7 @@ func tokenNext(c *gin.Context, user model.SysUser) {
 			response.FailWithMessage("jwt作废失败", c)
 			return
 		}
-		if err := service.SetRedisJWT(loginJwt, user.Username); err != nil {
+		if err := service.SetRedisJWT(jwtStr, user.Username); err != nil {
 			response.FailWithMessage("设置登录状态失败", c)
 			return
 		}

+ 37 - 21
server/middleware/jwt.go

@@ -9,6 +9,7 @@ import (
 	"gin-vue-admin/service"
 	"github.com/dgrijalva/jwt-go"
 	"github.com/gin-gonic/gin"
+	"strconv"
 	"time"
 )
 
@@ -16,9 +17,6 @@ func JWTAuth() gin.HandlerFunc {
 	return func(c *gin.Context) {
 		// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localSstorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
 		token := c.Request.Header.Get("x-token")
-		modelToken := model.JwtBlacklist{
-			Jwt: token,
-		}
 		if token == "" {
 			response.Result(response.ERROR, gin.H{
 				"reload": true,
@@ -26,7 +24,7 @@ func JWTAuth() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
-		if service.IsBlacklist(token, modelToken) {
+		if service.IsBlacklist(token) {
 			response.Result(response.ERROR, gin.H{
 				"reload": true,
 			}, "您的帐户异地登陆或令牌失效", c)
@@ -50,6 +48,24 @@ func JWTAuth() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
+		if claims.ExpiresAt - time.Now().Unix()<claims.BufferTime {
+			claims.ExpiresAt = time.Now().Unix() + 60*60*24*7
+			newToken,_ := j.CreateToken(*claims)
+			newClaims,_ := j.ParseToken(newToken)
+			c.Header("new-token",newToken)
+			c.Header("new-expires-at",strconv.FormatInt(newClaims.ExpiresAt,10))
+			if global.GVA_CONFIG.System.UseMultipoint {
+				err,RedisJwtToken := service.GetRedisJWT(newClaims.Username)
+				if err!=nil {
+					global.GVA_LOG.Error(err)
+				}else{
+					service.JsonInBlacklist(model.JwtBlacklist{Jwt: RedisJwtToken})
+					//当之前的取成功时才进行拉黑操作
+				}
+				// 无论如何都要记录当前的活跃状态
+				_ = service.SetRedisJWT(newToken,newClaims.Username)
+			}
+		}
 		c.Set("claims", claims)
 		c.Next()
 	}
@@ -111,20 +127,20 @@ func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
 }
 
 // 更新token
-func (j *JWT) RefreshToken(tokenString string) (string, error) {
-	jwt.TimeFunc = func() time.Time {
-		return time.Unix(0, 0)
-	}
-	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
-		return j.SigningKey, nil
-	})
-	if err != nil {
-		return "", err
-	}
-	if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
-		jwt.TimeFunc = time.Now
-		claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
-		return j.CreateToken(*claims)
-	}
-	return "", TokenInvalid
-}
+//func (j *JWT) RefreshToken(tokenString string) (string, error) {
+//	jwt.TimeFunc = func() time.Time {
+//		return time.Unix(0, 0)
+//	}
+//	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
+//		return j.SigningKey, nil
+//	})
+//	if err != nil {
+//		return "", err
+//	}
+//	if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
+//		jwt.TimeFunc = time.Now
+//		claims.StandardClaims.ExpiresAt = time.Now().Unix() + 60*60*24*7
+//		return j.CreateToken(*claims)
+//	}
+//	return "", TokenInvalid
+//}

+ 2 - 0
server/model/request/jwt.go

@@ -9,7 +9,9 @@ import (
 type CustomClaims struct {
 	UUID        uuid.UUID
 	ID          uint
+	Username    string
 	NickName    string
 	AuthorityId string
+	BufferTime  int64
 	jwt.StandardClaims
 }

+ 7 - 4
server/service/jwt_black_list.go

@@ -3,6 +3,7 @@ package service
 import (
 	"gin-vue-admin/global"
 	"gin-vue-admin/model"
+	"time"
 )
 
 // @title    JsonInBlacklist
@@ -23,8 +24,8 @@ func JsonInBlacklist(jwtList model.JwtBlacklist) (err error) {
 // @param     jwtList         model.JwtBlacklist
 // @return    err             error
 
-func IsBlacklist(jwt string, jwtList model.JwtBlacklist) bool {
-	isNotFound := global.GVA_DB.Where("jwt = ?", jwt).First(&jwtList).RecordNotFound()
+func IsBlacklist(jwt string) bool {
+	isNotFound := global.GVA_DB.Where("jwt = ?", jwt).First(&model.JwtBlacklist{}).RecordNotFound()
 	return !isNotFound
 }
 
@@ -47,7 +48,9 @@ func GetRedisJWT(userName string) (err error, redisJWT string) {
 // @param     userName        string
 // @return    err             error
 
-func SetRedisJWT(jwtList model.JwtBlacklist, userName string) (err error) {
-	err = global.GVA_REDIS.Set(userName, jwtList.Jwt, 1000*1000*1000*60*60*24*7).Err()
+func SetRedisJWT(jwt string, userName string) (err error) {
+	// 此处过期时间等于jwt过期时间
+	timer := 60*60*24*7*time.Second
+	err = global.GVA_REDIS.Set(userName, jwt, timer).Err()
 	return err
 }

+ 0 - 8
web/src/permission.js

@@ -7,14 +7,6 @@ const whiteList = ['login', 'register']
 
 router.beforeEach(async(to, from, next) => {
     const token = store.getters['user/token']
-        // if (token) {
-        //     const expiresAt = store.getters['user/expiresAt']
-        //     const nowUnix = new Date().getTime()
-        //     const hasExpires = (expiresAt - nowUnix) < 0
-        //     if (hasExpires) {
-        //         store.dispatch['user/claerAll']
-        //     }
-        // }
         // 在白名单中的判断情况
     if (whiteList.indexOf(to.name) > -1) {
         if (token) {

+ 1 - 10
web/src/store/module/user.js

@@ -11,7 +11,6 @@ export const user = {
             authority: "",
         },
         token: "",
-        expiresAt: ""
     },
     mutations: {
         setUserInfo(state, userInfo) {
@@ -22,14 +21,9 @@ export const user = {
             // 这里的 `state` 对象是模块的局部状态
             state.token = token
         },
-        setExpiresAt(state, expiresAt) {
-            // 这里的 `state` 对象是模块的局部状态
-            state.expiresAt = expiresAt
-        },
         LoginOut(state) {
             state.userInfo = {}
             state.token = ""
-            state.expiresAt = ""
             router.push({ name: 'login', replace: true })
             sessionStorage.clear()
             window.location.reload()
@@ -45,7 +39,6 @@ export const user = {
             const res = await login(loginInfo)
             commit('setUserInfo', res.data.user)
             commit('setToken', res.data.token)
-            commit('setExpiresAt', res.data.expiresAt)
             if (res.code == 0) {
                 const redirect = router.history.current.query.redirect
                 if (redirect) {
@@ -69,8 +62,6 @@ export const user = {
         token(state) {
             return state.token
         },
-        expiresAt(state) {
-            return state.expiresAt
-        }
+
     }
 }

+ 10 - 7
web/src/utils/request.js

@@ -21,13 +21,13 @@ const showLoading = () => {
 }
 
 const closeLoading = () => {
-    acitveAxios--
-    if (acitveAxios <= 0) {
-        clearTimeout(timer)
-        loadingInstance && loadingInstance.close()
+        acitveAxios--
+        if (acitveAxios <= 0) {
+            clearTimeout(timer)
+            loadingInstance && loadingInstance.close()
+        }
     }
-}
-//http request 拦截器
+    //http request 拦截器
 service.interceptors.request.use(
     config => {
         showLoading()
@@ -37,7 +37,7 @@ service.interceptors.request.use(
         config.headers = {
             'Content-Type': 'application/json',
             'x-token': token,
-            'x-user-id':user.ID
+            'x-user-id': user.ID
         }
         return config;
     },
@@ -57,6 +57,9 @@ service.interceptors.request.use(
 service.interceptors.response.use(
     response => {
         closeLoading()
+        if (response.headers["new-token"]) {
+            store.commit('user/setToken', response.headers["new-token"])
+        }
         if (response.data.code == 0 || response.headers.success === "true") {
             return response.data
         } else {