用go设计开发一个自己的轻量级登录库/框架吧(业务篇
- 同一用户只能登录一次
- 同一用户多次登录多token
- 同一用户多次登录共享一个token
源码:weloe/token-go: a light login library (github.com
存储结构
- 同一用户只能登录一次
- 同一用户多次登录多token
- 同一用户多次登录共享一个token
我们不能使用无状态token模式,要有状态,在后端存储会话信息才能达成想要实现的一些逻辑,因此,存储会话信息是必要的。
对于整个会话,我们会存储一个loginId-session的k-v结构。
Session结构体
type TokenSign struct {
Value string
Device string
}
type Session struct {
Id string
TokenSignList *list.List
}
总之,我们实现的业务将基于这两种k-v结构
功能实现
我们再来梳理一些功能和配置的对应关系
IsConcurrent == false
IsConcurrent == true && IsShare == false这时候配置MaxLoginCount
才生效
IsConcurrent == true && IsShare == true
我们大致将它分为几个阶段:
-
生成session
-
返回信息
-
检测登录人数
生成token
生成token的时候,我们要判断他是否是可多次登录,也就是isConcurrent
是否为false
。
IsConcurrent == true && IsShare == true,就判断能否复用之前的token
loginModel *model.Login
是为了支持自定义这几个参数type model.Login struct { Device string IsLastingCookie bool Timeout int64 Token string IsWriteHeader bool }
// createLoginToken create by config.TokenConfig and model.Login func (e *Enforcer createLoginToken(id string, loginModel *model.Login (string, error { tokenConfig := e.config var tokenValue string var err error // if isConcurrent is false, if !tokenConfig.IsConcurrent { err = e.Replaced(id, loginModel.Device if err != nil { return "", err } } // if loginModel set token, return directly if loginModel.Token != "" { return loginModel.Token, nil } // if share token if tokenConfig.IsConcurrent && tokenConfig.IsShare { // reuse the previous token. if v := e.GetSession(id; v != nil { tokenValue = v.GetLastTokenByDevice(loginModel.Device if tokenValue != "" { return tokenValue, nil } } } // create new token tokenValue, err = e.generateFunc.Exec(tokenConfig.TokenStyle if err != nil { return "", err } return tokenValue, nil }
生成session
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer.go#L183
// add tokenSign if session = e.GetSession(id; session == nil { session = model.NewSession(e.spliceSessionKey(id, "account-session", id } session.AddTokenSign(&model.TokenSign{ Value: tokenValue, Device: loginModel.Device, }
存储
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer.go#L192
// reset session err = e.SetSession(id, session, loginModel.Timeout if err != nil { return "", err } // set token-id err = e.adapter.SetStr(e.spliceTokenKey(tokenValue, id, loginModel.Timeout if err != nil { return "", err }
返回token
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer_internal_api.go#L51
IsReadCookie、
IsWriteHeader
和CookieConfig
// responseToken set token to cookie or header func (e *Enforcer responseToken(tokenValue string, loginModel *model.Login, ctx ctx.Context error { if ctx == nil { return nil } tokenConfig := e.config // set token to cookie if tokenConfig.IsReadCookie { cookieTimeout := tokenConfig.Timeout if loginModel.IsLastingCookie { cookieTimeout = -1 } // add cookie use tokenConfig.CookieConfig ctx.Response(.AddCookie(tokenConfig.TokenName, tokenValue, tokenConfig.CookieConfig.Path, tokenConfig.CookieConfig.Domain, cookieTimeout } // set token to header if loginModel.IsWriteHeader { ctx.Response(.SetHeader(tokenConfig.TokenName, tokenValue } return nil }
调用watcher和logger
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer.go#L210
// called watcher m := &model.Login{ Device: loginModel.Device, IsLastingCookie: loginModel.IsLastingCookie, Timeout: loginModel.Timeout, JwtData: loginModel.JwtData, Token: tokenValue, IsWriteHeader: loginModel.IsWriteHeader, } // called logger e.logger.Login(e.loginType, id, tokenValue, m if e.watcher != nil { e.watcher.Login(e.loginType, id, tokenValue, m }
检测登录人数
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer.go#L227
IsConcurrent == true && IsShare == false,也就是:同一用户多次登录多token模式,提供一个特殊值-1,如果为-1就认为不对登录数量进行限制,不然就开始强制退出,就是删除一部分的token
// if login success check it if tokenConfig.IsConcurrent && !tokenConfig.IsShare { // check if the number of sessions for this account exceeds the maximum limit. if tokenConfig.MaxLoginCount != -1 { if session = e.GetSession(id; session != nil { // logout account until loginCount == maxLoginCount if loginCount > maxLoginCount for element, i := session.TokenSignList.Front(, 0; element != nil && i < session.TokenSignList.Len(-int(tokenConfig.MaxLoginCount; element, i = element.Next(, i+1 { tokenSign := element.Value.(*model.TokenSign // delete tokenSign session.RemoveTokenSign(tokenSign.Value // delete token-id err = e.adapter.Delete(e.spliceTokenKey(tokenSign.Value if err != nil { return "", err } } // check TokenSignList length, if length == 0, delete this session if session != nil && session.TokenSignList.Len( == 0 { err = e.deleteSession(id if err != nil { return "", err } } } }
测试
同一用户只能登录一次
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer_test.go#L295
IsConcurrent = false IsShare = false
func TestEnforcerNotConcurrentNotShareLogin(t *testing.T { err, enforcer, ctx := NewTestNotConcurrentEnforcer(t if err != nil { t.Errorf("InitWithConfig( failed: %v", err } loginModel := model.DefaultLoginModel( for i := 0; i < 4; i++ { _, err = enforcer.LoginByModel("id", loginModel, ctx if err != nil { t.Errorf("Login( failed: %v", err } } session := enforcer.GetSession("id" if session.TokenSignList.Len( != 1 { t.Errorf("Login( failed: unexpected session.TokenSignList length = %v", session.TokenSignList.Len( } }
同一用户多次登录共享一个token
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer_test.go#L335
IsConcurrent = true IsShare = false
func TestEnforcer_ConcurrentNotShareMultiLogin(t *testing.T { err, enforcer, ctx := NewTestConcurrentEnforcer(t if err != nil { t.Errorf("InitWithConfig( failed: %v", err } loginModel := model.DefaultLoginModel( for i := 0; i < 14; i++ { _, err = enforcer.LoginByModel("id", loginModel, ctx if err != nil { t.Errorf("Login( failed: %v", err } } session := enforcer.GetSession("id" if session.TokenSignList.Len( != 12 { t.Errorf("Login( failed: unexpected session.TokenSignList length = %v", session.TokenSignList.Len( } }
同一用户多次登录多token
https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer_test.go#LL316C17-L316C17
IsConcurrent = true IsShare = true
func TestEnforcer_ConcurrentShare(t *testing.T { err, enforcer, ctx := NewTestEnforcer(t if err != nil { t.Errorf("InitWithConfig( failed: %v", err } loginModel := model.DefaultLoginModel( for i := 0; i < 5; i++ { _, err = enforcer.LoginByModel("id", loginModel, ctx if err != nil { t.Errorf("Login( failed: %v", err } } session := enforcer.GetSession("id" if session.TokenSignList.Len( != 1 { t.Errorf("Login( failed: unexpected session.TokenSignList length = %v", session.TokenSignList.Len( } }
至此,我们就实现了三种登录模式,