为什么有这篇小作文?
最近要给自己的项目加上token自动续期,但是在网上搜的写法五花八门,有的光前端部分就写了几百行代码,我看着费劲,摸了半天也没有实现,所以决定自己造轮子
项目构成
- 后端部分:使用golang的gin框架起的服务
- 前端部分:vue+elementui
先说后端部分,后端逻辑相对前端简单点,关键三步
登陆接口生成双token
1 | "github.com/dgrijalva/jwt-go" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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用的 }) } } } |
刷新token的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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, }) } } } |
路由中间件里验证token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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 } |
总结一下:后端部分三步,1.登陆时生成双token,2,路由中间件里验证token,过期时返回201状态码(201是我私人定的,并不是行业标准)。3,刷新token的方法里也和登陆接口一样返回双token
前端部分
前端部分在axios封装时候加拦截器判断token是否过期,我这里跟别人写的最大的不同点是:我创建了两个axios对象,一个正常数据请求用(server),另一个专门刷新token用(serverRefreshToken),这样写的好处是省去了易错的判断逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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 server |
总结一下,前端部分:1,正常数据请求和刷新token用的请求分开了,各司其职。省去复杂的判断。2,获取新的token和refreshToken后更新原来旧的token和refreshToken。(完结)
到此这篇关于vue中双token和无感刷新token的区别的文章就介绍到这了,更多相关vue中双token和无感刷新token内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!