Используйте ASP.NET Core для написания веб-API, позволяющего пользователям изменять свои пароли AD.

После завершения API для проверки пароля AD После,Затем попробуйте написать часть для смены пароля,Несмотря на то, что я споткнулся, я наконец закончил это.。На этот раз я расскажу, как написать три разных пакета.,И в названии будет упомянуто “Измените пароль AD” Это потому, что я обнаружил в процессе написания,Для выполнения некоторых методов требуются разрешения администратора домена.,Поэтому рамки этой статьи будут ограничены использованием только собственной учетной записи и пароля пользователя.,Вы можете выполнить действие по смене пароля。


"Люкс"
Вообще говоря,Для модификации AD могут использоваться три типа пакетов::

  • System.DirectoryServices.Протоколы (С.ДС.П)
  • System.DirectoryServices (С.ДС)
  • System.DirectoryServices.AccountManagement (С.ДС.АМ)

Сравнение трех комплектов:

  • оперативный уровень:S.DS.AM > S.DS > S.DS.P
  • сложность:S.DS.P > S.DS > S.DS.AM
  • Гибкость:S.DS.P > S.DS > S.DS.AM
  • скорость:S.DS.P > S.DS > S.DS.AM

другое,Поскольку S.DS.P — это низкоуровневая операция, основанная на протоколе LDAP.,Таким образом, совместимость также высока,Для OpenLDAP、Поддержка AD не проблема,Но S.DS и S.DS.AM могут быть более ограниченными.,AD для Microsoft (Active Directory)。

Службы/PasswordManagementService.cs

using AD.Models;
using Microsoft.Extensions.Options;
using System.DirectoryServices;
using System.DirectoryServices.Protocols;
using System.Net;
using System.Text;
using System.DirectoryServices.AccountManagement;
using System.Runtime.Versioning;


namespace AD.Services
{
    public class PasswordManagementService(IOptions<LdapSettings> ldapSettings)
    {
        private readonly string _ldapServer = ldapSettings.Value.Server;
        private readonly string _domain = ldapSettings.Value.Domain;
        private readonly string _baseDn = ldapSettings.Value.BaseDn;

        // 修改密碼 (透過 System.DirectoryServices.Protocols)
        public bool ChangePasswordSdsP(string username, string oldPassword, string newPassword)
        {
            try
            {
                using var connection = new LdapConnection(new LdapDirectoryIdentifier(_ldapServer, 636)); // 修改密碼必須使用 LDAPS 636 port。
                connection.Credential = new NetworkCredential(username, oldPassword, _domain);
                connection.SessionOptions.SecureSocketLayer = true; // 修改密碼必須使用 LDAPS。
                connection.AuthType = AuthType.Kerberos; // 如果使用 Negotiate 會先嘗試 Kerberos,失敗再改試 NTLM。
                connection.Bind(); // 嘗試綁定,成功表示驗證通過

                // 以 sAMAccountName 查詢使用者的 DN
                SearchRequest searchRequest = new(
                    _baseDn, // 根目錄
                    $"(sAMAccountName={username})", // 根據 sAMAccountName 查詢
                    System.DirectoryServices.Protocols.SearchScope.Subtree,
                    "distinguishedName" // 只獲取 DN 屬性
                );

                SearchResponse searchResponse = (SearchResponse)connection.SendRequest(searchRequest);

                if (searchResponse.Entries.Count == 0)
                {
                    Console.WriteLine("User not found.");
                    return false;
                }

                string userDn = searchResponse.Entries[0].DistinguishedName;

                // 使用 LDAP 修改密碼屬性 (unicodePwd) 時,
                // 只有高權限帳號 (如 Domain Admins) 可以對 unicodePwd 屬性執行修改 (DirectoryAttributeOperation.Replace)。
                // 若要讓一般使用者更改自己的密碼,必須透過 LDAPS (安全通道),且同時送出 delete 與 add 操作來替換密碼。
                var deleteOldPassword = new DirectoryAttributeModification
                {
                    Operation = DirectoryAttributeOperation.Delete,
                    Name = "unicodePwd"
                };
                deleteOldPassword.Add(Encoding.Unicode.GetBytes($"\"{oldPassword}\""));

                var addNewPassword = new DirectoryAttributeModification
                {
                    Operation = DirectoryAttributeOperation.Add,
                    Name = "unicodePwd"
                };
                addNewPassword.Add(Encoding.Unicode.GetBytes($"\"{newPassword}\""));

                // 組合 ModifyRequest,執行 Delete + Add 操作
                var request = new ModifyRequest(
                    userDn,
                    deleteOldPassword,
                    addNewPassword
                );

                connection.SendRequest(request);
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
                return false;
            }
        }

        // 修改密碼 (透過 System.DirectoryServices)
        [SupportedOSPlatform("windows")] // 宣告以下方法僅適用於 Windows,避免 PrincipalContext 等 API 被提示要留意跨平臺問題。
        public bool ChangePasswordSds(string username, string oldPassword, string newPassword)
        {
            try
            {
                // 使用 DirectorySearcher 以 sAMAccountName 查詢使用者的 DN
                DirectorySearcher searcher = new(new DirectoryEntry($"LDAP://{_baseDn}"));
                searcher.Filter = $"(sAMAccountName={username})";
                searcher.PropertiesToLoad.Add("distinguishedName");

                SearchResult result = searcher.FindOne();

                if (result != null)
                {
                    string userDn = result.Properties["distinguishedName"][0].ToString();

                    using DirectoryEntry user = new($"LDAP://{userDn}", username, oldPassword);
                    user.Invoke("ChangePassword", [oldPassword, newPassword]);
                    user.CommitChanges();
                    return true;
                }
                else
                {
                    Console.WriteLine("User not found.");
                    return false;
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
                return false;
            }
        }

        // 修改密碼 (透過 System.DirectoryServices.AccountManagement)
        [SupportedOSPlatform("windows")] // 宣告以下方法僅適用於 Windows,避免 PrincipalContext 等 API 被提示要留意跨平臺問題。
        public bool ChangePasswordSdsAm(string username, string oldPassword, string newPassword)
        {
            try
            {
                using var context = new PrincipalContext(ContextType.Domain, _domain);

                // 驗證舊密碼是否正確
                if (!context.ValidateCredentials(username, oldPassword))
                {
                    throw new UnauthorizedAccessException("用戶名或舊密碼不正確!");
                }

                // 使用 UserPrincipal 修改密碼
                using var user = UserPrincipal.FindByIdentity(context, username) ?? throw new Exception("找不到指定的使用者!");
                user.ChangePassword(oldPassword, newPassword);
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"修改密碼時發生錯誤: {ex.Message}");
                return false;
            }
        }
    }
}

Контроллеры/PasswordManagementController.cs

using AD.Models;
using AD.Services;
using Microsoft.AspNetCore.Mvc;
using System.Runtime.Versioning;

namespace AD.Controllers
{
    [Route("Password")]
    [ApiController]

    public class PasswordManagementController(PasswordManagementService passwordManagement) : ControllerBase
    {
        // 修改密碼 (透過 System.DirectoryServices.Protocols)
        [HttpPost("ChangeSdsP")]
        public IActionResult ChangeSdsP([FromBody] PasswordChangeRequest request)
        {
            if (passwordManagement.ChangePasswordSdsP(request.Username, request.OldPassword, request.NewPassword))
            {
                return Ok("密碼修改成功。");
            }
            return BadRequest("密碼修改失敗。");
        }

        // 修改密碼 (透過 System.DirectoryServices)
        [HttpPost("ChangeSds")]
        [SupportedOSPlatform("windows")] // 宣告以下用到的方法僅適用於 Windows,避免 ChangePassword 方法被提示要留意跨平臺問題。
        public IActionResult ChangeSds([FromBody] PasswordChangeRequest request)
        {
            if (passwordManagement.ChangePasswordSds(request.Username, request.OldPassword, request.NewPassword))
            {
                return Ok("密碼修改成功。");
            }
            return BadRequest("密碼修改失敗。");
        }

        // 修改密碼 (透過 System.DirectoryServices.AccountManagement)
        [HttpPost("ChangeSdsAm")]
        [SupportedOSPlatform("windows")] // 宣告以下用到的方法僅適用於 Windows,避免 ChangePassword 方法被提示要留意跨平臺問題。
        public IActionResult ChangeSdsAm([FromBody] PasswordChangeRequest request)
        {
            if (passwordManagement.ChangePasswordSdsAm(request.Username, request.OldPassword, request.NewPassword))
            {
                return Ok("密碼修改成功。");
            }
            return BadRequest("密碼修改失敗。");
        }
    }
}

Модели/AuthRequests.cs

namespace AD.Models
{
    public class PasswordChangeRequest
    {
        public string Username { get; set; }        // 使用者帳戶
        public string OldPassword { get; set; }   // 舊密碼
        public string NewPassword { get; set; }   // 新密碼
    }
}

Модели/LdapSettings.cs

namespace AD.Models
{
    public class LdapSettings
    {
        public string Server { get; set; } = string.Empty;
        public string Domain { get; set; } = string.Empty;
        public string BaseDn { get; set; } = string.Empty;
    }
}

appsettings.json

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },

    "AllowedHosts": "*",

    "LdapSettings": {
        "Server": "dc1.abc.com.tw", // 如果是用 Kerberos 驗證,AD 的伺服器不可以使用 IP。
        "Domain": "abc.com.tw",
        "BaseDn": "DC=abc,DC=com,DC=tw"
    }
}

Программа.cs

using AD.Models;
using AD.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.Configure<LdapSettings>(builder.Configuration.GetSection("LdapSettings")); // 讀取 appsettings.json 的 LdapSettings 資料。
builder.Services.AddScoped<PasswordManagementService>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.

// 讓 Swagger 只在開發環境時使用。
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Во время тестирования,Рекомендуется использовать Swagger,Это будет гораздо удобнее。

  最後,Сервер DC должен подтвердить, что он поддерживает LDAPS.,Кроме 636 Чтобы пинговать,Мне также нужно помочь DC установить учетные данные.,Для проверки будет точнее использовать инструмент ldp.exe на контроллере домена.。другое,Не забудьте убедиться, что флажок «Пользователи не могут менять пароли» в тестовой учетной записи не установлен.,Я застрял здесь на несколько дней,Наконец я понял, что забыл снять галочку...。

«Справочная ссылка»

Оставить комментарий

Пожалуйста, обратите внимание: Комментарий умеренности включен и может задержать ваш комментарий. Существует нет необходимости повторно свой комментарий.