认证中心 | 豆包MarsCode AI刷题

96 阅读2分钟

认证中心

本文章为【后端项目】青训营X豆包MarsCode 技术训练营 - 电商项目方案中认证中心的实现笔记。认证中兴包含分发身份令牌、校验身份令牌和续期身份令牌三个功能。主要技术点:Kitex,Gorm,Redis,jwt。

分发身份令牌

分发身份令牌的输入为用户ID,输出为登录令牌和刷新令牌。

认证中心会先为用户生成RSA私钥,之后使用该私钥生成登录令牌和刷新令牌。RSA私钥会被存放于MySQL数据库。在生成密钥时,若数据库中无用户记录,则认证中心直接创建,若已存在用户记录,则会更新记录中的密钥。除此之外,为了实现“长短令牌三验证”的JWT令牌续签策略,使用Redis数据库存储刷新令牌的md5哈希值。

func (s *AuthServiceImpl) DeliverTokenByRPC(ctx context.Context, req *auth.DeliverTokenReq) (resp *auth.DeliveryResp, err error) {
	resp = new(auth.DeliveryResp)
	userId := req.UserId
	prvKey, err := service.NewPrvKey(userId)
	if err != nil {
		return nil, fmt.Errorf("deliver token error: %w", err)
	}
	accessToken, refreshToken, err := service.GenerateToken(prvKey)
	if err != nil {
		return resp, fmt.Errorf("deliver token error: %w", err)
	}

	resp.AccessToken = accessToken
	resp.RefreshToken = refreshToken
	return resp, nil
}

Gorm操作数据库的代码如下,通过clause.OnConflict{UpdateAll: true}使得上述数据库操作可以仅通过一次数据库交互实现。

func CreatePrvKey(userId string, prvKey string) error {
	if userId == "" || prvKey == "" {
		return errors.New("save private key to database error: user-id and private key should not be empty")
	}
	result := db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&PreKey{UserId: userId, PrvKey: prvKey})
	if result.Error != nil {
		return fmt.Errorf("save private key to database error: %w", result.Error)
	}
	return nil
}

校验身份令牌

校验身份令牌的输入为用户ID和用户登录令牌,输出为校验结果。

认证中心先根据用户ID从MySQL数据库中查询到RSA公钥,之后使用该公钥验证登录令牌的合法性。

func (s *AuthServiceImpl) VerifyTokenByRPC(ctx context.Context, req *auth.VerifyTokenReq) (resp *auth.VerifyResp, err error) {
	resp = new(auth.VerifyResp)
	prvKey, err := service.LoadPrvKey(req.UserId)
	if err != nil {
		return resp, fmt.Errorf("verify token error: %w", err)
	}
	err = service.VerifyAccessToken(&prvKey.PublicKey, req.AccessToken)
	if err != nil {
		return resp, fmt.Errorf("verify token error: %w", err)
	}

	resp.Res = true
	return resp, nil
}

续期身份令牌

续期身份令牌的输入为用户ID和用户刷新令牌,输出为具有全新过期时间的登录令牌和刷新令牌。

认证中心会先使用RSA公钥验证用户令牌的合法性,之后查询刷新令牌是否未被使用,若未被使用则生成新的登录令牌和刷新令牌;若已被使用则返回错误、要求用户重新登录。刷新令牌未被使用的判断标准是刷新令牌的哈希值仍在Redis数据库中,在确认刷新令牌未被使用后,认证中心会从Redis数据库中删除旧刷新令牌的哈希值。

func VerifyRefreshToken(pubKey *rsa.PublicKey, tokenStr string) error {
	claims, err := parseToken(pubKey, tokenStr)
	if err != nil {
		return fmt.Errorf("verify refresh token error: %w", err)
	}

	if tokenType, ok := claims["type"]; !ok || tokenType.(string) != RefreshToken {
		return fmt.Errorf("verify refresh token error: unsupproted token: %v", tokenStr)
	}

	hashValue := getMD5Hash(tokenStr)
	_, err = db.GetToken(hashValue)
	if err != nil {
		return fmt.Errorf("verify refresh token error: %v", tokenStr)
	}

	err = db.DelToken(hashValue)
	if err != nil {
		return fmt.Errorf("verify refresh token error: %v", tokenStr)
	}

	return nil
}