You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:59:32