Golang Gin框架JWT Token在HTML请求头的写入与验证问询
嘿,咱们一步步解决你的JWT认证问题吧,先从后端逻辑修复开始,再搞定前端的Token携带问题:
一、后端核心问题修复
1. 完善Login函数:正确生成、返回Token
你的Login函数目前有几个关键问题:JWT用空密钥签名(完全无效)、请求体重复读取导致存Token失败、没把Token返回给前端。修改后的代码如下:
// 建议从环境变量读取密钥,这里先定义常量示例(务必用安全的随机字符串) const jwtSecret = "your_secure_jwt_secret_key_keep_it_safe" func Login(c *gin.Context) { c.Request.ParseForm() email := c.PostForm("email") password := c.PostForm("password") response := ResponseControllerList{} conditions := bson.M{"email": email, "password": password} data, err := models.MatchUser(conditions) if err != nil { fmt.Println(err) response = ResponseControllerList{ config.FailureCode, config.FailureFlag, config.FailureMsg, config.UsernamePassword, nil, } GetResponseList(c, response) return } dataCount, err := models.GetRecordsCount(config.CustomerCollection, conditions) if err != nil { fmt.Println(err) response = ResponseControllerList{ config.FailureCode, config.FailureFlag, config.FailureMsg, "Failed to count user records", nil, } GetResponseList(c, response) return } // 验证账号密码匹配 isMatch := false for _, signup := range data { if email == signup.Email && password == signup.Password { isMatch = true break // 匹配到就终止循环,提升效率 } } if !isMatch { response = ResponseControllerList{ config.FailureCode, config.FailureFlag, config.FailureMsg, config.UsernamePassword, nil, } GetResponseList(c, response) return } // 生成JWT Token:注意绝对不要把密码存进Claims!JWT Payload是可解码的! token := jwt.NewWithClaims(jwt.SigningMethodHS256, &User{ Email: email, StandardClaims: jwt.StandardClaims{ ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), // 设置24小时过期 }, }) tokenstring, err := token.SignedString([]byte(jwtSecret)) if err != nil { fmt.Println(err) response = ResponseControllerList{ config.FailureCode, config.FailureFlag, "Failed to generate auth token", nil, nil, } GetResponseList(c, response) return } // 保存Token到数据库:直接用生成的tokenstring,不用解析请求体 tok := models.Token{Tok: tokenstring} if err = models.SaveToken(&tok); err != nil { fmt.Println("Error saving token:", err) response = ResponseControllerList{ config.FailureCode, config.FailureFlag, "Failed to save auth token", nil, nil, } GetResponseList(c, response) return } // 构造成功响应,同时把Token返回给前端(响应头+响应体双保险) response = ResponseControllerList{ config.SuccessCode, config.SuccessFlag, config.SuccessMsg, data, dataCount, } // 标准做法:把Token放在Authorization头,格式为Bearer <token> c.Header("Authorization", "Bearer "+tokenstring) // 也可以在响应体里额外返回Token(需要修改ResponseControllerList结构体添加Token字段) // response.Token = tokenstring GetResponseList(c, response) fmt.Println("Token generated and saved successfully") }
2. 修复ValidateToken中间件:正确获取并验证Token
遵循JWT标准,从Authorization请求头获取Token,先验证Token本身的有效性(签名、过期),再去数据库匹配:
import ( "fmt" "strings" "time" "github.com/dgrijalva/jwt-go" ) func ValidateToken(c *gin.Context) { // 从Authorization头获取Token authHeader := c.GetHeader("Authorization") if authHeader == "" { response := ResponseUser{"Unauthorized: No auth token provided"} c.AbortWithStatusJSON(401, response) return } // 拆分Bearer前缀和Token内容 parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { response := ResponseUser{"Unauthorized: Invalid token format"} c.AbortWithStatusJSON(401, response) return } tokenString := parts[1] // 先验证JWT本身的有效性(签名、过期时间) token, err := jwt.ParseWithClaims(tokenString, &User{}, 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(jwtSecret), nil }) if err != nil || !token.Valid { response := ResponseUser{"Unauthorized: Invalid or expired token"} c.AbortWithStatusJSON(401, response) return } // 解析Token中的用户信息 claims, ok := token.Claims.(*User) if !ok { response := ResponseUser{"Unauthorized: Failed to parse token claims"} c.AbortWithStatusJSON(401, response) return } // 可选:验证Token是否存在于数据库(用于主动失效场景) condition := bson.M{"token": tokenString} data, err := models.MatchToken(condition) if err != nil || len(data) == 0 { response := ResponseUser{"Unauthorized: Token not found in system"} c.AbortWithStatusJSON(401, response) return } // 将用户信息存入上下文,供后续接口使用 c.Set("user_email", claims.Email) fmt.Println("Authorized user:", claims.Email) c.Next() }
二、前端Token处理修复
1. 登录AJAX:保存Token到本地存储
登录成功后,把后端返回的Token存入localStorage,方便后续请求携带:
$.ajax({ url: "/api/v2/login", type: "POST", data: {'email': email, "password": password}, success: function(results, status, xhr) { console.log(results) if(results.response.message === "Success"){ // 从响应头获取Token const authHeader = xhr.getResponseHeader('Authorization'); if(authHeader) { const token = authHeader.split(' ')[1]; localStorage.setItem('jwtToken', token); } alert("Login successful!"); // 跳转授权页面,无需在URL带UserId(后端可通过Token获取用户信息) document.location.href = "/api/v1/customer"; } else if(results.response.message === "Failure"){ alert("Login failed: " + results.response.data); } }, error: function(xhr) { alert("Request failed: " + xhr.responseText); } });
2. 授权页面AJAX:携带Token到请求头
后续请求从localStorage取出Token,放在Authorization头里发送:
$(document).ready(function(){ $('#activate').hide(); // 获取本地存储的Token const token = localStorage.getItem('jwtToken'); if(!token) { // 无Token则跳转到登录页 document.location.href = "/api/v2/login"; return; } $.ajax({ url: "/api/v1/customer/get-data", type: "GET", dataType: 'json', async: false, // 携带Token到请求头 headers: { 'Authorization': 'Bearer ' + token }, success: function(data) { console.log("User data:", data); // 处理返回的数据 }, error: function(xhr) { if(xhr.status === 401) { // Token失效或无效,清除本地存储并跳转登录 localStorage.removeItem('jwtToken'); document.location.href = "/api/v2/login"; } else { alert("Failed to fetch data: " + xhr.responseText); } } }); });
额外优化建议
- 别在URL里带敏感信息:之前的UserId参数容易泄露,建议后端通过Token解析用户身份
- 启用HTTPS:JWT是敏感信息,必须通过HTTPS传输避免被窃取
- Token失效机制:如果需要主动注销Token,可以维护数据库黑名单,或者用短过期时间+刷新Token的方案
- 前端Token过期处理:收到401响应时自动清除Token并跳转登录页
内容的提问来源于stack exchange,提问作者gourav
相关产品推荐
相关产品推荐

