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

2023-05-13 12:25:20 来源:博客园
用go设计开发一个自己的轻量级登录库/框架吧(业务篇)

本篇会讲讲框架的登录业务的实现。实现三种登录模式:

同一用户只能登录一次同一用户多次登录多token同一用户多次登录共享一个token

源码:weloe/token-go: a light login library (github.com)

存储结构

首先从我们要考虑是底层该怎么存储登录信息来去达成这三种登录模式


(资料图片)

同一用户只能登录一次同一用户多次登录多token同一用户多次登录共享一个token

我们不能使用无状态token模式,要有状态,在后端存储会话信息才能达成想要实现的一些逻辑,因此,存储会话信息是必要的。

对于每个请求,我们会存储一个token-loginId的k-v结构。

对于整个会话,我们会存储一个loginId-session的k-v结构。

基于这个存储结构我们就可以方便的实现这三种模式。

Session结构体

session包括了多个tokenValue,这就是我们用来实现同一用户多次登录多token,或者同一用户多次登录共享一个token的关键点

type TokenSign struct {   Value  string   Device string}type Session struct {   Id            string   TokenSignList *list.List}

总之,我们实现的业务将基于这两种k-v结构

功能实现

源码:https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer.go#L167

我们再来梳理一些功能和配置的对应关系

同一用户只能登录一次:IsConcurrent == false

同一用户多次登录多token: IsConcurrent == true && IsShare == false这时候配置MaxLoginCount才生效

同一用户多次登录共享一个token: IsConcurrent == true && IsShare == true

接着我们再讲讲登录的具体流程:

我们大致将它分为几个阶段:

生成token

生成session

存储token-id , id-session

返回信息

调用watcher和logger

检测登录人数

生成token

https://github.com/weloe/token-go/blob/f58ba4d93f0f012972bf6a35b9127229b5c328fe/enforcer_internal_api.go#L12

生成token的时候,我们要判断他是否是可多次登录,也就是isConcurrent是否为false

如果可多次登录并且共享token即IsConcurrent == true && IsShare == true,就判断能否复用之前的token

这里我们还允许用户自定义token。

loginModel *model.Login是为了支持自定义这几个参数

type model.Login struct {Device          stringIsLastingCookie boolTimeout         int64Token           stringIsWriteHeader   bool}
// createLoginToken create by config.TokenConfig and model.Loginfunc (e *Enforcer) createLoginToken(id string, loginModel *model.Login) (string, error) {tokenConfig := e.configvar tokenValue stringvar 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 directlyif loginModel.Token != "" {return loginModel.Token, nil}// if share tokenif 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 tokentokenValue, 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

先判断是否已经存在session,如果不存在需要先创建,避免空指针

// add tokenSignif 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

在存储的时候,需要拼接key防止与其他的key重复

// reset sessionerr = e.SetSession(id, session, loginModel.Timeout)if err != nil {return "", err}// set token-iderr = 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

这个操作对应我们配置的TokenConfig的IsReadCookieIsWriteHeaderCookieConfig

// responseToken set token to cookie or headerfunc (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 watcherm := &model.Login{Device:          loginModel.Device,IsLastingCookie: loginModel.IsLastingCookie,Timeout:         loginModel.Timeout,JwtData:         loginModel.JwtData,Token:           tokenValue,IsWriteHeader:   loginModel.IsWriteHeader,}// called loggere.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 itif 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 > maxLoginCountfor 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 tokenSignsession.RemoveTokenSign(tokenSign.Value)// delete token-iderr = e.adapter.Delete(e.spliceTokenKey(tokenSign.Value))if err != nil {return "", err}}// check TokenSignList length, if length == 0, delete this sessionif 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 = falseIsShare = 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 = trueIsShare = 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 = trueIsShare = 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())}}

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

标签:

上一篇:每日报道:浙江龙泉:千年古城“正青春”
下一篇:最后一页
业界
更多
手机
更多
测评
软件
数码