8.1 安全相关名词
- 认证 vs 授权
- 认证(Authentication)确认「你是谁」;授权(Authorization)确认「你能做什么」。登录是认证,角色权限是授权。
- JWT(JSON Web Token)
- 由 header.payload.signature 三段组成的自包含令牌,服务端用密钥签名,无需存储会话即可校验,适合无状态 API。
- Session
- 有状态会话:服务端存 session 数据,客户端只持 cookie 中的 session ID,适合传统 Web 与需要即时失效的场景。
- CORS(跨域资源共享)
- 浏览器同源策略下,跨域请求需服务端返回
Access-Control-Allow-*响应头显式放行。 - 限流(Rate Limiting)
- 限制单位时间请求数,防刷防打爆,常用令牌桶/漏桶算法。
- CSRF(跨站请求伪造)
- 诱导已登录用户在第三方站点触发请求。用 CSRF token 或 SameSite cookie 防护。
- bcrypt
- 专为密码设计的慢哈希算法,自带盐(salt)与可调 cost,抵御彩虹表与暴力破解。
8.2 密码哈希 bcrypt
import "golang.org/x/crypto/bcrypt"
// 注册时:哈希存库(绝不明文存密码!)
func HashPassword(pw string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
return string(b), err
}
// 登录时:比对
func CheckPassword(hash, pw string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) == nil
}
8.3 签发 JWT
import "github.com/golang-jwt/jwt/v5"
type Claims struct {
UserID uint `json:"uid"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func GenToken(uid uint, role, secret string) (string, error) {
claims := Claims{
UserID: uid, Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return t.SignedString([]byte(secret))
}
8.4 JWT 认证中间件
func JWTAuth(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
auth := c.GetHeader("Authorization")
// 期望格式:Bearer <token>
if !strings.HasPrefix(auth, "Bearer ") {
c.AbortWithStatusJSON(401, gin.H{"error": "missing token"})
return
}
tokenStr := strings.TrimPrefix(auth, "Bearer ")
var claims Claims
token, err := jwt.ParseWithClaims(tokenStr, &claims,
func(t *jwt.Token) (any, error) {
// 校验签名算法,防 alg=none 攻击
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(secret), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(401, gin.H{"error": "invalid token"})
return
}
c.Set("userID", claims.UserID)
c.Set("role", claims.Role)
c.Next()
}
}
// 授权:基于角色
func RequireRole(role string) gin.HandlerFunc {
return func(c *gin.Context) {
if c.GetString("role") != role {
c.AbortWithStatusJSON(403, gin.H{"error": "forbidden"})
return
}
c.Next()
}
}
JWT 无法即时失效
签发后到期前一直有效,「登出/封号立即生效」需要额外机制:短时效 access token + refresh token,或维护一个 Redis 黑名单/白名单。密钥务必从环境变量注入,切勿硬编码进代码或提交仓库。
8.5 CORS 跨域
import "github.com/gin-contrib/cors"
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://gufacode.com"}, // 别用 * 配 credentials
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
8.6 令牌桶限流
import "golang.org/x/time/rate"
// 全局:每秒 100 个令牌,突发 200
func RateLimit(r rate.Limit, burst int) gin.HandlerFunc {
limiter := rate.NewLimiter(r, burst)
return func(c *gin.Context) {
if !limiter.Allow() {
c.AbortWithStatusJSON(429, gin.H{"error": "too many requests"})
return
}
c.Next()
}
}
// 按 IP/用户限流可用 map[string]*rate.Limiter + 定期清理,或 Redis 分布式限流
8.7 其他安全清单
| 威胁 | 防护 |
|---|---|
| SQL 注入 | 用 GORM/参数化查询,绝不拼字符串 |
| XSS | 模板自动转义;API 返回 JSON 由前端转义 |
| CSRF | SameSite=Lax/Strict cookie 或 csrf token |
| 敏感信息泄露 | 响应过滤密码哈希字段,用 json:"-" 排除 |
| 明文传输 | 强制 HTTPS,HSTS 头,反代终止 TLS |
| 暴力破解 | 登录接口限流 + 失败锁定 + 验证码 |
8.8 小结
- 密码用 bcrypt 哈希存储,登录用 CompareHashAndPassword 比对。
- 无状态 API 用 JWT,中间件校验签名与算法,Set 用户信息给下游;角色授权单独中间件。
- CORS 精确白名单,配 credentials 时不能用
*;限流用令牌桶,分布式场景上 Redis。 - JWT 无法即时失效,需 refresh token 或黑名单;密钥从环境注入。
- 下一章讲 工程化——分层、viper 配置、zap 日志、优雅关机、Swagger。