jwt签名密钥必须用[]byte而非string直接传入,因jwt.signingmethodhs256要求密钥为[]byte类型,字符串直传会导致“key is of invalid type”错误;生产环境密钥长度应≥32字节,并从环境变量加载校验。

JWT签名密钥必须用[]byte而非string直接传入jwt.SigningMethodHS256
很多人在调用jwt.NewWithClaims后,直接把字符串密钥传给token.SignedString("my-secret"),结果得到crypto/hmac: key is of invalid type string错误。Go的jwt-go(v3及以前)要求密钥必须是[]byte类型,哪怕只是临时转换也要显式写[]byte("my-secret")。
实际使用中建议从环境变量加载并转为[]byte:
secret := os.Getenv("JWT_SECRET")
if secret == "" {
log.Fatal("JWT_SECRET is required")
}
signingKey := []byte(secret)
- 硬编码
[]byte("xxx")仅用于开发,上线必须走配置或环境变量 - 如果用
github.com/golang-jwt/jwt/v5(新库),接口已改为接受interface{},但为兼容旧逻辑和明确语义,仍推荐传[]byte - 密钥长度不足32字节会削弱HS256安全性,生产环境建议至少32随机字节(可用
openssl rand -hex 32生成)
Echo中间件里解析Token要手动检查token.Valid且捕获jwt.ValidationError
仅仅调用token, err := jwt.Parse(...)不够——err为nil不代表Token有效,它可能过期、被篡改、签发时间异常等。必须显式判断token.Valid,否则攻击者可构造过期但签名合法的Token绕过鉴权。
典型错误写法:if err != nil { return echo.NewHTTPError(401) }就返回,没管token.Valid。
正确流程应为:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, echo.NewHTTPError(401, "invalid signing method")
}
return signingKey, nil
})
if err != nil || !token.Valid {
return echo.NewHTTPError(401, "invalid or expired token")
}
-
jwt.Parse成功但token.Valid == false时,err可能是nil,也可能是一个*jwt.ValidationError,需统一按!token.Valid判断 - 若需区分过期(
ValidationErrorExpired)和签名无效(ValidationErrorSignatureInvalid),可类型断言err.(*jwt.ValidationError)后检查Errors字段 - 不要在中间件里直接
c.Set("user_id", ...)就完事,务必先确认token.Valid为true
用户角色鉴权不能只靠中间件拦截,必须在Handler内二次校验c.Get("role")
常见做法是在JWT中间件里解析出claims["role"]并存入c.Set("role", role),然后在需要权限的Handler里直接读取。但这存在风险:中间件一旦漏掉或被绕过,c.Get("role")可能为nil或默认值,导致越权。
更稳妥的方式是把鉴权逻辑下沉到Handler内部,每次操作前重新从Token Claims中提取并校验:
token := c.Get("user").(*jwt.Token)
claims := token.Claims.(jwt.MapClaims)
role := claims["role"].(string)
if role != "admin" {
return echo.NewHTTPError(403, "insufficient permissions")
}
- 避免依赖
c.Set/c.Get链路的完整性,Token本身才是唯一可信源 - 如果Role来自数据库动态加载(如RBAC),不应塞进JWT,而应在Handler里查DB比对——JWT只放不变的标识(如
user_id),动态权限走服务端校验 - 注意
claims["role"]类型断言要加ok判断,防止panic:if role, ok := claims["role"].(string); !ok { ... }
Refresh Token机制需独立存储+设置短过期,不能复用Access Token的JWT逻辑
很多人试图用两个不同有效期的JWT(一个15分钟,一个7天)实现refresh,但这是错的:Refresh Token一旦泄露,攻击者就能长期换新Token。它必须服务端可撤销,且不能是无状态JWT。
正确做法是:Access Token保持短时效(如15分钟)、无状态;Refresh Token用随机字符串(如uuid.NewString()),存入Redis,设置过期时间(如7天)和关联的user_id与issued_at。
- 发放时:Access Token含
user_id和exp;Refresh Token字符串存Redis,key为refresh:{token},value为json{"user_id":123,"issued_at":171...} - 刷新时:校验Refresh Token是否存在于Redis,且
issued_at未被覆盖(防重放),验证通过后发新Access Token,并删旧Refresh Token、写新Refresh Token - 退出登录时:删掉Redis里该用户的全部Refresh Token key(可通过
user_id索引)
JWT本身不适合做Refresh Token——它的不可撤销性是设计使然,强行用长时效JWT当refresh,等于放弃安全底线。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










