Code Monkey home page Code Monkey logo

gtoken's Introduction

gtoken

介绍

基于GoFrame框架的token插件,通过服务端验证方式实现token认证;已完全可以支撑线上token认证,通过Redis支持集群模式;使用简单,大家可以放心使用;

全面适配GoFrame v2.0.0 ; GoFrame v1.X.X 请使用gtoken v1.4.X相关版本;

gtoken优势

  1. gtoken支撑单点应用测试使用内存存储,支持个人项目文件存储,也支持企业集群使用redis存储;完全适用于企业生产级使用;
  2. 有效的避免了jwt服务端无法退出问题;
  3. 解决jwt无法作废已颁布的令牌,只能等到令牌过期问题;
  4. 通过用户扩展信息存储在服务端,有效规避了jwt携带大量用户扩展信息导致降低传输效率问题;
  5. 有效避免jwt需要客户端实现续签功能,增加客户端复杂度;支持服务端自动续期,客户端不需要关心续签逻辑;

特性说明

  1. 支持token认证,不强依赖于session和cookie,适用jwt和session认证所有场景;
  2. 支持单机gcache和集群gredis模式;
# 缓存模式 1 gcache 2 gredis 3 fileCache
CacheMode = 2
  1. 支持服务端缓存自动续期功能
// 注:通过MaxRefresh,默认当用户第五天访问时,自动续期
// 超时时间 默认10天
Timeout int
// 缓存刷新时间 默认为超时时间的一半
MaxRefresh int
  1. 支持分组拦截、全局拦截、深度路径拦截,便于根据个人需求定制拦截器;建议使用分组拦截方式;
  2. 框架使用简单,只需要设置登录验证方法以及登录、登出路径即可;
  3. gtoken v1.4.0版本开始支持分组中间件方式实现,但依然兼容全局和深度中间件实现方式;
  4. gtoken v1.5.0全面适配GoFrame v2.0.0 ; GoFrame v1.X.X 请使用GfToken v1.4.X相关版本;

安装教程

  • gopath模式: go get github.com/goflyfox/gtoken
  • 或者 使用go.mod添加 :require github.com/goflyfox/gtoken latest

分组中间件实现

GoFrame官方推荐使用Group方式实现路由和中间件;

使用说明

推荐使用分组方式实现

	// 启动gtoken
	gfToken := &gtoken.GfToken{
		LoginPath:        "/login",
		LoginBeforeFunc:  loginFunc,
		LogoutPath:       "/user/logout",
	}
	s.Group("/admin", func(group *ghttp.RouterGroup) {
		group.Middleware(CORS)
		gfToken.Middleware(group)

		group.ALL("/system/user", func(r *ghttp.Request) {
			r.Response.WriteJson(gtoken.Succ("system user"))
		})
		………………
	})

登录方法实现,通过username返回空或者r.ExitAll()\r.Exit()处理认证失败;

特别提示:这里注册的路径严格按照GF group方式,所以注册的路径是/admin/login/admin/user/logout

func Login(r *ghttp.Request) (string, interface{}) {
	username := r.GetPostString("username")
	passwd := r.GetPostString("passwd")

	// TODO 进行登录校验
	if username == "" || passwd == "" {
		r.Response.WriteJson(gtoken.Fail("账号或密码错误."))
		r.ExitAll()
	}
	
	// 第一个字段是用户唯一标识,第二个字段是扩展参数user data
	return username, ""
}

通过gtoken.GetTokenData(r)获取登录信息

路径拦截规则

    AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, // 不拦截路径  /user/info,/system/user/info,/system/user,
  1. 分组中间件实现,不需要设置AuthPaths认证路径,设置也没有作用,需要认证路径为该分组下所有路由
  2. 使用分组拦截的是通过GoFrame的group.Middleware(authMiddleware)方法,对该分组下的所有路由进行拦截;
  3. 对登录接口路径loginPath和登出接口路径logoutPath做拦截认证放行,登出放行是为了避免认证过期无法登出情况;
  4. 严格按照GoFrame分组中间件拦截优先级;如果使用跨域中间件,建议放在跨域中间件之后;
  5. 如果配置AuthExcludePaths路径,会将配置的不拦截路径排除;

逻辑测试

参考sample项目,先运行main.go,然后可运行api_test.go进行测试并查看结果;验证逻辑说明:

  1. 访问用户信息,提示未携带token
  2. 调用登录后,携带token访问正常
  3. 调用登出提示成功
  4. 携带之前token访问,提示未登录
=== RUN   TestAdminSystemUser
    api_admin_test.go:22: 1. not login and visit user
    api_admin_test.go:29: {"code":-401,"msg":"请求错误或登录超时","data":""}
    api_admin_test.go:42: 2. execute login and visit user
    api_admin_test.go:45: {"code":0,"msg":"success","data":"system user"}
    api_admin_test.go:51: 3. execute logout
    api_admin_test.go:54: {"code":0,"msg":"success","data":"Logout success"}
    api_admin_test.go:60: 4. visit user
    api_admin_test.go:65: {"code":-401,"msg":"请求错误或登录超时","data":""}

全局中间件实现

使用说明

只需要配置登录路径、登出路径、拦截路径以及登录校验实现即可

	// 启动gtoken
	gtoken := &gtoken.GfToken{
		LoginPath:       "/login",
		LoginBeforeFunc: loginFunc,
		LogoutPath:      "/user/logout",
		AuthPaths:        g.SliceStr{"/user", "/system"}, // 这里是按照前缀拦截,拦截/user /user/list /user/add ...
		GlobalMiddleware: true,                           // 开启全局拦截,默认关闭
	}
	gtoken.Start()

登录方法实现,通过username返回空或者r.ExitAll()\r.Exit()处理认证失败;

func Login(r *ghttp.Request) (string, interface{}) {
	username := r.GetPostString("username")
	passwd := r.GetPostString("passwd")

	// TODO 进行登录校验
	if username == "" || passwd == "" {
		r.Response.WriteJson(gtoken.Fail("账号或密码错误."))
		r.ExitAll()
	}
	
	// 第一个字段是用户唯一标识,第二个字段是扩展参数user data
	return username, ""
}

通过gtoken.GetTokenData(r)获取登录信息

路径拦截规则

    AuthPaths:        g.SliceStr{"/user", "/system"},             // 这里是按照前缀拦截,拦截/user /user/list /user/add ...
    AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, // 不拦截路径  /user/info,/system/user/info,/system/user,
    GlobalMiddleware: true,                           // 开启全局拦截,默认关闭
  1. GlobalMiddleware:true全局拦截的是通过GF的BindMiddleware方法创建拦截/*
  2. GlobalMiddleware:false路径拦截的是通过GF的BindMiddleware方法创建拦截/user*和/system/*
  3. 按照中间件优先级路径拦截优先级很高;如果先实现部分中间件在认证前处理需要切换成全局拦截器,严格按照注册顺序即可;
  4. 程序先处理认证路径,如果满足;再排除不拦截路径;
  5. 如果只想用排除路径功能,将拦截路径设置为/*即可;

逻辑测试

参考sample1项目,先运行main.go,然后可运行api_test.go进行测试并查看结果;验证逻辑说明:

  1. 访问用户信息,提示未携带token
  2. 调用登录后,携带token访问正常
  3. 调用登出提示成功
  4. 携带之前token访问,提示未登录
=== RUN   TestSystemUser
    api_test.go:43: 1. not login and visit user
    api_test.go:50: {"code":-401,"msg":"请求错误或登录超时","data":""}
    api_test.go:63: 2. execute login and visit user
    api_test.go:66: {"code":0,"msg":"success","data":"system user"}
    api_test.go:72: 3. execute logout
    api_test.go:75: {"code":0,"msg":"success","data":"Logout success"}
    api_test.go:81: 4. visit user
    api_test.go:86: {"code":-401,"msg":"请求错误或登录超时","data":""}

返回码及配置项

  1. 正常操作成功返回0
  2. 未登录访问需要登录资源返回401
  3. 程序异常返回-99,如编解码错误等
SUCCESS      = 0  // 正常
FAIL         = -1  // 失败
ERROR        = -99  // 异常
UNAUTHORIZED = -401  // 未认证

配置项说明

具体可参考GfToken结构体,字段解释如下:

名称 配置字段 说明 分组中间件 全局中间件
服务名 ServerName 默认空即可 支持 支持
缓存模式 CacheMode 1 gcache 2 gredis 3 fileCache 默认1 支持 支持
缓存key CacheKey 默认缓存前缀GToken: 支持 支持
超时时间 Timeout 默认10天(毫秒) 支持 支持
缓存刷新时间 MaxRefresh 默认为超时时间的一半(毫秒) 支持 支持
Token分隔符 TokenDelimiter 默认_ 支持 支持
Token加密key EncryptKey 默认12345678912345678912345678912345 支持 支持
认证失败提示 AuthFailMsg 默认请求错误或登录超时 支持 支持
是否支持多端登录 MultiLogin 默认false 支持 支持
中间件类型 MiddlewareType 1、Group 2、Bind 3 、Global;
使用分组模式不需要设置
支持 支持
登录路径 LoginPath 登录接口路径 支持 支持
登录验证方法 LoginBeforeFunc 登录验证需要用户实现方法 支持 支持
登录返回方法 LoginAfterFunc 登录完成后调用 支持 支持
登出地址 LogoutPath 登出接口路径 支持 支持
登出验证方法 LogoutBeforeFunc 登出接口前调用 支持 支持
登出返回方法 LogoutAfterFunc 登出接口完成后调用 支持 支持
拦截地址 AuthPaths 此路径列表进行认证 不需要 支持
拦截排除地址 AuthExcludePaths 此路径列表不进行认证 支持 支持
认证验证方法 AuthBeforeFunc 拦截认证前后调用 支持 支持
认证返回方法 AuthAfterFunc 拦截认证完成后调用 支持 支持

文档

https://goframe.org/pages/viewpage.action?pageId=1115974

感谢

  1. gf框架 https://github.com/gogf/gf

项目支持

jflyfox

gtoken's People

Contributors

ddpmz avatar zcool321 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gtoken's Issues

第一次登录问题

场景:
第一次登录,支持多端登录
问题:
当支持多端登录的时候,在放进token前,会进行一次缓存检查,getToken, (gtoken.go 78行)
然后进入getCache里,在这个方法里(gtoken_cache.go 50行),由于缓存里没有key,会log error一下:
g.Log().Error(ctx, "[GToken]cache get error", err)
这样,会导致打印堆栈信息,给日志收集带来不必要的歧义,建议使用Info或者Notice

怎么适配gf init创建的项目

标准的 gf init 项目
api.v1.user.go
type UserLoginReq struct {
g.Meta path:"/user/login" tags:"user" method:"post" summary:"You first hello api"
Username string json:"username"
Password string json:"password"
}

type UserLoginRes struct {
g.Meta mime:"text/html" example:"string"
}

controller.user.go
func (c *cUser) Login(ctx context.Context, req *v1.UserLoginReq) (res *v1.UserLoginRes, err error) {
fmt.Println(req.Username, req.Password)
return
}

cmd.go
s.Group("/api", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
controller.User,
)
s.Run()
}

这种方式该怎么创建 gToken 实例呢?
LoginBeforeFunc: 该函数已经指定了参数类型的啊

使用websocket如何使用该验证?

使用websocket的时候,无法在head里面添加token,但是使用url添加的token,无法识别,解析出来的识别,这种怎么处理呢?

特性:支持websocket的验证

目前gtoken内部中间件方法auth里,没有针对websocket请求的验证,如果有了对websocket的验证支持,功能上就比较齐全了。
现在大一点的项目里,普通http请求和websocket请求一定是并存的,这样使用gtoken无法解决全场景问题,生产环境下比较难以真正的应用起来,所以建议作者能增加对websocket请求验证的支持。

gfile方法名替换问题

image
在 github.com/gogf/gf/v2 v2.0.0-rc3 以上的版本 已经没有 gfile.TempDir 该方法 导致运行报错
image

缓存超时时间刷新判断问题

image

nowTime 取的gtime.Now().Millisecond()值,范围[0,999]。refreshTime取的gtime.Now().Millisecond() + m.MaxRefresh,该值正常情况会>999,这样if gconv.Int64(refreshTime) == 0 || nowTime > gconv.Int(refreshTime) { 判断刷新缓存的逻辑就永远执行不到。

user auth uuid error错误

func (c *bToken) AuthAfterFunc(r *ghttp.Request, respData gtoken.Resp) { g.Log().Info(r.Context(), "这里是 gftoken 中间件*******") if respData.Success() { g.Log().Info(r.Context(), "每次都走到这里") //todo 往context里面注入用户信息 r.Middleware.Next() } else { response.Error(true, r, "用户信息验证失败", "") r.ExitAll() } }

下载报错[email protected]\net\gtrace\gtrace_baggage

go get -u github.com/goflyfox/gtoken/gtoken
go: found github.com/goflyfox/gtoken/gtoken in github.com/goflyfox/gtoken v1.4.2
go: gopkg.in/yaml.v3 upgrade => v3.0.0-20210107192922-496545a6307b
go: go.opentelemetry.io/otel upgrade => v0.20.0
go: github.com/gorilla/websocket upgrade => v1.4.2
go: go.opentelemetry.io/otel/trace upgrade => v0.20.0
go: golang.org/x/net upgrade => v0.0.0-20210726213435-c6fcb2dbf985
go: golang.org/x/text upgrade => v0.3.6
go: golang.org/x/sys upgrade => v0.0.0-20210630005230-0f9fa26af87c
go: github.com/grokify/html-strip-tags-go upgrade => v0.0.1
go: github.com/mattn/go-runewidth upgrade => v0.0.13
go: github.com/rivo/uniseg upgrade => v0.2.0

github.com/gogf/gf/net/gtrace

....\pkg\mod\github.com\gogf\[email protected]\net\gtrace\gtrace_baggage.go:40:10: undefined: "go.opentelemetry.io/otel/baggage".ContextWithValues
....\pkg\mod\github.com\gogf\[email protected]\net\gtrace\gtrace_baggage.go:51:10: undefined: "go.opentelemetry.io/otel/baggage".ContextWithValues
....\pkg\mod\github.com\gogf\[email protected]\net\gtrace\gtrace_baggage.go:58:9: undefined: "go.opentelemetry.io/otel/baggage".Set
....\pkg\mod\github.com\gogf\[email protected]\net\gtrace\gtrace_baggage.go:73:11: undefined: "go.opentelemetry.io/otel/baggage".Value
....\pkg\mod\github.com\gogf\[email protected]\net\gtrace\gtrace_span.go:20:32: cannot use opts (type []trace.SpanOption) as type []trace.SpanStartOption in argument to NewTracer().Tracer.Start

关于源码的一点疑问

func (m *GfToken) Logout(r *ghttp.Request) {
	if !m.LogoutBeforeFunc(r) {
		return
	}

	// 获取请求token
	respData := m.getRequestToken(r)
	if respData.Success() {
		// 删除token
		m.RemoveToken(r.Context(), respData.DataString())
	}

	m.LogoutAfterFunc(r, respData)
}

在源码中,登出是要调用LogoutBeforeFunc和LogoutAfterFunc两个方法的,而我在运用的时候是没有配置这两个方法的,这两个方法打印出来的值为nil,神奇的是这两个函数没有实现,程序却能正常运行,没有panic?
是否golang在函数为nil的时候会自动跳过不执行?

我在使用gtoken时候遇到了这个报错panic: reflect: Elem of invalid type func(*ghttp.Request) (string, interface {})

我使用了例子里的函数:

func Login(r *ghttp.Request) (string, interface{}) {
	username := r.GetPostString("username")
	passwd := r.GetPostString("passwd")

	// TODO 进行登录校验
	if username == "" || passwd == "" {
		r.Response.WriteJson(gtoken.Fail("账号或密码错误."))
		r.ExitAll()
	}

	return username, ""

在启动之后会报错退出。报错信息如下:

2021-08-04 16:34:36.982 [INFO] [GToken][params:{"AuthExcludePaths":"","AuthFailMsg":"请求错误或登录超时","AuthPaths":"","CacheKey":"GToken:","CacheMode":1,"EncryptKey":"12345678912345678912345678912345","LoginPath":"/login","Log"/user/logout","MiddlewareType":1,"MultiLogin":false,"Timeout":864000000,"TokenDelimiter":"_"}]start...  
panic: reflect: Elem of invalid type func(*ghttp.Request) (string, interface {})

我猜测是返回的第二个值的问题,看注释说的是
第二个字段是扩展参数user data
我想问一下这个user data应该是什么样的。

是否支持从cookie中获取token?

目前来看getRequestToken 方法,只能从header头和body或者url中获取,我有个多站点,二级域名和顶级域名共享cookie , 登录成功后把token设置到cookie中了,但是httponly了,前端js不能拿到cookie的token ,能否直接在getRequestToken 中增加一个从cookie中获取token

关于LoginPath的路由请求方法修改问题

你好,我想问下LoginPath的路径能不能指定请求方法?
我查了你的源代码用的BindHandler生成的路由,没办法指定请求的方法。因为不想开那么多的口,怕产生安全问题。

是否可以自定义返回结果

目前此框架返回的结果(至少默认的loginlogout)用的是gtoken自己的一套返回结果和错误码,该返回结果是否可以自定义(甚至错误信息也想自定义;-)

如何将响应结果的 msg 改为 message?

我知道在 gtoken_resp.go 文件中可以直接修改,有没有不修改库本身文件的方法?

type Resp struct {
	Code int         `json:"code"`
	Msg  string      `json:"message"`
	Data interface{} `json:"data"`
}

分组中间件只注册authMiddleware

  
// Middleware 绑定group
func (m *GfToken) Middleware(group *ghttp.RouterGroup) bool {
	if !m.InitConfig() {
		return false
	}
	// 设置为Group模式
	m.MiddlewareType = MiddlewareTypeGroup
	glog.Info("[GToken][params:" + m.String() + "]start... ")

	// 缓存模式
	if m.CacheMode > CacheModeRedis {
		glog.Error("[GToken]CacheMode set error")
		return false
	}
	// 登录
	if m.LoginPath == "" || m.LoginBeforeFunc == nil {
		glog.Error("[GToken]LoginPath or LoginBeforeFunc not set")
		return false
	}
	// 登出
	if m.LogoutPath == "" {
		glog.Error("[GToken]LogoutPath or LogoutFunc not set")
		return false
	}

	group.Middleware(m.authMiddleware)
	group.ALL(m.LoginPath, m.Login)
	group.ALL(m.LogoutPath, m.Logout)

	return true
}
`
不同模块调用分组中间件有个问题,每次都会重新注册登录和退出路由,其实有的模块我只是要注册 authMiddleware 登录状态判断这个中间件,能否提供一个方法只注册`group.Middleware(m.authMiddleware)`,不要注册登录和退出路由。

token的特殊字符"+"和"/"会被过滤掉。

在gf框架里,请求在做对象转换的过程中,token特殊字符会被替换成空。

例如携带有"+"和"/"的token访问下面的接口:

func (m *_menu) Test(ctx context.Context, req *model.PutMenuReq) (res *model.InfoRes, err error) {
	....
}

控制台显示Token不完全,特殊字符处被过滤掉。

路由绑定是这样的:

plToken := &gtoken.GfToken{
  LoginPath:       "/login",
  LoginBeforeFunc: loginFunc,
  LogoutPath:      "/logout",
}

s.Group("/system", func(group *ghttp.RouterGroup) {
group.Middleware(service.Middleware.CORS)
plToken.Middleware(group)

group.ALL("/menu", system.Menu)
}

路由拦截器能否不拦截指定路由?

路由拦截器能否不拦截指定路由?因为事实上大多数场景需要拦截的路由居多,不需要拦截的路由居多,有了这个功能后,使用起来会方便很多。

redis数据库配置问题

您好,请教下。我把CacheMode = 2,注释掉了sample1/config/config.toml default那一行就报错如下:
image
两行都不注释就报错如下:
image
暂时没找到redis的配置文档说明,目前我的配置如下:
image

token是如何执行的

image

其中gtoken的LoginBeoreFunc对应的函数是在LoginPath路由前执行还是绑定执行呢.我从图片中可以看到执行LoginPath的路由,然后执行了绑定的LoginBeforeFunc函数.不知道这样对吗

gf v1.11.5 error

2020-03-04 21:54:45.561 build error:

github.com/goflyfox/gtoken/gtoken

....\gopath\pkg\mod\github.com\goflyfox\[email protected]\gtoken\gtoken.go:397:41: not enough arguments in call to grand.Str
have (number)
want (string, int)

解决:
line 397 :
// 重新生成uuid
newUuid, err := gmd5.Encrypt(grand.Letters(10))

decrypt error :cipherText is not a multiple of the block size

上才艺

image

代码配置ru'xia如下
image

变量配置如下
image

描述
使用的最新版本安装的 github.com/goflyfox/gtoken v1.4.2
期初以为是我加密秘钥的问题,后来换成gtoken 默认也是提示这个错误

附上请求数据

{
  "isSuccess": true,
  "data": {
    "token": "Q1BFcDFuWXU3K2xCZEdMUGYzam5xYzdLbGJBY2VlU3c4UEo5WGxwVnNBSnZSY0JWZTYxTnBYc0tmcUU2cDh6SFBEN2tLb2F1MVh3NFQ0SzNZOFk3VXBrdGVjNEdwa3JFQnJSaFdrcDAyNnM9",
    "userKey": "dc4778f3a9f145f6c90e123a11a46bf6",
    "uuid": "1bdad1ae172ee83f8eb267899522b41d"
  },
  "errMsg": ""
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.