45.双token无感熟悉以及解决sso单点登录限制

上文已经介绍了jwt的基本原理和用法,44.jwt在go中的使用及原理(一),本文将介绍双token机制,以及sso单点登录

一、双Token机制

基于token安全性的处理 access token 和 refresh token

以下access token简称 atokenrefresh token 简称 rtoken。无感刷新方式。

在用户登录的时候颁发两个tokenatoken rtokenatoken 的有效期很短,根据业务实际需求可以自定义。一般设置为10分钟足够。rtoken有效期较长,一般可以设置为一星期或者一个月,根据实际业务需求可以自行定义。(根据查询资料得知 rtoken需要进行client-sercet才能有效)。当atoken过期之后可以通过rtoken进行刷新,但是rtoken过期之后,只能重新登录来获取。

atoken丢失之后没关系,因为它有效期很短。当rtoken丢失之后也没关系,因为他需要配合client-sercet才能使用。

在生成token时,我们一次生成两个token,atoken用于认证,会包含有用户相关信息,如UserID,Username等, 而rtoken不会保存用户信息,专门用于刷新atoken

// GenToken 颁发token access token 和 refresh token
func GenToken(UserID int64, Username string) (atoken, rtoken string, err error) {
	rc := jwt.RegisteredClaims{
		ExpiresAt: getJWTTime(ATokenExpiredDuration),
		Issuer:    TokenIssuer,
	}
	at := MyClaim{
		UserID,
		Username,
		rc,
	}
	atoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, at).SignedString(mySecret)

	// refresh token 不需要保存任何用户信息
	rt := rc
	rt.ExpiresAt = getJWTTime(RTokenExpiredDuration)
	rtoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, rt).SignedString(mySecret)
	return
}

在验证用户登录之后,根据传入的UserIDUsername ,生成atokenrtoken。在颁发token中可以分别规定两个token的过期时间

校验token

// VerifyToken 验证Token
func VerifyToken(tokenID string) (*MyClaim, error) {
	var myc = new(MyClaim)
	token, err := jwt.ParseWithClaims(tokenID, myc, keyFunc)
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		return nil, ErrorInvalidToken
	}

	return myc, nil
}

根据传入的token值来判断是否有错误,如果错误为无效,说明token格式不正确或者token已经过期。

无感刷新token

首先校验rtoken是否还有效,rtoken有效的情况下继续校验atoken是否是因为过期导致的失效,是的话可以刷新atoken然后返回给前端。

// RefreshToken 通过 refresh token 刷新 atoken
func RefreshToken(atoken, rtoken string) (newAtoken, newRtoken string, err error) {
	// rtoken 无效直接返回
	if _, err = jwt.Parse(rtoken, keyFunc); err != nil {
		return
	}
	// 从旧access token 中解析出claims数据
	var claim MyClaim
	_, err = jwt.ParseWithClaims(atoken, &claim, keyFunc)
	// 判断错误是不是因为access token 正常过期导致的
	v, _ := err.(*jwt.ValidationError)
	if v.Errors == jwt.ValidationErrorExpired {
		return GenToken(claim.UserID, claim.Username)
	}
	return
}

完整代码

package main

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v4"
)

const (
	ATokenExpiredDuration  = 10 * time.Minute
	RTokenExpiredDuration  = 30 * 24 * time.Hour
	TokenIssuer            = ""
)

var (
	mySecret          = []byte("xxxx")
	ErrorInvalidToken = errors.New("verify Token Failed")
)

type MyClaim struct {
	UserID   int64  `json:"user_id"`
	Username string `json:"username"`
	jwt.RegisteredClaims
}

func getJWTTime(t time.Duration) *jwt.NumericDate {
	return jwt.NewNumericDate(time.Now().Add(t))
}

func keyFunc(token *jwt.Token) (interface{}, error) {
	return mySecret, nil
}

// GenToken 颁发token access token 和 refresh token
func GenToken(UserID int64, Username string) (atoken, rtoken string, err error) {
	rc := jwt.RegisteredClaims{
		ExpiresAt: getJWTTime(ATokenExpiredDuration),
		Issuer:    TokenIssuer,
	}
	at := MyClaim{
		UserID,
		Username,
		rc,
	}
	atoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, at).SignedString(mySecret)

	// refresh token 不需要保存任何用户信息
	rt := rc
	rt.ExpiresAt = getJWTTime(RTokenExpiredDuration)
	rtoken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, rt).SignedString(mySecret)
	return
}

// VerifyToken 验证Token
func VerifyToken(tokenID string) (*MyClaim, error) {
	var myc = new(MyClaim)
	token, err := jwt.ParseWithClaims(tokenID, myc, keyFunc)
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		err = ErrorInvalidToken
		return nil, err
	}

	return myc, nil
}

// RefreshToken 通过 refresh token 刷新 atoken
func RefreshToken(atoken, rtoken string) (newAtoken, newRtoken string, err error) {
	// rtoken 无效直接返回
	if _, err = jwt.Parse(rtoken, keyFunc); err != nil {
		return
	}
	// 从旧access token 中解析出claims数据
	var claim MyClaim
	_, err = jwt.ParseWithClaims(atoken, &claim, keyFunc)
	// 判断错误是不是因为access token 正常过期导致的
	v, _ := err.(*jwt.ValidationError)
	if v.Errors == jwt.ValidationErrorExpired {
		return GenToken(claim.UserID, claim.Username)
	}
	return
}

二、双Token最佳实践

使用 JWT 实现的双令牌验证主要有以下几步:
在这里插入图片描述

  • 后端需要对外提供一个刷新Token的接口,前端需要是实现一个当Access Token过期时自动请求刷新Token接口获取新Access Token的拦截器。
  • 用户登录时,服务端同时生成并返回 access tokenrefresh token。其中,access token 的有效期较短,例如 10 分钟。而 refresh token 的有效期较长,例如 30 天。这两种 token 都可以用 jwt-go 生成。
  • 如果 refresh token 也过期了,那么客户端必须要重新登录,以获取新的 access tokenrefresh token

注意:在实际的生产环境中,为了保证系统的安全性,你可能需要考虑到以下几点:

  • Token也可以在服务端保存一份,比如存到Redis中,并对前端传来的tokenredis中的比较,这样可以实现服务端主动让token失效,比如从redis删除token即可。
  • 考虑到用户的session状态,当用户退出登录或者修改密码后,需要把保存在服务端的refresh token删除或者置为无效。
  • 应用 HTTPS 协议以保护你的 token 不被截获。
  • 使用黑名单机制,当用户的 token 被盗或者用户退出登录后,你可以把这个 token 添加到黑名单中,防止它再次被用于请求。
  • 考虑到服务的可用性,你可能需要把 token 保存在像Redis这样的内存数据库中,以提升性能。

以上是 JWT 双令牌验证的一种常见的最佳实践,但需要注意,不同的业务场景可能需要不同的安全策略,总是需要根据实际业务需求和环境来灵活调整。

三、SSO单点登录

SSO说明
SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。https://baike.baidu.com/item/SSO/3451380

例如访问在网易账号中心(http://reg.163.com/ )登录之,访问以下站点都是登录状态

  • 网易直播 http://v.163.com
  • 网易博客 http://blog.163.com
  • 网易花田 http://love.163.com
  • 网易考拉 https://www.kaola.com
  • 网易Lofter http://www.lofter.com

SSO设计可参考:单点登录(SSO)的设计与实现

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值