登录接口须用 bcrypt 校验密码哈希,禁止明文比对;注册用 generatefrompassword(cost=12),登录用 comparehashandpassword;需手动校验用户名密码非空且长度合规。

登录接口必须校验密码哈希,别直接比对明文
用户密码绝不能以明文形式存储或比对。bcrypt 是 Gin 生态中最稳妥的选择,它自带盐值和可调成本因子。用 bcrypt.CompareHashAndPassword 校验,而不是自己写字符串相等判断——后者会引入时序攻击风险。
常见错误是:查出用户后直接 user.Password == input.Password,这既不安全也不符合密码学实践。
- 注册时用
bcrypt.GenerateFromPassword存哈希值(推荐cost=12) - 登录时用
bcrypt.CompareHashAndPassword(hashed, []byte(input.Password)),返回nil才算通过 - 若数据库查不到用户,也统一返回“用户名或密码错误”,避免泄露账号存在性
用 ShouldBindJSON 解析登录参数,但得手动校验非空
ShouldBindJSON 能自动映射 JSON 字段到结构体,但它默认不拒绝空字符串或零值字段。比如用户传 {"username":"","password":""},结构体字段会是空,但不会报错。
登录场景下,username 和 password 都必须非空且长度合理(如 username ≤ 32 字符,password ≥ 8 字符)。
- 定义结构体时加
binding:"required"标签,但注意:它只校验字段是否缺失,不校验空字符串 - 务必在 Bind 后追加手动检查:
if strings.TrimSpace(loginReq.Username) == "" || len(strings.TrimSpace(loginReq.Password)) - 错误响应统一用
c.AbortWithStatusJSON(400, gin.H{"error": "用户名或密码格式不正确"})
生成 JWT 令牌时别硬编码密钥,且要设好过期时间
Gin 本身不内置 JWT 支持,一般搭配 github.com/golang-jwt/jwt/v5。密钥如果写死在代码里(比如 var secret = "my-secret"),上线后极难轮换,也容易被反编译提取。
过期时间设太长(如 7 天)会增加令牌泄露后的风险;设太短(如 5 分钟)又影响体验,需配合 refresh token 机制——但简单登录接口可先用 2 小时。
- 从环境变量读密钥:
os.Getenv("JWT_SECRET"),启动前确保已设置 - payload 中至少包含
sub(用户 ID)、exp(过期时间戳)、iat(签发时间) - 签名用
jwt.SigningMethodHS256,别用None算法(存在严重漏洞) - 响应中把令牌放在
Authorization: Bearer <token></token>头里,或 JSON body 的token字段,保持前后端约定一致
别忘了在路由层加 POST /login,且禁用中间件
登录接口必须是公开的,不能挂载鉴权中间件(比如 AuthMiddleware()),否则永远进不来。Gin 路由树是显式声明的,没加中间件就是裸路由。
另一个易错点:用 GET /login?username=xxx&password=yyy —— 密码会留在 access log、代理缓存、浏览器历史中,必须用 POST + JSON body。
- 注册路由写成:
r.POST("/login", LoginHandler),不要写在r.Use(...)之后的分组里 - 确保前端请求头含
Content-Type: application/json,否则ShouldBindJSON会静默失败 - 本地调试可用 curl:
curl -X POST http://localhost:8080/login -H "Content-Type: application/json" -d '{"username":"test","password":"12345678"}'











