GO-JWT
什么是 JWT
JWT,全称 JSON Web Token,是一种开放标准(RFC 7519),用于安全地在双方之间传递信息。尤其适用于身份验证和授权场景。JWT 的设计允许信息在各方之间安全地、 compactly(紧凑地)传输,因为其自身包含了所有需要的认证信息,从而减少了需要查询数据库或会话存储的需求。
JWT主要由三部分组成,通过.连接:
- Header(头部):描述JWT的元数据,通常包括类型(通常是
JWT)和使用的签名算法(如HS256、RS256等)。 - Payload(载荷):包含声明(claims),即用户的相关信息。这些信息可以是公开的,也可以是私有的,但应避免放入敏感信息,因为该部分可以被解码查看。载荷中的声明可以验证,但不加密。
- Signature(签名):用于验证JWT的完整性和来源。它是通过将Header和Payload分别进行Base64编码后,再与一个秘钥(secret)一起通过指定的算法(如HMAC SHA256)计算得出的。
JWT的工作流程大致如下:
- 认证阶段:用户向服务器提供凭证(如用户名和密码)。服务器验证凭证无误后,生成一个JWT,其中包含用户标识符和其他声明,并使用秘钥对其进行签名。
- 使用阶段:客户端收到JWT后,可以在后续的每个请求中将其放在HTTP请求头中发送给服务器,以此证明自己的身份。
- 验证阶段:服务器收到JWT后,会使用相同的秘钥验证JWT的签名,确保其未被篡改,并检查过期时间等其他声明,从而决定是否允许执行请求。
JWT的优势在于它的无状态性,服务器不需要存储会话信息,这减轻了服务器的压力,同时也方便了跨域认证。但需要注意的是,JWT的安全性依赖于秘钥的安全保管以及对JWT过期时间等的合理设置。
注册的时候 加密 密码
// user.go
func (u *User) SaveUser() (*User, error) {
err := DB.Create(&u).Error
if err != nil {
return &User{}, err
}
return u, nil
}
// 使用gorm的hook在保存密码前对密码进行hash
func (u *User) BeforeSave(tx *gorm.DB) error {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = string(hashedPassword)
u.Username = html.EscapeString(strings.TrimSpace(u.Username))
return nil
}
// register.go
func Register(c *gin.Context) {
var req ReqRegister
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"data": err.Error(),
})
return
}
u := models.User{
Username: req.Username,
Password: req.Password,
}
_, err := u.SaveUser()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"data": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "register success",
"data": req,
})
}
钩子事件:可在创建用户前后执行的事件。
Login
//controllers/login.go
func Login(c *gin.Context) {
var req ReqLogin
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u := models.User{
Username: req.Username,
Password: req.Password,
}
// 调用 models.LoginCheck 对用户名和密码进行验证
token, err := models.LoginCheck(u.Username, u.Password)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "username or password is incorrect.",
})
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
}
//models/user.go
func VerifyPassword(password, hashedPassword string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}
func LoginCheck(username, password string) (string, error) {
var err error
u := User{}
err = DB.Model(User{}).Where("username = ?", username).Take(&u).Error
if err != nil {
return "", err
}
err = VerifyPassword(password, u.Password)
if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
return "", err
}
token, err := token.GenerateToken(u.ID)
if err != nil {
return "", err
}
return token, nil
}
//utils/token.go
package utils
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func GenerateToken(user_id uint) (string, error) {
token_lifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN"))
if err != nil {
return "", err
}
claims := jwt.MapClaims{}
claims["authorized"] = true
claims["user_id"] = user_id
claims["exp"] = time.Now().Add(time.Hour * time.Duration(token_lifespan)).Unix()
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(os.Getenv("API_SECRET")))
}
func TokenValid(c *gin.Context) error {
tokenString := ExtractToken(c)
fmt.Println(tokenString)
_, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("API_SECRET")), nil
})
if err != nil {
return err
}
return nil
}
// 从请求头中获取token
func ExtractToken(c *gin.Context) string {
bearerToken := c.GetHeader("Authorization")
if len(strings.Split(bearerToken, " ")) == 2 {
return strings.Split(bearerToken, " ")[1]
}
return ""
}
// 从jwt中解析出user_id
func ExtractTokenID(c *gin.Context) (uint, error) {
tokenString := ExtractToken(c)
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("API_SECRET")), nil
})
if err != nil {
return 0, err
}
claims, ok := token.Claims.(jwt.MapClaims)
// 如果jwt有效,将user_id转换为浮点数字符串,然后再转换为 uint32
if ok && token.Valid {
uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32)
if err != nil {
return 0, err
}
return uint(uid), nil
}
return 0, nil
}
设置环境变量:
go get github.com/joho/godotenv
.env:
TOKEN_HOUR_LIFESPAN=1
API_SECRET="wP3-sN6&gG4-lV8>gJ9)"
创建 JWT 认证中间件
//middlewares/jwt.go
package middlewares
import (
"UserLoginSystem/utils"
"net/http"
"github.com/gin-gonic/gin"
)
func JwtAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
err := utils.TokenValid(c)
if err != nil {
c.String(http.StatusUnauthorized, err.Error())
c.Abort()
return
}
c.Next()
}
}
//controllers/login.go
func CurrentUser(c *gin.Context) {
// 从token中解析出user_id
user_id, err := token.ExtractTokenID(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 根据user_id从数据库查询数据
u, err := models.GetUserByID(user_id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "success",
"data": u,
})
}
//models/user.go
总结
1. JWT 核心结构
// Header示例(实际存储为Base64编码)
// {
// "alg": "HS256", // 必填,签名算法类型
// "typ": "JWT" // 必填,令牌类型固定值
// }
// Payload示例(实际存储为Base64编码)
// {
// "user_id": 123, // 自定义声明
// "exp": 1672531200 // 标准声明-过期时间(UNIX时间戳)
// }
// Signature生成公式:
// HMACSHA256(
// base64UrlEncode(header) + "." +
// base64UrlEncode(payload),
// secretKey
// )
2. 密码安全处理
// models/user.go
// BeforeSave - Gorm的Hook函数,在保存前自动执行
func (u *User) BeforeSave(tx *gorm.DB) error {
// bcrypt加密(自动加盐,DefaultCost=10)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err // 加密失败阻止保存
}
u.Password = string(hashedPassword)
// 用户名安全处理
u.Username = html.EscapeString(strings.TrimSpace(u.Username))
return nil
}
3. 登录与Token生成
// controllers/login.go
func Login(c *gin.Context) {
// 绑定JSON数据到结构体
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "请求格式错误"})
return
}
// 生成Token(包含用户ID和过期时间)
token, err := models.LoginCheck(req.Username, req.Password)
// ...错误处理
}
// models/user.go
func LoginCheck(username, password string) (string, error) {
// 数据库查询用户
err = DB.Where("username = ?", username).Take(&u).Error
// bcrypt密码验证
err = VerifyPassword(password, u.Password)
// 生成JWT Token
token, err := token.GenerateToken(u.ID)
return token, nil
}
4. Token工具函数
// utils/token.go
// GenerateToken - 生成JWT Token
func GenerateToken(user_id uint) (string, error) {
// 从环境变量读取有效期(小时)
lifespan, _ := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN"))
claims := jwt.MapClaims{
"user_id": user_id, // 用户标识
"exp": time.Now().Add(time.Hour * lifespan).Unix(), // 过期时间
}
// 创建Token(使用HS256算法)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 签名(使用环境变量中的秘钥)
return token.SignedString([]byte(os.Getenv("API_SECRET")))
}
// TokenValid - 验证Token有效性
func TokenValid(c *gin.Context) error {
tokenString := ExtractToken(c) // 从Header提取
// 解析并验证签名算法和秘钥
_, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
// 检查算法是否为HMAC
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("不支持的签名算法")
}
return []byte(os.Getenv("API_SECRET")), nil
})
return err // nil表示验证通过
}
5. 中间件实现
// middlewares/jwt.go
// JwtAuthMiddleware - JWT认证中间件
func JwtAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 验证Token
if err := utils.TokenValid(c); err != nil {
c.AbortWithStatus(401) // 认证失败
return
}
c.Next() // 继续后续处理
}
}
6. 环境变量配置
# .env文件
TOKEN_HOUR_LIFESPAN=1 # Token有效期(小时)
API_SECRET="your_strong_secret" # 签名秘钥(建议32+字符)
关键安全注意事项
-
秘钥管理:
API_SECRET必须足够复杂(建议随机生成32位以上字符串)- 生产环境必须通过环境变量注入,禁止硬编码
-
Token有效期:
- 根据业务需求设置合理有效期(通常1-24小时)
- 敏感操作应使用更短的有效期
-
密码存储:
- 必须使用bcrypt等自适应哈希算法
- 禁止使用MD5/SHA1等快速哈希
-
传输安全:
- 必须通过HTTPS传输
- 建议设置Secure和HttpOnly的Cookie

浙公网安备 33010602011771号