From 19723602075444f68fc0b381f01059dd1c91994b Mon Sep 17 00:00:00 2001 From: MayueCif Date: Tue, 21 Jul 2026 10:25:11 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=86=E5=8C=96LDAP=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=8E=E5=BC=82=E5=B8=B8=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增4个LDAP相关异常码至UserFriendlyExceptionCodes,细分连接失败原因。引入LdapAuthenticateHelper,分阶段校验LDAP连接并抛出明确异常,提升问题定位效率。LdapCommandHandler统一调用新工具类,增强代码复用与异常一致性。修正LdapConnectTestCommandValidator泛型参数。多语言资源补充异常码本地化提示。_Imports.cs全局引入Ldap命名空间。 --- .../Constants/UserFriendlyExceptionCodes.cs | 4 + .../LdapConnectTestCommandValidator.cs | 2 +- .../Subjects/LdapCommandHandler.cs | 13 +-- .../Assets/I18n/en-US.json | 4 + .../Assets/I18n/ja-JP.json | 4 + .../Assets/I18n/ru-RU.json | 4 + .../Assets/I18n/zh-CN.json | 4 + .../Ldap/LdapAuthenticateHelper.cs | 100 ++++++++++++++++++ .../Masa.Auth.Service.Admin/_Imports.cs | 1 + 9 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 src/Services/Masa.Auth.Service.Admin/Infrastructure/Ldap/LdapAuthenticateHelper.cs diff --git a/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs b/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs index aedd917e..4d23a4ab 100644 --- a/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs +++ b/src/Contracts/Masa.Auth.Contracts.Admin/Infrastructure/Constants/UserFriendlyExceptionCodes.cs @@ -16,6 +16,10 @@ public static class UserFriendlyExceptionCodes public const string INVALID_EMAIL_CAPTCHA = "InvalidEmailCaptcha"; public const string INVALID_SMS_CAPTCHA = "InvalidSMSCaptcha"; public const string CONNECT_ERROR = "ConnectError"; + public const string LDAP_SERVER_UNREACHABLE = "LdapServerUnreachable"; + public const string LDAP_SSL_MISMATCH = "LdapSslMismatch"; + public const string LDAP_CREDENTIALS_INVALID = "LdapCredentialsInvalid"; + public const string LDAP_BIND_FAILED = "LdapBindFailed"; public const string INVALID_SEND_EMAIL_TYPE = "InvalidSendEmailType"; public const string INVALID_SEND_MSG_CODE_TYPE = "InvalidSendMsgCodeType"; public const string INVALID_RESET_PASSWORD_TYPE = "InvalidResetPasswordType"; diff --git a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/LdapConnectTestCommandValidator.cs b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/LdapConnectTestCommandValidator.cs index 0a81f404..6cdd66d6 100644 --- a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/LdapConnectTestCommandValidator.cs +++ b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/Commands/LdapConnectTestCommandValidator.cs @@ -3,7 +3,7 @@ namespace Masa.Auth.Service.Admin.Application.Subjects.Commands; -public class LdapConnectTestCommandValidator : AbstractValidator +public class LdapConnectTestCommandValidator : AbstractValidator { public LdapConnectTestCommandValidator() { diff --git a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/LdapCommandHandler.cs b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/LdapCommandHandler.cs index 998bcf60..7938701d 100644 --- a/src/Services/Masa.Auth.Service.Admin/Application/Subjects/LdapCommandHandler.cs +++ b/src/Services/Masa.Auth.Service.Admin/Application/Subjects/LdapCommandHandler.cs @@ -31,12 +31,7 @@ public LdapCommandHandler( [EventHandler] public async Task LdapConnectTestAsync(LdapConnectTestCommand ldapConnectTestCommand) { - var ldapOptions = ldapConnectTestCommand.LdapDetailDto.Adapt(); - var ldapProvider = _ldapFactory.CreateProvider(ldapOptions); - if (!await ldapProvider.AuthenticateAsync(ldapOptions.RootUserDn, ldapOptions.RootUserPassword)) - { - throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.CONNECT_ERROR); - } + await LdapAuthenticateHelper.AuthenticateOrThrowAsync(ldapConnectTestCommand.LdapDetailDto); } [EventHandler] @@ -44,12 +39,10 @@ public async Task LdapUpsertAsync(LdapUpsertCommand ldapUpsertCommand) { var ldapIdpDto = ldapUpsertCommand.LdapDetailDto; + await LdapAuthenticateHelper.AuthenticateOrThrowAsync(ldapIdpDto); + var ldapOptions = ldapIdpDto.Adapt(); var ldapProvider = _ldapFactory.CreateProvider(ldapOptions); - if (!await ldapProvider.AuthenticateAsync(ldapOptions.RootUserDn, ldapOptions.RootUserPassword)) - { - throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.CONNECT_ERROR); - } var ldapIdp = new LdapIdp( ldapIdpDto.ServerAddress, diff --git a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/en-US.json b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/en-US.json index 6c0dbd81..26c758f9 100644 --- a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/en-US.json +++ b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/en-US.json @@ -14,6 +14,10 @@ "InvalidSMSCaptcha": "The sms captcha is invalid", "InvalidCaptcha": "Invalid Captcha", "ConnectError": "connect error", + "LdapServerUnreachable": "[Address/Port] Unable to reach LDAP server {0}:{1}. Please verify the server address and port", + "LdapSslMismatch": "[LDAP/LDAPS or TLS error] The {0} handshake failed for {1}:{2}. Verify the protocol, port, and server certificate", + "LdapCredentialsInvalid": "[Credentials] Invalid RootUserDn or password. Please check and retry", + "LdapBindFailed": "[LDAP authentication] The server rejected the bind or the connection was interrupted. Check the account permissions and LDAP server policy", "InvalidSendEmailType": "Invalid SendEmailType", "InvalidSendMsgCodeType": "Invalid SendMsgCodeType", "InvalidResetPasswordType": "Invalid ResetPasswordType", diff --git a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ja-JP.json b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ja-JP.json index 87389a56..43606dab 100644 --- a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ja-JP.json +++ b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ja-JP.json @@ -14,6 +14,10 @@ "InvalidSMSCaptcha": "SMS認証コードが無効です", "InvalidCaptcha": "認証コードが無効です", "ConnectError": "接続エラー", + "LdapServerUnreachable": "【アドレス/ポートエラー】LDAPサーバー {0}:{1} に接続できません。アドレスとポートを確認してください", + "LdapSslMismatch": "【LDAP/LDAPSまたはTLSエラー】{0} で {1}:{2} に接続する際にハンドシェイクに失敗しました。プロトコル、ポート、サーバー証明書を確認してください", + "LdapCredentialsInvalid": "【アカウント/パスワードエラー】RootUserDnまたはパスワードが正しくありません", + "LdapBindFailed": "【LDAP認証失敗】LDAPサーバーがBind要求を拒否したか、接続が中断されました。アカウント権限とLDAPサーバーの認証ポリシーを確認してください", "InvalidSendEmailType": "SendEmailTypeが無効です", "InvalidSendMsgCodeType": "SendMsgCodeTypeが無効です", "InvalidResetPasswordType": "パスワードリセットタイプが無効です", diff --git a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ru-RU.json b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ru-RU.json index 978dfae9..77cf0906 100644 --- a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ru-RU.json +++ b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/ru-RU.json @@ -14,6 +14,10 @@ "InvalidSMSCaptcha": "Неверный SMS-код", "InvalidCaptcha": "Неверный код подтверждения", "ConnectError": "Ошибка соединения", + "LdapServerUnreachable": "[Адрес/порт] Не удалось подключиться к LDAP-серверу {0}:{1}. Проверьте адрес и порт", + "LdapSslMismatch": "[LDAP/LDAPS или TLS] Не удалось выполнить рукопожатие {0} с {1}:{2}. Проверьте протокол, порт и сертификат сервера", + "LdapCredentialsInvalid": "[Учетные данные] Неверный RootUserDn или пароль", + "LdapBindFailed": "[Аутентификация LDAP] Сервер отклонил запрос Bind или соединение было прервано. Проверьте права учетной записи и политику LDAP-сервера", "InvalidSendEmailType": "Неверный тип SendEmailType", "InvalidSendMsgCodeType": "Неверный тип SendMsgCodeType", "InvalidResetPasswordType": "Неверный тип ResetPasswordType", diff --git a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/zh-CN.json b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/zh-CN.json index 9171db97..c8196f3f 100644 --- a/src/Services/Masa.Auth.Service.Admin/Assets/I18n/zh-CN.json +++ b/src/Services/Masa.Auth.Service.Admin/Assets/I18n/zh-CN.json @@ -14,6 +14,10 @@ "InvalidSMSCaptcha": "短信验证码无效", "InvalidCaptcha": "验证码无效", "ConnectError": "连接错误", + "LdapServerUnreachable": "【地址/端口错误】无法连接到 LDAP 服务器 {0}:{1},请检查服务器地址和端口", + "LdapSslMismatch": "【LDAP/LDAPS 或 TLS 错误】使用 {0} 连接 {1}:{2} 时握手失败,请检查协议、端口和服务器证书", + "LdapCredentialsInvalid": "【域账号/密码错误】RootUserDn 或密码不正确,请检查后重试", + "LdapBindFailed": "【LDAP 认证失败】LDAP 服务器拒绝了 Bind 请求或连接被中断,请检查账号权限及服务器认证策略", "InvalidSendEmailType": "SendEmailType无效", "InvalidSendMsgCodeType": "SendMsgCodeType无效", "InvalidResetPasswordType": "ResetPasswordType无效", diff --git a/src/Services/Masa.Auth.Service.Admin/Infrastructure/Ldap/LdapAuthenticateHelper.cs b/src/Services/Masa.Auth.Service.Admin/Infrastructure/Ldap/LdapAuthenticateHelper.cs new file mode 100644 index 00000000..7675cba7 --- /dev/null +++ b/src/Services/Masa.Auth.Service.Admin/Infrastructure/Ldap/LdapAuthenticateHelper.cs @@ -0,0 +1,100 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +using System.Net.Sockets; + +namespace Masa.Auth.Service.Admin.Infrastructure.Ldap; + +/// +/// LDAP 连接校验,按失败阶段返回明确提示: +/// 1. TCP 地址/端口不可达 → +/// 2. LDAP/LDAPS 握手失败 → +/// 3. LDAP result code 49 → +/// 4. 其它 Bind 失败 → +/// +public static class LdapAuthenticateHelper +{ + /// + /// LDAP result code: invalidCredentials (RFC 4511). + /// + const int LdapInvalidCredentials = 49; + + /// + /// Connect timeout for LDAP connect-test / save validation (milliseconds). + /// + const int ConnectionTimeoutMs = 10_000; + + public static async Task AuthenticateOrThrowAsync(LdapDetailDto ldapDetailDto) + { + await EnsureTcpConnectionAsync(ldapDetailDto); + + using var connection = new LdapConnection + { + SecureSocketLayer = ldapDetailDto.IsLdaps, + ConnectionTimeout = ConnectionTimeoutMs + }; + + // Stage 1: TCP / TLS connect — address、port、LDAP vs LDAPS + try + { + await connection.ConnectAsync(ldapDetailDto.ServerAddress, ldapDetailDto.ServerPort); + } + catch (Exception ex) + { + // TCP 已经连通,此时失败发生在 LDAP 明文协议或 TLS 握手阶段。 + // 不再把它误报为“地址/端口不可达”。 + throw CreateProtocolException(ldapDetailDto); + } + + // Stage 2: Bind — credentials + try + { + await connection.BindAsync(ldapDetailDto.RootUserDn, ldapDetailDto.RootUserPassword); + } + catch (LdapException ex) when (ex.ResultCode == LdapInvalidCredentials) + { + throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.LDAP_CREDENTIALS_INVALID); + } + catch (LdapException) + { + // 只有 result code 49 才能确定是账号或密码错误。 + throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.LDAP_BIND_FAILED); + } + catch (Exception) + { + // Bind 阶段连接中断、服务端策略异常等,不能归类为密码错误。 + throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.LDAP_BIND_FAILED); + } + } + + static async Task EnsureTcpConnectionAsync(LdapDetailDto ldapDetailDto) + { + using var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(ConnectionTimeoutMs)); + using var tcpClient = new TcpClient(); + + try + { + await tcpClient.ConnectAsync( + ldapDetailDto.ServerAddress, + ldapDetailDto.ServerPort, + timeout.Token); + } + catch (Exception ex) when (ex is SocketException or OperationCanceledException) + { + throw new UserFriendlyException( + UserFriendlyExceptionCodes.LDAP_SERVER_UNREACHABLE, + ldapDetailDto.ServerAddress, + ldapDetailDto.ServerPort.ToString()); + } + } + + static UserFriendlyException CreateProtocolException(LdapDetailDto ldapDetailDto) + { + var mode = ldapDetailDto.IsLdaps ? "LDAPS" : "LDAP"; + return new UserFriendlyException( + UserFriendlyExceptionCodes.LDAP_SSL_MISMATCH, + mode, + ldapDetailDto.ServerAddress, + ldapDetailDto.ServerPort.ToString()); + } +} diff --git a/src/Services/Masa.Auth.Service.Admin/_Imports.cs b/src/Services/Masa.Auth.Service.Admin/_Imports.cs index 9ab59f57..072b1ac4 100644 --- a/src/Services/Masa.Auth.Service.Admin/_Imports.cs +++ b/src/Services/Masa.Auth.Service.Admin/_Imports.cs @@ -81,6 +81,7 @@ global using Masa.Auth.Service.Admin.Infrastructure.CacheModels; global using Masa.Auth.Service.Admin.Infrastructure.Email; global using Masa.Auth.Service.Admin.Infrastructure.Extensions; +global using Masa.Auth.Service.Admin.Infrastructure.Ldap; global using Masa.Auth.Service.Admin.Infrastructure.Message; global using Masa.Auth.Service.Admin.Infrastructure.Middleware; global using Masa.Auth.Service.Admin.Infrastructure.Models;