From b9192f6cdeec278dc3bec8e0e04325679918cfc8 Mon Sep 17 00:00:00 2001 From: wsczx Date: Thu, 21 Aug 2025 18:37:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=AF=81=E4=B9=A6=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E5=BC=80=E5=85=B3=EF=BC=8C=E4=BC=98=E5=8C=96=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E4=B8=8B=E8=BD=BD=E8=AF=81=E4=B9=A6=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/base/cfg.go | 1 + server/base/config.go | 1 + server/conf/server-sample.toml | 12 +++++++- server/conf/server.toml | 5 ---- server/handler/link_auth.go | 55 ++++++++++++++++++++-------------- server/handler/server.go | 7 +++-- web/src/pages/set/Other.vue | 18 +++++++++-- 7 files changed, 67 insertions(+), 32 deletions(-) diff --git a/server/base/cfg.go b/server/base/cfg.go index 8fda823..7a856db 100644 --- a/server/base/cfg.go +++ b/server/base/cfg.go @@ -47,6 +47,7 @@ type ServerConfig struct { DbSource string `json:"db_source"` CertFile string `json:"cert_file"` CertKey string `json:"cert_key"` + AuthAloneCert bool `json:"auth_alone_cert"` ClientCertCAFile string `json:"client_ca_file"` ClientCertCAKeyFile string `json:"client_ca_key_file"` FilesPath string `json:"files_path"` diff --git a/server/base/config.go b/server/base/config.go index 92e5e6f..d7afce7 100644 --- a/server/base/config.go +++ b/server/base/config.go @@ -33,6 +33,7 @@ var configs = []config{ {Typ: cfgStr, Name: "db_source", Usage: "数据库source", ValStr: "./conf/anylink.db"}, {Typ: cfgStr, Name: "cert_file", Usage: "证书文件", ValStr: "./conf/vpn_cert.pem"}, {Typ: cfgStr, Name: "cert_key", Usage: "证书密钥", ValStr: "./conf/vpn_cert.key"}, + {Typ: cfgBool, Name: "auth_alone_cert", Usage: "启用独立证书验证", ValBool: false}, {Typ: cfgStr, Name: "client_ca_file", Usage: "客户端证书CA证书", ValStr: "./conf/client_ca.pem"}, {Typ: cfgStr, Name: "client_ca_key_file", Usage: "客户端证书CA密钥", ValStr: "./conf/client_ca.key"}, {Typ: cfgStr, Name: "files_path", Usage: "外部下载文件路径", ValStr: "./conf/files"}, diff --git a/server/conf/server-sample.toml b/server/conf/server-sample.toml index 2413925..477b0e8 100644 --- a/server/conf/server-sample.toml +++ b/server/conf/server-sample.toml @@ -9,6 +9,16 @@ db_source = "./conf/anylink.db" #证书文件 使用跟nginx一样的证书即可 cert_file = "./conf/vpn_cert.pem" cert_key = "./conf/vpn_cert.key" + +#是否启用独立证书验证,开启后客户端连接需要携带证书 +#如果不开启则使用用户名密码验证 +auth_alone_cert = false + +#客户端证书CA证书 +client_cert_ca_file = "./conf/client_ca.pem" +#客户端证书CA密钥 +client_cert_ca_key_file = "./conf/client_ca.key" + files_path = "./conf/files" profile = "./conf/profile.xml" #profile name(用于区分不同服务端的配置) @@ -52,7 +62,7 @@ admin_addr = ":8800" proxy_protocol = false #开启go标准库http.Server的日志 -http_server_log=false +http_server_log = false #虚拟网络类型[tun macvtap tap] link_mode = "tun" diff --git a/server/conf/server.toml b/server/conf/server.toml index d9947c2..d937bf3 100644 --- a/server/conf/server.toml +++ b/server/conf/server.toml @@ -11,11 +11,6 @@ cert_file = "./conf/vpn_cert.pem" cert_key = "./conf/vpn_cert.key" files_path = "./conf/files" -#客户端证书CA证书 -client_cert_ca_file = "./conf/client_ca.pem" -#客户端证书CA密钥 -client_cert_ca_key_file = "./conf/client_ca.key" - #日志目录,默认为空写入标准输出 #log_path = "./log" log_level = "debug" diff --git a/server/handler/link_auth.go b/server/handler/link_auth.go index fdd6a06..9efe1e9 100644 --- a/server/handler/link_auth.go +++ b/server/handler/link_auth.go @@ -78,32 +78,43 @@ func LinkAuth(w http.ResponseWriter, r *http.Request) { return } // 检查客户端证书认证 - if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 { - clientCert := r.TLS.PeerCertificates[0] - username := clientCert.Subject.CommonName - groupname := clientCert.Subject.OrganizationalUnit[0] - if username == "" || groupname == "" { - base.Warn("客户端证书缺少用户名或组名") - w.WriteHeader(http.StatusBadRequest) - return - } - - // 验证证书有效性和用户状态 - if dbdata.ValidateClientCert(clientCert, userAgent) { - // 证书认证成功,创建会话 - base.Info("用户通过证书认证:", username) - - sessionData.ClientRequest.GroupSelect = groupname - sessionData.ClientRequest.Auth.Username = username + if base.Cfg.AuthAloneCert { + if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 { + clientCert := r.TLS.PeerCertificates[0] + username := clientCert.Subject.CommonName + groupname := clientCert.Subject.OrganizationalUnit[0] + if username == "" || groupname == "" { + base.Warn("客户端证书缺少用户名或组名") + w.WriteHeader(http.StatusBadRequest) + return + } ua.Username = username ua.GroupName = groupname - ua.Info = "用户通过证书认证登录" - ua.Status = dbdata.UserConnected - dbdata.UserActLogIns.Add(*ua, userAgent) + // 验证证书有效性和用户状态 + if dbdata.ValidateClientCert(clientCert, userAgent) { + // 证书认证成功,创建会话 + base.Info("用户通过证书认证:", username) - CreateSession(w, r, sessionData) - return + sessionData.ClientRequest.GroupSelect = groupname + sessionData.ClientRequest.Auth.Username = username + ua.Info = "用户通过证书认证登录" + ua.Status = dbdata.UserConnected + dbdata.UserActLogIns.Add(*ua, userAgent) + + CreateSession(w, r, sessionData) + return + } else { + ua.Info = "客户端证书验证失败" + ua.Status = dbdata.UserAuthFail + dbdata.UserActLogIns.Add(*ua, userAgent) + + w.WriteHeader(http.StatusForbidden) + return + } } + base.Warn("启用了独立证书验证,但用户未提供有效证书") + w.WriteHeader(http.StatusForbidden) + return } if cr.Type == "init" { diff --git a/server/handler/server.go b/server/handler/server.go index 57cf418..19ecf12 100644 --- a/server/handler/server.go +++ b/server/handler/server.go @@ -66,13 +66,16 @@ func startTls() { NextProtos: []string{"http/1.1"}, MinVersion: tls.VersionTLS12, CipherSuites: selectedCipherSuites, - ClientAuth: tls.VerifyClientCertIfGiven, // 验证客户端证书 - ClientCAs: dbdata.LoadClientCAPool(), // 加载客户端CA证书 GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { base.Trace("GetCertificate ServerName", chi.ServerName) return dbdata.GetCertificateBySNI(chi.ServerName) }, } + // 开启证书认证 + if base.Cfg.AuthAloneCert { + tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven // 验证客户端证书 + tlsConfig.ClientCAs = dbdata.LoadClientCAPool() // 加载客户端CA证书 + } srv := &http.Server{ Addr: addr, Handler: initRoute(), diff --git a/web/src/pages/set/Other.vue b/web/src/pages/set/Other.vue index af4b8de..f8bb017 100644 --- a/web/src/pages/set/Other.vue +++ b/web/src/pages/set/Other.vue @@ -561,6 +561,20 @@ export default { url: '/set/client_cert/download?' + params.toString(), responseType: 'blob' }).then(response => { + const contentType = response.headers['content-type']; + if (contentType && contentType.includes('application/json')) { + const reader = new FileReader(); + reader.onload = () => { + try { + const errorData = JSON.parse(reader.result); + this.$message.error(errorData.msg || '证书下载失败'); + } catch (e) { + this.$message.error('证书下载失败'); + } + }; + reader.readAsText(response.data); + return; + } const blob = new Blob([response.data], { type: 'application/x-pkcs12' }); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); @@ -572,8 +586,8 @@ export default { window.URL.revokeObjectURL(url); this.$message.success('证书下载成功'); }).catch(error => { - if (error.response && error.response.data && error.response.data.msg) { - this.$message.error(error.response.data.msg); + if (error.response && error.response.data) { + this.$message.error(error.response.data.msg || '证书下载失败'); } else { this.$message.error('证书下载失败'); }