fix: 修复CSP白屏并优化LDAP配置

This commit is contained in:
Logan.Li committed 2026-07-25 22:28:16 +08:00
1 parent a14d9e62e6
commit c7f59f2cbf
12 files changed
+930 -70

No files matched your search

+48 -26
View File
@@ -6,7 +6,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/http"
"strconv"
"strings"
@@ -19,6 +18,7 @@ import (
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/bcrypt"
"vpn-platform/backend/internal/ldaputil"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/service/ikev2"
"vpn-platform/backend/internal/service/l2tp"
@@ -85,27 +85,20 @@ func ldapSettings() (*model.LDAPConfig, error) {
}
func ldapConnect(cfg *model.LDAPConfig) (*ldap.Conn, error) {
url := cfg.URL
if !strings.HasPrefix(url, "ldap://") && !strings.HasPrefix(url, "ldaps://") {
// 安全默认:未显式指定协议时按 ldaps,避免口令明文过网
url = "ldaps://" + url
}
conn, err := ldap.DialURL(url)
dialURL, err := ldaputil.DialURL(cfg.URL)
if err != nil {
return nil, err
}
if cfg.StartTLS && strings.HasPrefix(url, "ldap://") {
host := strings.TrimPrefix(url, "ldap://")
if h, _, err := net.SplitHostPort(host); err == nil {
host = h
}
conn, err := ldap.DialURL(dialURL)
if err != nil {
return nil, err
}
if cfg.StartTLS && strings.HasPrefix(dialURL, "ldap://") {
host := ldaputil.Hostname(dialURL)
if err := conn.StartTLS(&tls.Config{ServerName: host}); err != nil {
conn.Close()
return nil, fmt.Errorf("StartTLS 升级失败: %w", err)
}
} else if strings.HasPrefix(url, "ldap://") {
conn.Close()
return nil, fmt.Errorf("明文 ldap:// 存在口令泄露风险,请改用 ldaps:// 或开启 StartTLS")
}
if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.BindPassword); err != nil {
@@ -126,7 +119,12 @@ func (s *Server) authenticateLDAP(username, password string) (*model.User, error
return nil, err
}
defer conn.Close()
filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UsernameAttribute, ldap.EscapeFilter(username))
filter := cfg.UserFilter
if strings.Contains(filter, "%s") {
filter = fmt.Sprintf(filter, ldap.EscapeFilter(username))
} else {
filter = fmt.Sprintf("(&%s(%s=%s))", filter, cfg.UsernameAttribute, ldap.EscapeFilter(username))
}
result, err := conn.Search(ldap.NewSearchRequest(cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 5, false, filter, []string{"dn", cfg.UsernameAttribute, "cn", "mail"}, nil))
if err != nil || len(result.Entries) != 1 {
return nil, fmt.Errorf("LDAP 用户不存在")
@@ -361,6 +359,12 @@ func (s *Server) saveLDAPConfig(c *gin.Context) {
c.JSON(400, gin.H{"error": "LDAP 地址与 Base DN 必填"})
return
}
if req.Enabled {
if _, err := ldaputil.DialURL(req.URL); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
}
var cfg model.LDAPConfig
store.DB.First(&cfg)
cfg.URL, cfg.BindDN, cfg.BaseDN, cfg.UserFilter, cfg.UsernameAttribute, cfg.StartTLS, cfg.Enabled =
@@ -373,17 +377,35 @@ func (s *Server) saveLDAPConfig(c *gin.Context) {
}
func (s *Server) testLDAP(c *gin.Context) {
cfg, err := ldapSettings()
if err == nil {
conn, connErr := ldapConnect(cfg)
if connErr == nil {
conn.Close()
c.JSON(200, gin.H{"ok": true})
return
}
err = connErr
var req struct {
URL string `json:"url"`
BindDN string `json:"bind_dn"`
BindPassword string `json:"bind_password"`
StartTLS bool `json:"starttls"`
}
c.JSON(400, gin.H{"error": "LDAP 连接失败: " + err.Error()})
if c.ShouldBindJSON(&req) != nil || strings.TrimSpace(req.URL) == "" {
c.JSON(400, gin.H{"error": "LDAP 服务器地址必填"})
return
}
cfg := &model.LDAPConfig{URL: strings.TrimSpace(req.URL), BindDN: strings.TrimSpace(req.BindDN), BindPassword: req.BindPassword, StartTLS: req.StartTLS}
if cfg.BindPassword == "" {
var saved model.LDAPConfig
if store.DB.First(&saved).Error == nil && saved.BindPassword != "" {
plain, err := store.DecryptSecret(saved.BindPassword)
if err != nil {
c.JSON(400, gin.H{"error": "LDAP 配置密钥无效"})
return
}
cfg.BindPassword = plain
}
}
conn, err := ldapConnect(cfg)
if err != nil {
c.JSON(400, gin.H{"error": "LDAP 连接失败: " + err.Error()})
return
}
conn.Close()
c.JSON(200, gin.H{"ok": true})
}
func (s *Server) listVPNUsers(c *gin.Context) {