JWT生成需从环境变量读取≥32字节密钥并显式设exp,用golang-jwt/jwt/v5结构体声明claims,签发后返回纯token字符串或HTTP-only Cookie,中间件须调用VerifyExpiresAt校验过期。

JWT生成前必须设置密钥和过期时间
密钥(secret)不能硬编码在代码里,更不能用短字符串或常见单词(比如 "mysecret"),否则签发的令牌极易被暴力破解。生产环境建议从环境变量读取,且长度不低于 32 字节。过期时间(exp)也得显式设置,Echo 的 jwt.NewToken() 不会自动加 exp 字段——漏掉这个,令牌就永不过期,属于高危配置。
实操建议:
- 用
os.Getenv("JWT_SECRET")读密钥,启动时校验非空 - 过期时间统一用
time.Now().Add(24 * time.Hour)计算,别直接写数字 - 密钥若为字符串,务必用
[]byte(secret)转成字节切片再传给jwt.SigningKey
用 github.com/golang-jwt/jwt/v5 签发标准令牌
Echo 本身不内置 JWT 实现,得靠第三方库。现在主流是 github.com/golang-jwt/jwt/v5(不是已归档的 dgrijalva/jwt-go)。老教程里常见的 token.Claims = jwt.MapClaims{...} 在 v5 里会报错:v5 强制使用结构体声明 claims,否则签名失败但无提示,容易卡在“返回空字符串”这种静默失败上。
一个最小可用示例:
type Claims struct {
jwt.RegisteredClaims
UserID uint `json:"user_id"`
Role string `json:"role"`
}
<p>func generateToken(userID uint, role string) (string, error) {
claims := &Claims{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
UserID: userID,
Role: role,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(os.Getenv("JWT_SECRET")))
}
登录成功后通过 Echo.Context 写入响应头或 JSON
生成完 token 后,别直接拼 Bearer xxx 放响应体里——前端要的是干净的 token 字符串,由它自己决定怎么塞进 Authorization 头。常见错误是把 token 包进 map[string]interface{} 返回,导致前端多一层解析,还可能触发 XSS 风险(如果没做转义)。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
推荐做法:
- HTTP 状态码用
c.JSON(200, map[string]string{"token": tokenStr}),字段名固定为"token" - 如需设 HTTP-only Cookie(更安全),用
c.SetCookie(&http.Cookie{Name: "auth_token", Value: tokenStr, HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode}) - 绝对不要用
c.Header("Authorization", "Bearer "+tokenStr)—— 这是服务端往响应头写,客户端收不到,也不符合规范
注意 v5 版本的签名方法和错误处理差异
v5 把签名校验逻辑拆得更细,token.Valid 只检查签名格式,不校验 exp 或 nbf;真正验证过期要用 token.VerifyExpiresAt(time.Now(), true)。很多人在中间件里只判 token.Valid 就放行,结果过期 token 依然能通过。
中间件中正确校验姿势:
token, err := jwt.ParseWithClaims(authHeader[7:], &Claims{}, func(t *jwt.Token) (interface{}, error) {
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil || !token.Valid {
return echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired token")
}
if !token.Claims.(*Claims).VerifyExpiresAt(time.Now(), true) {
return echo.NewHTTPError(http.StatusUnauthorized, "token expired")
}
密钥轮换、多算法支持、refresh token 流程这些进阶点,都建立在先跑通这个基础链路之上。没验证 exp 就上线,等于没设防。

















