安全编码指南
输入验证
// 白名单验证(优于黑名单)
func validateEmail(email string) bool {
pattern := `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`
matched, _ := regexp.MatchString(pattern, email)
return matched && len(email) <= 254
}
// 数值范围验证
func validateAge(age int) error {
if age < 0 || age > 150 {
return errors.New("年龄必须在 0 到 150 之间")
}
return nil
}
// 文件上传验证
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/webp": true,
}
// 始终检查魔术字节,而非仅检查扩展名或 Content-Type
密码哈希
// Go - bcrypt(推荐)
import "golang.org/x/crypto/bcrypt"
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// 绝不使用:
// md5.Sum([]byte(password))
// sha256.Sum256([]byte(password)) // 快速哈希,不适合密码
密钥管理
// 绝不硬编码密钥
// 不好的做法:
apiKey := "sk-live-abc123def456..."
// 好的做法:环境变量
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
log.Fatal("OPENAI_API_KEY 未设置")
}
// 更好的做法:密钥管理服务
// AWS Secrets Manager、HashiCorp Vault、GCP Secret Manager
// .gitignore 必须包含:
// .env
// *.pem
// *.key
// credentials.json
HTTP 安全响应头
// Go/Gin 中间件
func SecurityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("X-XSS-Protection", "1; mode=block")
c.Header("Referrer-Policy", "strict-origin-when-cross-origin")
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.Header("Content-Security-Policy",
"default-src 'self'; script-src 'self'")
c.Next()
}
}
安全编码原则
| 原则 | 说明 |
|---|---|
| 纵深防御 | 多层安全控制;单点失效不会导致系统沦陷 |
| 最小权限 | 每个组件只授予其所需的最小权限 |
| 安全失败 | 发生错误时拒绝访问而非放行 |
| 关注点分离 | 隔离认证、数据、业务逻辑 |
| 不信任输入 | 验证和净化所有外部数据 |
| 保持简单 | 复杂代码漏洞更多;简洁即安全 |