觉得这个经验还蛮有意思??的,整理一篇文章分享给大家。jwt的问题首先说明jwt的问题,也就是更换jwt的原因:jwt不能在服务端主动退出jwt不能使发出的token失效,只能等到token过期jwt携带了大量的用户扩展信息jwt的请求流程图gtoken的优势gtoken的请求流程和jwt基本一样。gtoken的好处就是可以帮我们解决jwt的问题,而且还提供了很好用的特性,比如:gtoken支持单点应用使用内存存储,支持个人项目文件存储,也支持企业集群使用redis存储,完全适合个人和企业生产级使用;有效避免jwt服务器无法退出的问题;解决jwt无法使下发的token失效,只能等到token过期的问题;将用户扩展信息存储在服务器上,有效避免jwt携带大量用户扩展信息导致传输效率降低的问题;有效避免jwt要求客户端实现续费功能,增加客户端的复杂度;支持服务端自动续费,客户端无需关心续费逻辑;注意问题支持服务端缓存的自动更新功能,不需要通过refresh_token来刷新token,简化了客户端版本的操作。注:gtokenv1.5.0完全兼容GoFramev2.0.0;GoFramev1.X.X请使用GfTokenv1.4.X相关版本TIPS:我下面的演示demo和源码阅读都是基于v1.4.x版本。后续会更新视频教程,带您领略最新版goframe和gtoken教程。有需要的朋友可以关注我。Demodemo下面的demodemo可以复制到本地的main.go文件中执行。更新依赖包时一定要注意版本。让我们把重点放在我们踩过的陷阱上。Login方法会要求我们返回两个值:第一个值对应userKey,然后我们可以根据userKey获取token。第二个值对应数据,是interface{}类型。我们可以在这里定义,比如Userid,用户名等数据。先有这个概念就够了。为了让大家更好的理解,文末带大家阅读源码。入门示例代码片段的关键逻辑已被注释。包mainimport("github.com/goflyfox/gtoken/gtoken""github.com/gogf/gf/frame/g""github.com/gogf/gf/net/ghttp""github.com/gogf/gf/os/glog")varTestServerNamestring//varTestServerNamestring="gtoken"funcmain(){glog.Info("########servicestart...")g.Cfg().SetPath("示例/示例")s:=g.Server(TestServerName)initRouter(s)glog.Info("########servicefinish.")s.Run()}vargfToken*gtoken.GfToken/*统一路由注册*/funcinitRouter(s*ghttp.Server){//非认证接口s.Group("/",func(group*ghttp.RouterGroup){group.Middleware(CORS)//调试路由组。ALL("/hello",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("hello"))})})//认证接口loginFunc:=Login//启动gtokengfToken:=>oken.GfToken{ServerName:TestServerName,LoginPath:"/login",LoginBeforeFunc:loginFunc,LogoutPath:"/user/logout",AuthExcludePaths:g.SliceStr{"/user/info","/system/user/info"},//不要截取路径/user/info,/system/user/info,/system/user,MultiLogin:g.Config().GetBool("gToken.MultiLogin"),}s.Group("/",func(group*ghttp.RouterGroup){group.Middleware(CORS)gfToken.Middleware(group)group.ALL("/system/user",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("系统用户"))})group.ALL("/user/data",func(r*ghttp.Request){r.Response.WriteJson(gfToken.GetTokenData(r))})group.ALL("/user/info",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("userinfo"))})group.ALL("/system/user/info",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("systemuserinfo"))})})//启动gtokengfAdminToken:=>oken.GfToken{ServerName:TestServerName,//Timeout:10*1000,LoginPath:"/login",LoginBeforeFunc:loginFunc,LogoutPath:"/user/logout",AuthExcludePaths:g.SliceStr{"/admin/user/info","/admin/system/user/info"},//不要抠路径/user/info,/system/user/info,/system/用户,MultiLogin:g.Config().GetBool("gToken.MultiLogin"),}s.Group("/admin",func(group*ghttp.RouterGroup){group.Middleware(CORS)gfAdminToken.Middleware(group)group.ALL("/system/user",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("systemuser"))})group.ALL("/user/info",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("userinfo"))})group.ALL("/system/user/info",func(r*ghttp.Request){r.Response.WriteJson(gtoken.Succ("系统用户信息"))})})}funcLogin(r*ghttp.Request)(string,interface{}){username:=r.GetString("username")passwd:=r.GetString("passwd")ifusername==""||passwd==""{r.Response.WriteJson(gtoken.Fail("账号或密码错误."))r.ExitAll()}returnusername,"1"/**返回的第一个参数对应:userKey返回的第二个参数对应:data{"code":0,"msg":"success","data":{"createTime":1652838582190,"data":"1","refreshTime":1653270582190,"userKey":"王中阳","uuid":"ac75676efeb906f9959cf35f779a1d38"}}*/}//跨域funcCORS(r*ghttp.Request){r.Response.CORSDefault()r.Middleware.Next()}运行效果启动项目:访问非认证接口:返回成功未登录时访问认证接口:返回错误提示请求登录接口:返回token再次带上token访问认证接口:返回成功并且运行过了主流程,分析源码就这么简单。下面就带大家分析一下源码,了解一下作者是如何设计的。刷新token首先我觉得gtoken好的设计思路不是用refresh_token来刷新token,而是在服务端主动刷新。在源码的getToken方法中更新了refreshTime和createTime。更新createTime为当前时间,refreshTime为当前时间+自定义刷新时间。如下图所示,每次执行validToken都会调用getToken方法来验证token,即每次验证token的有效性,如果满足刷新token有效期的条件,这样就会进行刷新操作(刷新token的过期时间,token的值保持不变),token被刷新没有任何意义。GfToken结构我们再看一下GfToken结构,更好的理解作者的设计思路:因为CacheMode支持redis,也就意味着支持集群模式。当我们启动gtoken时,我们只需要设置登录和注销路径即可。另外,login和logout都提供了BeforeFunc和AfterFunc,方便我们明确定义使用场景。//GfTokengtokenstructtypeGfTokenstruct{//GoFrame服务器名称ServerNamestring//缓存模式1gcache2gredisdefault1CacheModeint8//缓存键CacheKeystring//默认超时时间为10天(毫秒)Timeoutint//缓存默认刷新时间为超时时间的一半(毫秒)MaxRefreshint//Token分隔符TokenDelimiterstring//Token加密密钥EncryptKey[]byte//鉴权失败中文提示AuthFailMsgstring//是否支持多终端登录,默认isfalseMultiLoginbool//是否全局认证,兼容历史版本,过时的GlobalMiddlewarebool//中间件类型1GroupMiddleware2BindMiddleware3GlobalMiddlewareMiddlewareTypeuint//登录路径LoginPathstring//登录验证方式返回userKey用户ID如果userKey为空,结束LoginBeforeFunc的执行func(r*ghttp.Request)(string,interface{})//登录返回方法LoginAfterFuncfunc(r*ghttp.Requestst,respDataResp)//注销地址LogoutPathstring//注销验证方法返回true继续执行,否则结束执行LogoutBeforeFuncfunc(r*ghttp.Request)bool//注销返回方法LogoutAfterFuncfunc(r*ghttp.Request,respDataResp)//拦截地址AuthPathsg.SliceStr//拦截排除地址AuthExcludePathsg.SliceStr//鉴权验证方法返回true继续执行,否则结束执行AuthBeforeFuncfunc(r*ghttp.Request)bool//鉴权返回方法AuthAfterFuncfunc(r*ghttp.Request,respDataResp)}思考题I有N个子系统如何使用gtoken实现sso登录?也就是说,如果一个子系统登录了,其他所有子系统都自动登录,不需要第二次登录?想想……我想出的解决办法是配合cookie:各个子系统的二级域名不一致,但是主域名是一样的。登录后,我将token写入主域名的cookie中进行分享。前端网站通过cookieRequest服务接口获取token。同时,刷新token后,cookie的有效期也要刷新,避免cookie获取token失败。进一步阅读源码再次仔细阅读源码,我们发现了一个适合刷新cookie有效期的场景:AuthAfterFunc,我们可以重写这个方法实现验证通过后的操作:如果token验证有效、刷新cookie有效期;如果验证无效则自定义返回信息。(经常我们自己项目中的code代码和gtoken的定义不一致,但是gtoken支持很方便的重写返回值)综上所述,在我们项目之前,我们是使用jwt实现sso登录的。刚拿到rewrite的需求的时候,我们也是一头雾水的Foggy。在仔细阅读gtoken源码之前,我设计了refresh_token刷新策略。仔细阅读了源码,发现真香。这段经历最大的收获是:遇到不容易解决的问题时,带着问题阅读源码是一种非常高效的方式。本文转载自微信公众号《程序员的升级打怪之旅》,作者“王中阳围棋”,可通过以下二维码关注。转载本文请联系《程序员升级打怪之旅》公众号。