Go 实践|基于 Token 的登录流程

背景

上一篇的文章沉淀了基于 session 的登录流程:Go 实践|基于 session 的登录流程 - 掘金,但是你会发现在分布式服务和单点登录等功能时使用 session 机制完成登录认证过程就需要解决共享和同步 session的问题。在现如今的发展的情况下,session 机制已经无法满足一些服务架构。

基于 Token 认证机制

Token 认证机制其实就是服务端将认证信息返回给客户端,客户端访问其他页面时都需要携带认证信息给服务端,由服务端验证这个认证信息是否有效可使用。大致流程:

  • 使用账号和密码请求登录,服务端接收到账号和密码进行验证
  • 验证成功,就会签发一个Token,返回给客户端
  • 前端接收Token并存储,后续客户端再请求其他页面都携带 Token
  • 服务端接收到客户端的请求时,先验证请求中携带的 token,如果验证成功,就向客户端返回请求的数据

Token 生成和验证

现在网上有很多关于 JWT 的使用说明可以直接搜索使用。其中, Token = Header + '.' + Payload + '.' + Signature。

  • Header 是用来说明签名的加密算法
json 复制代码
{
    "typ":"jwt"
    "alg":"HS256"
}
  • Payload 是记录我们可能需要的信息,除了现有已提供的标准的Claims ,还可以自定义一些可能需要的字段,比如 userId,userName 等。claims 是可以被解密的,所以不要存放重要而敏感的的信息
erlang 复制代码
// --- 标准
iss (issuer): JWT 的签发人
exp (expiration time): 过期时间
sub (subject): 主题,该jwt所面向的用户
aud (audience): 受众 接收该 jwt的一方
nbf (Not Before): 生效时间
iat (Issued At): 签发时间
jti (JWT ID): 编号

// 自定义 Claims
userId
userName
  • Signature 是由Header 中声明的加密算法进行生成的签名

实践

创建 jwtMaker 文件,定义 Maker 接口来规范 jwtMaker 只需要进行创建 CreateToken 和验证 VerifyToken两个方法。可以使用现在比较常用的一个jwt 包来完成 Token 的 ,直接获取:go get github.com/dgrijalva/jwt-go

声明Claims

定义一个标识用户信息的结构体,这部分也是所需要的 Claims。如用户名称、签发时间、过期时间。

go 复制代码
type CustomClaims struct {
	ID       uuid.UUID
	UserName string
    IssuedAt  time.Time
	ExpiresAt time.Time
}

func NewCustomClaims(userName string, duration time.Duration) (*CustomClaims, error) {
	tokenId, err := uuid.NewRandom()
	if err != nil {
		return nil, err
	}
	customClaim := &CustomClaims{
		ID:        tokenId,
		UserName:  userName,
		IssuedAt:  time.Now(),
		ExpiresAt: time.Now().Add(duration),
	}
	return customClaim, nil
}

func (c *CustomClaims) Valid() error {
	if time.Now().After(c.ExpiresAt) {
		return errors.New("token has expired")
	}
	return nil
}

创建签名

go 复制代码
func (j *JwtMaker) CreateToken(userName string, duration time.Duration) (string, error) {
	payload, err := NewCustomClaims(userName, duration)
	if err != nil {
		return "", err
	}

	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
	return jwtToken.SignedString([]byte(j.SecretKey))
}

验证签名

go 复制代码
func (j *JwtMaker) VerifyToken(token string) (*CustomClaims, error) {
	var result = new(CustomClaims)
	jwtToken, err := jwt.ParseWithClaims(token, result, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("无效 Token")
		} else {
			return []byte(j.SecretKey), nil
		}
	})
	if err != nil {
		return nil, err
	}
	// 校验 token
	if jwtToken.Valid {
		return result, nil
	}
	if err = result.Valid(); err != nil {
		return nil, fmt.Errorf("token 已过期")
	}
	return nil, fmt.Errorf("invalid token")
}

验证签名时,通过解析 token ,验证 token 是否有效,再验证是否过期。验证Token 是用在用户登录后所有请求都需要携带 token ,然后服务端获取到 token 再进行验证过。

多设备登录登出

使用 Token 进行无状态认证后,就可以更方便实现多端登录。在 Claims 中可以加入签发 Token 的机器信息,这样就可以有效的控制多端的登录登出多种场景。

  • 允许多端同时登录,任意端退出时其他端不退出:每个端生成都生成自己的Token,互相独立。
  • 允许多端同时登录,任意端退出其他端也退出:增加用户设备表,记录用户的登录设备和对应的Token ,当退出时将该用户所有的Token 都失效。
  • 多端选择性退出部分端:退出登录时增加一个需要退出端的对应设备类型或者设备ID,然后对应设备下的token 进行失效处理。

等等还有其他的使用场景,使用token 可以自由安全支持多端登录的机制。

最后

session 是用于记录服务器和客户端会话状态的机制。session 存储在服务端中,但当在集群或微服务架构中,多个服务端都在提供服务时,用户要获取自己的会话状态时,无论请求到那个服务端时都必须获取到自己相关的唯一 session 信息,所以此时需要多个服务端共享这个session。所以,如果将 session 存储再缓存中,就可以通过这种方式共享session。

相关推荐
赴前尘18 小时前
golang获取一个系统中没有被占用的端口
开发语言·后端·golang
沉默王二18 小时前
TRAE+Gemini,成为我解读 Agent 微服项目的最佳工具
java·后端·程序员
星月昭铭18 小时前
Spring Boot写一个/v1/chat/completions接口给Cherry Studio流式调用
java·spring boot·后端·ai
回家路上绕了弯18 小时前
分布式事务TCC详解:高并发场景下的柔性事务最优解?
分布式·后端
Coder_Boy_18 小时前
基于DDD+Spring Boot 3.2+LangChain4j构建企业级智能客服系统 版本升级
java·人工智能·spring boot·后端·langchain
武昌库里写JAVA18 小时前
vue+iview+node+express实现文件上传,显示上传进度条,实时计算上传速度
java·vue.js·spring boot·后端·sql
老华带你飞19 小时前
学生宿舍管理|基于java + vue学生宿舍管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
ArabySide20 小时前
【Spring Boot】理解Spring Bean作用域的设计
spring boot·后端·spring
计算机程序设计小李同学20 小时前
基于 Spring Boot 和 Vue.js 技术栈的网上订餐系统
vue.js·spring boot·后端
用户990450177800920 小时前
若依工作流集成camunda实现审批驳回功能
后端