安全清单

安全不是一项功能,而是贯穿开发的纪律。本页按"依赖 → 输入 → 注入 → Web → 认证 → 运行时"给出 Go 服务的核心安全实践,结尾附上线检查清单。

依赖安全:govulncheck

依赖漏洞是 Go 服务最现实的风险面。官方工具 govulncheck 扫描已知漏洞(CVE),且只报告真正被你的代码调用到的漏洞,误报极少:

go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...

# GitHub Actions 中作为独立步骤
- name: Vulnerability scan
  run: govulncheck ./...

配套纪律:依赖最小化(每加一个依赖都是加攻击面)、定期 go get -u 升级补丁版本、锁定关键依赖的版本变更评审。

输入:一切外部输入不可信

信任边界之内(你的代码)与之外(用户、第三方)之间,所有进入的数据都要校验:

// 白名单 + 长度 + 格式,三件套
var codePattern = regexp.MustCompile(`^[0-9a-zA-Z_-]{3,16}$`)

func validateCode(code string) error {
    if len(code) < 3 || len(code) > 16 {
        return errors.New("长度不符")
    }
    if !codePattern.MatchString(code) {
        return errors.New("格式不符")
    }
    return nil
}

三类高危输入场景:

  • 路径穿越:用户输入拼进文件路径时,../.. 可读任意文件。修复:filepath.Clean 后校验仍在基准目录内,或直接拒绝含分隔符的输入。
  • SSRF:接受用户提供的 URL 并出站请求时,攻击者可探测内网(http://169.254.169.254/ 云凭证端点是经典目标)。修复:解析后校验 host 非内网段、禁止重定向跟随、出站走白名单。
  • 正则输入:Go 的 RE2 引擎无回溯,天然免疫多数正则 DoS,但仍应限制输入长度。

注入防护

类型规则参考
SQL 注入永远参数化($1/?),禁止拼接 SQL数据库
命令注入os/exec 用参数数组,禁止拼 shell 字符串本页下方
模板注入html/template(自动转义),勿用 text/template 渲染 HTML-
// ❌ shell 拼接:用户输入 "a; rm -rf /" 直接执行
exec.Command("sh", "-c", "convert "+userInput)

// ✅ 参数数组:输入永远只是一个参数,无 shell 参与
cmd := exec.CommandContext(ctx, "convert", userInput)
cmd.Stdout = out

gosec(golangci-lint 中启用)会自动标记这两类问题。

Web 专项

Go 的 encoding/json 与 html/template 已规避大半 XSS(自动转义),剩余重点是响应控制:

func SecureHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h := w.Header()
        h.Set("X-Content-Type-Options", "nosniff")
        h.Set("X-Frame-Options", "DENY")
        h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
        h.Set("Content-Security-Policy", "default-src 'self'")
        next.ServeHTTP(w, r)
    })
}

func CORS(allowedOrigins []string, next http.Handler) http.Handler {
    set := make(map[string]bool, len(allowedOrigins))
    for _, o := range allowedOrigins {
        set[o] = true
    }
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")
        if set[origin] { // 白名单匹配,绝不能用 "*" 反射任意来源
            w.Header().Set("Access-Control-Allow-Origin", origin)
            w.Header().Set("Vary", "Origin")
        }
        next.ServeHTTP(w, r)
    })
}

要点:

  • Cookie 会话必须设置 HttpOnly、Secure、SameSite=Lax,可同时防 XSS 窃取与 CSRF。
  • CORS 是白名单机制,不是"允许所有来源的调试开关"。
  • HTTPS 终止可在负载均衡/网关,服务自身跳过明文端口即可;需要 Go 直接终止 TLS 时用 autocert(Let's Encrypt 自动证书)。

认证与会话

实战项目的认证栈(bcrypt + JWT)之外,生产还需补齐:

项实践
密码策略强制最小长度(12+),不设复杂度花招;bcrypt cost ≥ 10
登录防爆破按用户名 + IP 双维度限流(限流组件复用)
错误信息登录失败统一"用户名或密码错误",不暴露枚举面
JWT 生命周期短有效期(15 分钟)+ 刷新令牌;登出即吊销需配黑名单或短 TTL
传输令牌绝不进 URL,只走 Authorization 头或 HttpOnly Cookie

密钥与配置

  • 密钥只来自环境变量或密钥管理服务,代码/配置文件/日志中零硬编码(实战项目的 JWT_SECRET 即此模式)。
  • 仓库接入秘密扫描(如 gitleaks)防止误提交;泄漏过的密钥立即轮换,删除提交不等于未泄漏。
  • 日志脱敏:密码、令牌、证件号不入日志(slog 字段化日志天然便于过滤)。

运行时最小权限

  • 容器以 nonroot 运行、使用 distroless 无 shell 镜像(工程化)。
  • 管理端点(/debug/pprof、/metrics)只绑内网地址。
  • 文件系统只读挂载 + 显式声明需要的写路径。
  • 服务间调用用最小权限凭证,不共享超级账号。

上线安全检查清单

  • govulncheck ./... 零高危,且已加入 CI
  • 所有外部输入经过校验(长度/格式/白名单)
  • SQL 全参数化,exec 无 shell 拼接
  • Cookie 三件套(HttpOnly/Secure/SameSite)与 CORS 白名单确认
  • 登录接口有限流,错误信息不泄露用户存在性
  • JWT 算法锁定、短有效期、密钥来自环境
  • 日志无敏感字段,仓库有秘密扫描
  • 容器 nonroot + distroless,管理端口未对公网开放

小结

  • 依赖漏洞用 govulncheck 常态化扫描,依赖面越窄越安全。
  • 一切输入不可信:路径穿越与 SSRF 是 Go 服务两大特色风险。
  • XSS/CSRF 优先交给框架与 Cookie 属性,CORS 严格白名单。
  • 认证看生命周期(短令牌 + 限流 + 不泄露枚举面),运行时看最小权限。

安全与性能、应用问题速查共同构成生产三章的实践闭环。