双token和无感刷新token

项目构成

  • 后端部分:使用golang的gin框架起的服务
  • 前端部分:vue+elementui

先说后端部分,后端逻辑相对前端简单点,关键两步

  1. 登陆接口生成双token
    arduino复制代码"github.com/dgrijalva/jwt-go"
    go复制代码func (this UserController) DoLogin(ctx *gin.Context) {
        username := ctx.Request.FormValue("username")
        passWord := ctx.Request.FormValue("password")
        passMd5 := middlewares.CreateMD5(passWord)
        expireTime := time.Now().Add(10 * time.Second).Unix() //token过期时间10秒,主要是测试方便
        refreshTime := time.Now().Add(20 * time.Second).Unix() //刷新的时间限制,超过20秒重新登录
        user := []modules.User{}
        err := modules.DB.Model(&modules.User{}).Where("username = ? AND password = ?", username, passMd5).Find(&user).Error
        if err != nil || len(user) == 0 {
            ctx.JSON(400, gin.H{
                "success": false,
                "message": "用户名或密码错误",
            })
        } else {
            println("expireTime", string(rune(expireTime)))
            myClaims := MyClaims{
                user.Id,
                jwt.StandardClaims{
                    ExpiresAt: expireTime,
                },
            }
            myClaimsRefrrsh := MyClaims{
                user.Id,
                jwt.StandardClaims{
                    ExpiresAt: refreshTime,
                },
            }
            jwtKey := []byte("lyf123456")
            tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)
            tokenStr, err := tokenObj.SignedString(jwtKey)
            tokenFresh := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsRefrrsh)
            tokenStrRefresh, err2 := tokenFresh.SignedString(jwtKey)
            if err != nil && err2 != nil {
                ctx.JSON(200, gin.H{
                    "message": "生成token失败",
                    "success": false,
                })
            } else {
                ctx.JSON(200, gin.H{
                    "message":      "登录成功",
                    "success":      true,
                    "token":        tokenStr,//数据请求的token
                    "refreshToken": tokenStrRefresh,//刷新token用的
                })
            }
        }
    }
    

  2. 刷新token的方法
    go复制代码func (this UserController) RefrshToken(ctx *gin.Context) {
        tokenData := ctx.Request.Header.Get("Authorization") //这里是个关键点,刷新token时也要带上token,不过这里是前端传的refreshToken
        if tokenData == "" {
            ctx.JSON(401, gin.H{
                "message": "token为空",
                "success": false,
            })
            ctx.Abort()
            return
        }
        tokenStr := strings.Split(tokenData, " ")[1]
        _, claims, err := middlewares.ParseToken(tokenStr)
        expireTime := time.Now().Add(10 * time.Second).Unix()
        refreshTime := time.Now().Add(20 * time.Second).Unix()
        if err != nil {
            ctx.JSON(400, gin.H{
                "success": false,
                "message": "token传入错误",
            })
        } else {
            myClaims := MyClaims{
                claims.Uid,
                jwt.StandardClaims{
                    ExpiresAt: expireTime,
                },
            }
            myClaimsRefrrsh := MyClaims{
                claims.Uid,
                jwt.StandardClaims{
                    ExpiresAt: refreshTime,
                },
            }
            jwtKey := []byte("lyf123456")
            tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)
            tokenStr, err := tokenObj.SignedString(jwtKey)
            tokenFresh := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsRefrrsh)
            tokenStrRefresh, err2 := tokenFresh.SignedString(jwtKey)
            if err != nil && err2 != nil {
                ctx.JSON(400, gin.H{
                    "message": "生成token失败",
                    "success": false,
                })
            } else {
                ctx.JSON(200, gin.H{
                    "message":      "刷新token成功",
                    "success":      true,
                    "token":        tokenStr,
                    "refreshToken": tokenStrRefresh,
                })
            }
        }
    }
    

  3. 路由中间件里验证token
    go复制代码package middlewares
    
    import (
        "strings"
    
        "github.com/dgrijalva/jwt-go"
        "github.com/gin-gonic/gin"
    )
    
    type MyClaims struct {
        Uid int
        jwt.StandardClaims
    }
    
    func AuthMiddleWare(c *gin.Context) {
        tokenData := c.Request.Header.Get("Authorization")
        if tokenData == "" {
            c.JSON(401, gin.H{
                "message": "token为空",
                "success": false,
            })
            c.Abort()
            return
        }
        tokenStr := strings.Split(tokenData, " ")[1]
        token, _, err := ParseToken(tokenStr)
        if err != nil || !token.Valid {
             // 这里我感觉觉是个关键点,我看别人写的,过期了返回401,但是前端的axios的响应拦截器里捕获不到,所以我用201状态码,
            c.JSON(201, gin.H{
                "message": "token已过期",
                "success": false,
            })
            c.Abort()
            return
        } else {
            c.Next()
        }
    }
    
    func ParseToken(tokenStr string) (*jwt.Token, *MyClaims, error) {
        jwtKey := []byte("lyf123456")
        // 解析token
        myClaims := &MyClaims{}
        token, err := jwt.ParseWithClaims(tokenStr, myClaims, func(token *jwt.Token) (interface{}, error) {
            return jwtKey, nil
        })
        return token, myClaims, err
    }
    

    前端部分

    前端部分在axios封装时候加拦截器判断token是否过期,我这里跟别人写的最大的不同点是:我创建了两个axios对象,一个正常数据请求用(server),另一个专门刷新token用(serverRefreshToken),这样写的好处是省去了易错的判断逻辑

    javascript复制代码import axios from 'axios'
    import { ElMessage } from 'element-plus'
    import router from '../router'
    //数据请求用
    const server=axios.create({
      baseURL:'/shopApi',
      timeout:5000
    })
    // 刷新token专用
    const serverRefreshToken=axios.create({
      baseURL:'/shopApi',
      timeout:5000
    })
    //获取新token的方法
    async function getNewToken(){
      let res=await serverRefreshToken.request({
        url:`/admin/refresh`,
        method:"post",
      })
      if(res.status==200){
        sessionStorage.setItem("token",res.data.token)
        sessionStorage.setItem("refreshToken",res.data.refreshToken)
        return true
      }else{
        ElMessage.error(res.data.message)
        router.push('/login')
        return false
      }
    }
    //这里是正常获取数据用的请求拦截器,主要作用是给所有请求的请求头里加上token
    server.interceptors.request.use(config=>{
      let token=""
      token=sessionStorage.getItem("token")
      if(token){
        config.headers.Authorization="Bearer "+token
      }
      return config
    },error=>{
      Promise.reject(error)
    })
    //这里是正常获取数据用的响应拦截器,正常数据请求都是200状态码,当拦截到201状态码时,代表token过期了,
    // 应热心小伙伴的提醒,加上防止token过期后正好短时间内多个请求重复刷新token,刷新token成功再请求
    let isRefreshing=false
    let refreshFnArr=[]
    server.interceptors.response.use(async(res)=>{
      if(res.status==201){
        if(!isRefreshing){
        // 如果正好段时间内触发了多个请求
          isRefreshing=true
          let bl=await getNewToken()
          if(bl){
            refreshFnArr.forEach(fn=>{
              fn()
            })
            refreshFnArr=[]
            res= await server.request(res.config)
            isRefreshing=false
          }
        }else{
          return new Promise(resolve=>{
            refreshFnArr.push(
              ()=>{
                resolve(res.config)
              }
            )
          })
        }
      }
      return res
    },error=>{
      if(error.response.status==500||error.response.status==401||error.response.status==400){
        router.push('/login')
        ElMessage.error(error.response.data.message)
        Promise.reject(error)
      }
      
    })
    //这里是刷新token专用的axios对象,他的作用是给请求加上刷新token专用的refreshToken
    serverRefreshToken.interceptors.request.use(config=>{
      let token=""
      token=sessionStorage.getItem("refreshToken")
      if(token){
        config.headers.Authorization="Bearer "+token
      }
      return config
    },error=>{
      Promise.reject(error)
    })
    export default serve

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值