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

这篇具有很好参考价值的文章主要介绍了用go设计开发一个自己的轻量级登录库/框架吧(业务篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

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

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

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

}

至此,我们就实现了三种登录模式,文章来源地址https://www.toymoban.com/news/detail-442164.html

到了这里,关于用go设计开发一个自己的轻量级登录库/框架吧(业务篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • go-carbon v2.3.6 发布,轻量级、语义化、对开发者友好的 golang 时间处理库

    carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。 目前已被 awesome-go 收录,如果您觉得不错,请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于等于 1.16 Golang 版本小于 1.16 更新日志 将日历提取出来作为独立

    2024年01月24日
    浏览(50)
  • go-carbon 2.2.12 版本发布, 轻量级、语义化、对开发者友好的 Golang 时间处理库

    carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。 目前已被 awesome-go 收录,如果您觉得不错,请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于等于1.16 Golang 版本小于1.16 更新日志 增加对荷兰语的支持 测试

    2024年02月06日
    浏览(36)
  • go-carbon v2.3.5 发布,轻量级、语义化、对开发者友好的 golang 时间处理库

    carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。 目前已被 awesome-go 收录,如果您觉得不错,请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于等于 1.16 Golang 版本小于 1.16 更新日志 Default 结构体新增 Locale

    2024年02月01日
    浏览(38)
  • 使用Go语言打造轻量级Web框架

    前言 Web框架是Web开发中不可或缺的组件。它们的主要目标是抽象出HTTP请求和响应的细节,使开发人员可以更专注于业务逻辑的实现。在本篇文章中,我们将使用Go语言实现一个简单的Web框架,类似于Gin框架。 功能 我们的Web框架需要实现以下功能: 路由:处理HTTP请求的路由

    2023年04月08日
    浏览(33)
  • 特制自己的ChatGPT:多接口统一的轻量级LLM-IFT平台

    ©PaperWeekly 原创 · 作者 |  佀庆一 单位 |  中科院信息工程研究所 研究方向 |  视觉问答 项目简称: Alpaca-CoT(当羊驼遇上思维链) 项目标题: Alpaca-CoT: An Instruction Fine-Tuning Platform with Instruction Data Collection and Unified Large Language Models Interface 项目链接: https://github.com/PhoebusSi

    2024年02月04日
    浏览(26)
  • C#轻量级日志功能(只有一个类)

    最近在开发基于.net6的一个数据监控软件,使用其它开源log库都有点麻烦,就想着对Console.WriteLine()方法重定向到文件,非常方便的实现日志记录功能,同时也不影响之前的代码结构。 软件开始的地方要设置该重定向:

    2024年01月21日
    浏览(47)
  • CasaOS一个轻量级的家庭云系统

    简介 CasaOS是一款轻量级的家庭云系统,基于Docker安装部署,支持pc和手机,可玩性非常高,万物皆可以打成docker镜像后都可以安装。 你要你拥有一台电脑装上ubuntu你就能做all in one ,nas全家桶。安装简单,但是受网速影响至少要一个小时。 准备工作 一台装有docker的ubantu系统

    2024年02月05日
    浏览(38)
  • 轻量灵动: 革新轻量级服务开发

    从 JDK 8 升级到 JDK 17 可以让你的应用程序受益于新的功能、性能改进和安全增强。下面是一些 JDK 8 升级到 JDK 17 的最佳实战: 1.1、确定升级的必要性:首先,你需要评估你的应用程序是否需要升级到 JDK 17。查看 JDK 17 的新特性、改进和修复的 bug,以确定它们对你的应用程序

    2024年02月07日
    浏览(35)
  • Mainflux IoT:Go语言轻量级开源物联网平台,支持HTTP、MQTT、WebSocket、CoAP协议

    Mainflux是一个由法国的创业公司开发并维护的 安全、可扩展 的开源物联网平台,使用 Go语言开发、采用微服务的框架。Mainflux支持多种接入设备,包括设备、用户、APP;支持多种协议,包括HTTP、MQTT、WebSocket、CoAP,并支持他们之间的协议互转。 Mainflux的南向接口连接设备,北

    2024年02月01日
    浏览(95)
  • 【KRouter】一个简单且轻量级的Kotlin Routing框架

    KRouter(Kotlin-Router)是一个简单而轻量级的Kotlin路由框架。 具体来说,KRouter是一个通过URI来发现接口实现类的框架。它的使用方式如下: 之所以这样做,是因为在使用Voyager一段时间后,我发现模块之间的通信不够灵活,需要一些配置,而且使用DeepLink有点奇怪,所以我更喜

    2024年02月09日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包