// Package security 提供安全相关的 HTTP 中间件。 // // 该文件实现 HTTP Basic 认证中间件,支持安全的密码哈希 // (bcrypt 和 argon2id)。默认强制使用 HTTPS。 // // 使用示例: // // cfg := &config.AuthConfig{ // Type: "basic", // RequireTLS: true, // Algorithm: "bcrypt", // Users: []config.User{ // {Name: "admin", Password: "$2b$12$..."}, // bcrypt 哈希 // }, // Realm: "Restricted Area", // } // // auth, err := security.NewBasicAuth(cfg) // if err != nil { // log.Fatal(err) // } // // // 应用为中间件 // chain := middleware.NewChain(auth) // handler := chain.Apply(finalHandler) // // 作者:xfy package security import ( "crypto/rand" "encoding/base64" "errors" "fmt" "strings" "sync" "github.com/valyala/fasthttp" "golang.org/x/crypto/argon2" "golang.org/x/crypto/bcrypt" "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/middleware" ) // HashAlgorithm 表示密码哈希算法类型。 type HashAlgorithm int const ( // HashBcrypt bcrypt 算法(默认,推荐) HashBcrypt HashAlgorithm = iota // HashArgon2id Argon2id 算法(更安全,计算密集) HashArgon2id ) // BasicAuth 实现 HTTP Basic 认证中间件。 type BasicAuth struct { users map[string]string realm string algorithm HashAlgorithm mu sync.RWMutex argon2Params argon2Params requireTLS bool } // argon2Params 保存 Argon2id 配置参数。 type argon2Params struct { // time 迭代次数 time uint32 // memory 内存成本(KB) memory uint32 // threads 并行度 threads uint8 // saltLen 盐长度 saltLen uint32 // keyLen 输出密钥长度 keyLen uint32 } // defaultArgon2Params 默认 Argon2id 参数(OWASP 推荐) var defaultArgon2Params = argon2Params{ time: 3, memory: 64 * 1024, // 64 MB threads: 4, saltLen: 16, keyLen: 32, } // NewBasicAuth 创建 Basic 认证中间件。 // // 根据配置创建认证中间件实例,解析用户列表并设置哈希算法。 // // 参数: // - cfg: 认证配置,包含用户列表和设置 // // 返回值: // - *BasicAuth: 配置好的认证中间件 // - error: 配置无效时返回错误 func NewBasicAuth(cfg *config.AuthConfig) (*BasicAuth, error) { if cfg == nil { return nil, errors.New("auth config is nil") } if cfg.Type != "basic" { return nil, fmt.Errorf("unsupported auth type: %s", cfg.Type) } if len(cfg.Users) == 0 { return nil, errors.New("no users configured") } auth := &BasicAuth{ users: make(map[string]string), requireTLS: cfg.RequireTLS, // Default is true from config defaults argon2Params: defaultArgon2Params, } // 设置认证域 if cfg.Realm != "" { auth.realm = cfg.Realm } else { auth.realm = "Restricted Area" } // 设置哈希算法 switch strings.ToLower(cfg.Algorithm) { case "bcrypt", "": auth.algorithm = HashBcrypt case "argon2id": auth.algorithm = HashArgon2id default: return nil, fmt.Errorf("unsupported hash algorithm: %s", cfg.Algorithm) } // 加载用户 for _, user := range cfg.Users { if user.Name == "" { return nil, errors.New("username cannot be empty") } if user.Password == "" { return nil, fmt.Errorf("password for user %s cannot be empty", user.Name) } // 验证密码哈希格式 if err := validatePasswordHash(user.Password, auth.algorithm); err != nil { return nil, fmt.Errorf("invalid password hash for user %s: %w", user.Name, err) } auth.users[user.Name] = user.Password } return auth, nil } // Name 返回中间件名称。 // // 返回值: // - string: 中间件标识名 "basic_auth" func (ba *BasicAuth) Name() string { return "basic_auth" } // Process 用认证逻辑包装下一个处理器。 // // 认证失败返回 401 Unauthorized。 // // 参数: // - next: 下一个请求处理器 // // 返回值: // - fasthttp.RequestHandler: 包装后的处理器 func (ba *BasicAuth) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { // 检查 TLS 要求 if ba.requireTLS && !ctx.IsTLS() { ctx.Error("Forbidden: HTTPS required for authentication", fasthttp.StatusForbidden) return } // 提取并验证凭据 username, password, ok := ba.extractCredentials(ctx) if !ok { ba.sendAuthChallenge(ctx) return } // 执行认证 if !ba.Authenticate(username, password) { ba.sendAuthChallenge(ctx) return } // 认证成功,存储用户名到上下文(用于访问日志 $remote_user) ctx.SetUserValue("remote_user", username) // 继续执行下一个处理器 next(ctx) } } // Authenticate 验证用户名和密码凭据。 // // 根据配置的哈希算法验证密码,返回验证结果。 // // 参数: // - username: 用户名 // - password: 明文密码 // // 返回值: // - bool: true 表示认证成功,false 表示失败 func (ba *BasicAuth) Authenticate(username, password string) bool { ba.mu.RLock() hashedPassword, exists := ba.users[username] ba.mu.RUnlock() if !exists { return false } switch ba.algorithm { case HashBcrypt: return authenticateBcrypt(password, hashedPassword) case HashArgon2id: return authenticateArgon2id(password, hashedPassword) default: return false } } // authenticateBcrypt 使用 bcrypt 验证密码。 // // 参数: // - password: 明文密码 // - hash: bcrypt 哈希值 // // 返回值: // - bool: true 表示验证通过 func authenticateBcrypt(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // authenticateArgon2id 使用 Argon2id 验证密码。 // // 哈希格式:$argon2id$v=19$m=,t=