文章目录
- 首先定义Token相关的数据结构: type TokenDetails struct { AccessToken string RefreshToken string AccessUuid string RefreshUuid string AtExpires int64 RtExpires int64 } type AccessDetails struct { AccessUuid string UserId uint64 }
- 使用JWT(JSON Web Token)生成Token,并存储在Redis中: func CreateToken(userid uint64) (*TokenDetails, error) { td := &TokenDetails{} td.AtExpires = time.Now().Add(time.Minute * 15).Unix() td.AccessUuid = uuid.New().String() td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix() td.RefreshUuid = uuid.New().String() // 创建Access Token atClaims := jwt.MapClaims{} atClaims[“authorized”] = true atClaims[“access_uuid”] = td.AccessUuid atClaims[“user_id”] = userid atClaims[“exp”] = td.AtExpires at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims) td.AccessToken, _ = at.SignedString([]byte(os.Getenv(“ACCESS_SECRET”))) // 创建Refresh Token rtClaims := jwt.MapClaims{} rtClaims[“refresh_uuid”] = td.RefreshUuid rtClaims[“user_id”] = userid rtClaims[“exp”] = td.RtExpires rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims) td.RefreshToken, _ = rt.SignedString([]byte(os.Getenv(“REFRESH_SECRET”))) return td, nil } func CreateAuth(userid uint64, td *TokenDetails) error { at := time.Unix(td.AtExpires, 0) rt := time.Unix(td.RtExpires, 0) now := time.Now() // 存储Access Token errAccess := client.Set(td.AccessUuid, strconv.Itoa(int(userid)), at.Sub(now)).Err() if errAccess != nil { return errAccess } // 存储Refresh Token errRefresh := client.Set(td.RefreshUuid, strconv.Itoa(int(userid)), rt.Sub(now)).Err() if errRefresh != nil { return errRefresh } return nil }
- func Login(c *gin.Context) { var user User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(http.StatusUnprocessableEntity, “Invalid json provided”) return } // 验证用户凭据 // … // 生成Token td, err := CreateToken(user.ID) if err != nil { c.JSON(http.StatusUnprocessableEntity, err.Error()) return } // 存储Token saveErr := CreateAuth(user.ID, td) if saveErr != nil { c.JSON(http.StatusUnprocessableEntity, saveErr.Error()) return } tokens := map[string]string{ “access_token”: td.AccessToken, “refresh_token”: td.RefreshToken, } c.JSON(http.StatusOK, tokens) }
- func Refresh(c *gin.Context) { mapToken := map[string]string{} if err := c.ShouldBindJSON(&mapToken); err != nil { c.JSON(http.StatusUnprocessableEntity, err.Error()) return } refreshToken := mapToken[“refresh_token”] // 验证Refresh Token token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf(“unexpected signing method: %v”, token.Header[“alg”]) } return []byte(os.Getenv(“REFRESH_SECRET”)), nil }) if err != nil { c.JSON(http.StatusUnauthorized, “Refresh token expired”) return } // 检查Token是否有效 if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid { c.JSON(http.StatusUnauthorized, err) return } // 提取claims claims, ok := token.Claims.(jwt.MapClaims) if ok && token.Valid { refreshUuid, ok := claims[“refresh_uuid”].(string) if !ok { c.JSON(http.StatusUnprocessableEntity, err) return } userId, err := strconv.ParseUint(fmt.Sprintf(“%.f”, claims[“user_id”]), 10, 64) if err != nil { c.JSON(http.StatusUnprocessableEntity, “Error occurred”) return } // 删除旧的Refresh Token deleted, delErr := DeleteAuth(refreshUuid) if delErr != nil || deleted == 0 { c.JSON(http.StatusUnauthorized, “unauthorized”) return } // 创建新的Token对 ts, createErr := CreateToken(userId) if createErr != nil { c.JSON(http.StatusForbidden, createErr.Error()) return } // 保存新的Token saveErr := CreateAuth(userId, ts) if saveErr != nil { c.JSON(http.StatusForbidden, saveErr.Error()) return } tokens := map[string]string{ “access_token”: ts.AccessToken, “refresh_token”: ts.RefreshToken, } c.JSON(http.StatusCreated, tokens) } else { c.JSON(http.StatusUnauthorized, “refresh expired”) } }
- func TokenAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { err := TokenValid(c.Request) if err != nil { c.JSON(http.StatusUnauthorized, err.Error()) c.Abort() return } c.Next() } } func TokenValid(r *http.Request) error { token, err := VerifyToken(r) if err != nil { return err } if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid { return err } return nil } func VerifyToken(r *http.Request) (*jwt.Token, error) { tokenString := ExtractToken(r) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf(“unexpected signing method: %v”, token.Header[“alg”]) } return []byte(os.Getenv(“ACCESS_SECRET”)), nil }) if err != nil { return nil, err } return token, nil } func ExtractToken(r *http.Request) string { bearToken := r.Header.Get(“Authorization”) strArr := strings.Split(bearToken, ” “) if len(strArr) == 2 { return strArr[1] } return “” }
目录
- 引言
- 双Token机制概述
- 实现思路
- 1. 数据结构设计
- 2. Token生成与存储
- 3. 登录接口实现
- 4. Token刷新机制
- 5. 中间件实现Token验证
- 完整流程
- 总结
在现代Web应用中,身份认证是保障系统安全的重要环节。传统的单Token认证方式存在一些安全隐患,如Token泄露可能导致长期风险。双Token机制(Access Token + Refresh Token)提供了更好的安全性和用户体验。本文将介绍如何使用Go语言实现双Token登录系统。
双Token机制包含两种令牌:
- Access Token:短期有效的令牌,用于访问受保护资源
- Refresh Token:长期有效的令牌,用于获取新的Access Token
这种机制的优势在于:
- Access Token有效期短,即使泄露影响有限
- Refresh Token不直接用于资源访问,降低了泄露风险
- 无需频繁重新登录,保持用户体验
首先定义Token相关的数据结构:
type TokenDetails struct {
AccessToken string
RefreshToken string
AccessUuid string
RefreshUuid string
AtExpires int64
RtExpires int64
}
type AccessDetails struct {
AccessUuid string
UserId uint64
}
使用JWT(JSON Web Token)生成Token,并存储在Redis中:
func CreateToken(userid uint64) (*TokenDetails, error) {
td := &TokenDetails{}
td.AtExpires = time.Now().Add(time.Minute * 15).Unix()
td.AccessUuid = uuid.New().String()
td.RtExpires = time.Now().Add(time.Hour * 24 * 7).Unix()
td.RefreshUuid = uuid.New().String()
// 创建Access Token
atClaims := jwt.MapClaims{}
atClaims["authorized"] = true
atClaims["access_uuid"] = td.AccessUuid
atClaims["user_id"] = userid
atClaims["exp"] = td.AtExpires
at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
td.AccessToken, _ = at.SignedString([]byte(os.Getenv("ACCESS_SECRET")))
// 创建Refresh Token
rtClaims := jwt.MapClaims{}
rtClaims["refresh_uuid"] = td.RefreshUuid
rtClaims["user_id"] = userid
rtClaims["exp"] = td.RtExpires
rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims)
td.RefreshToken, _ = rt.SignedString([]byte(os.Getenv("REFRESH_SECRET")))
return td, nil
}
func CreateAuth(userid uint64, td *TokenDetails) error {
at := time.Unix(td.AtExpires, 0)
rt := time.Unix(td.RtExpires, 0)
now := time.Now()
// 存储Access Token
errAccess := client.Set(td.AccessUuid, strconv.Itoa(int(userid)), at.Sub(now)).Err()
if errAccess != nil {
return errAccess
}
// 存储Refresh Token
errRefresh := client.Set(td.RefreshUuid, strconv.Itoa(int(userid)), rt.Sub(now)).Err()
if errRefresh != nil {
return errRefresh
}
return nil
}
func Login(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
return
}
// 验证用户凭据
// ...
// 生成Token
td, err := CreateToken(user.ID)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, err.Error())
return
}
// 存储Token
saveErr := CreateAuth(user.ID, td)
if saveErr != nil {
c.JSON(http.StatusUnprocessableEntity, saveErr.Error())
return
}
tokens := map[string]string{
"access_token": td.AccessToken,
"refresh_token": td.RefreshToken,
}
c.JSON(http.StatusOK, tokens)
}
func Refresh(c *gin.Context) {
mapToken := map[string]string{}
if err := c.ShouldBindJSON(&mapToken); err != nil {
c.JSON(http.StatusUnprocessableEntity, err.Error())
return
}
refreshToken := mapToken["refresh_token"]
// 验证Refresh Token
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("REFRESH_SECRET")), nil
})
if err != nil {
c.JSON(http.StatusUnauthorized, "Refresh token expired")
return
}
// 检查Token是否有效
if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {
c.JSON(http.StatusUnauthorized, err)
return
}
// 提取claims
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
refreshUuid, ok := claims["refresh_uuid"].(string)
if !ok {
c.JSON(http.StatusUnprocessableEntity, err)
return
}
userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Error occurred")
return
}
// 删除旧的Refresh Token
deleted, delErr := DeleteAuth(refreshUuid)
if delErr != nil || deleted == 0 {
c.JSON(http.StatusUnauthorized, "unauthorized")
return
}
// 创建新的Token对
ts, createErr := CreateToken(userId)
if createErr != nil {
c.JSON(http.StatusForbidden, createErr.Error())
return
}
// 保存新的Token
saveErr := CreateAuth(userId, ts)
if saveErr != nil {
c.JSON(http.StatusForbidden, saveErr.Error())
return
}
tokens := map[string]string{
"access_token": ts.AccessToken,
"refresh_token": ts.RefreshToken,
}
c.JSON(http.StatusCreated, tokens)
} else {
c.JSON(http.StatusUnauthorized, "refresh expired")
}
}
func TokenAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
err := TokenValid(c.Request)
if err != nil {
c.JSON(http.StatusUnauthorized, err.Error())
c.Abort()
return
}
c.Next()
}
}
func TokenValid(r *http.Request) error {
token, err := VerifyToken(r)
if err != nil {
return err
}
if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {
return err
}
return nil
}
func VerifyToken(r *http.Request) (*jwt.Token, error) {
tokenString := ExtractToken(r)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("ACCESS_SECRET")), nil
})
if err != nil {
return nil, err
}
return token, nil
}
func ExtractToken(r *http.Request) string {
bearToken := r.Header.Get("Authorization")
strArr := strings.Split(bearToken, " ")
if len(strArr) == 2 {
return strArr[1]
}
return ""
}
- 用户登录:提供用户名密码,服务端验证后返回Access Token和Refresh Token
- 访问受保护资源:客户端在请求头中携带Access Token
- Access Token过期:服务端返回401错误
- 刷新Token:客户端使用Refresh Token请求新的Token对
- 继续访问:使用新的Access Token访问资源
通过Go语言实现双Token认证机制,我们能够构建更安全的身份认证系统。这种机制在保证安全性的同时,也提供了良好的用户体验。实际应用中,可以根据业务需求调整Token的有效期和实现细节。
以上就是Go语言中双Token登录系统的思路与实现详解的详细内容,更多关于Go双Token登录的资料请关注风君子博客其它相关文章!
您可能感兴趣的文章:
- Golang使用Token来验证
- Go 实现基于Token 的登录流程深度分析
- Golang token的生成和解析详解
- go gin+token(JWT)验证实现登陆验证
- golang jwt+token验证的实现