用go设计开发一个自己的轻量级登录库/框架吧(业务篇)

科技资讯 投稿 13400 0 评论

用go设计开发一个自己的轻量级登录库/框架吧(业务篇)

用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、IsWriteHeaderCookieConfig

// 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(
	}

}

至此,我们就实现了三种登录模式,

编程笔记 » 用go设计开发一个自己的轻量级登录库/框架吧(业务篇)

赞同 (67) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽