You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Golang Gin框架JWT认证跨域预检401错误求助

解决Gin + JWT 跨域预请求401问题

嘿,这个问题我太熟了!咱们先来搞清楚为啥Postman正常但Chrome报错:Postman不会发送预请求(OPTIONS请求),但浏览器在发送跨域的POST/GET等请求前,会先自动发一个OPTIONS请求去确认服务器是否允许跨域。你的JWT认证中间件拦截了这个OPTIONS请求——毕竟OPTIONS里不会带Authorization token嘛,所以直接返回了401,导致预请求失败,后续的真实请求根本发不出去。

下面给你一套完整的解决方案,分后端配置和前端调整两步来:

一、后端Gin配置正确的CORS中间件

你需要确保Gin能正确处理OPTIONS请求,并且允许前端的域名、必要的请求头和方法。这里有两种方式:

方式1:使用官方推荐的gin-contrib/cors包

这是最省心的方式,先安装包:

go get github.com/gin-contrib/cors

然后在你的Gin初始化代码里配置:

package main

import (
    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // 配置CORS规则
    corsConfig := cors.DefaultConfig()
    // 替换成你的前端实际域名,比如http://localhost:3000
    corsConfig.AllowOrigins = []string{"http://localhost:你的前端端口"}
    // 允许所有常用的请求方法
    corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
    // 允许必要的请求头,尤其是Authorization(JWT token用)和Content-Type
    corsConfig.AllowHeaders = []string{"Origin", "Content-Type", "Authorization"}
    // 允许携带凭证(比如cookie或token)
    corsConfig.AllowCredentials = true
    // 设置预请求缓存时间,减少OPTIONS请求的次数
    corsConfig.MaxAge = 12 * 3600

    // 注册CORS中间件,要放在所有路由和认证中间件之前!
    r.Use(cors.New(corsConfig))

    // 接下来注册你的路由
    r.POST("/login", loginHandler)
    // 认证中间件放在私有路由前面
    r.GET("/v1/hello", authMiddleware, helloHandler)

    r.Run(":8080")
}

方式2:自己手写CORS中间件

如果不想用第三方包,也可以自己写一个:

func corsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 获取请求的Origin头,允许跨域
        origin := c.Request.Header.Get("Origin")
        c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
        // 允许的请求方法
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        // 允许的请求头
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Authorization")
        // 允许携带凭证
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        // 预请求缓存12小时
        c.Writer.Header().Set("Access-Control-Max-Age", "43200")

        // 如果是OPTIONS预请求,直接返回200,不进入后续中间件
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(200)
            return
        }

        // 非OPTIONS请求,继续执行后续逻辑
        c.Next()
    }
}

// 然后在main里注册这个中间件(同样要放在最前面)
func main() {
    r := gin.Default()
    r.Use(corsMiddleware())
    // ... 注册路由和认证中间件
}

二、调整JWT认证中间件,放过OPTIONS请求

不管你用哪种CORS配置,都要确保你的JWT认证中间件不会拦截OPTIONS请求。修改你的authMiddleware

func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 先判断是不是OPTIONS请求,是的话直接放行
        if c.Request.Method == "OPTIONS" {
            c.Next()
            return
        }

        // 下面是你的JWT认证逻辑
        tokenStr := c.GetHeader("Authorization")
        // 比如去掉Bearer前缀,验证token...
        // 验证失败返回401,验证通过继续执行
        c.Next()
    }
}

三、前端Ajax请求调整

确保你的jQuery/Ajax请求设置了withCredentials: true,这样才能携带Authorization头和凭证:

// 登录请求示例
$.ajax({
    url: "http://localhost:8080/login",
    type: "POST",
    data: {
        username: "your-username",
        password: "your-password"
    },
    xhrFields: {
        withCredentials: true
    },
    success: function(res) {
        // 保存token
        const token = res.token;
        // 调用私有接口
        callPrivateApi(token);
    }
});

// 私有接口调用示例
function callPrivateApi(token) {
    $.ajax({
        url: "http://localhost:8080/v1/hello",
        type: "GET",
        headers: {
            "Authorization": "Bearer " + token
        },
        xhrFields: {
            withCredentials: true
        },
        success: function(data) {
            console.log("私有接口返回:", data);
        },
        error: function(xhr) {
            console.error("请求失败:", xhr.responseText);
        }
    });
}

为啥浏览器扩展没用?

很多CORS扩展只是简单添加Access-Control-Allow-Origin: *,但没有正确处理OPTIONS请求的响应状态,或者你的后端认证中间件还是会拦截OPTIONS请求,所以根本问题还是要从后端配置入手,而不是依赖浏览器扩展。

内容的提问来源于stack exchange,提问作者Medo

火山引擎 最新活动