Перейти к содержимому

AD, Kerberos и ADFS

Как устроен корпоративный вход в Windows-сети: чем Kerberos отличается от NTLM, как ASP.NET Core узнаёт пользователя и его группы AD, что делает ADFS и почему вызов от имени пользователя ломается на втором сервере. Эти вопросы задают везде, где приложения живут во внутренней сети с Active Directory.

Перед этой темой: OAuth 2.0 и OpenID Connect.

Junior+

Что такое Windows-аутентификация: чем NTLM отличается от Kerberos и что такое Negotiate?

Заголовок раздела «Что такое Windows-аутентификация: чем NTLM отличается от Kerberos и что такое Negotiate?»
Ответ

Что это

Windows-аутентификация — браузер входит в приложение под той учётной записью, с которой пользователь вошёл в Windows. Учётки хранит Active Directory (AD), а пароли проверяет контроллер домена (DC).

Чем отличаются протоколы

Признак NTLM Kerberos
Как доказывает отвечает на вызов сервера предъявляет билет от DC
Кто проверяет DC при каждом входе сервер сам
Проверка сервера нет есть

Что такое Negotiate

Схема HTTP, которая пробует Kerberos, а при неудаче берёт NTLM. Сервер отвечает 401 с заголовком WWW-Authenticate: Negotiate, браузер повторяет запрос с токеном в Authorization.

Частая ошибка

Открывать сайт по IP-адресу. Kerberos требует DNS-имя, поэтому вход молча пойдёт через NTLM.

Как устроено

NTLM — три сообщения в двух кругах 401:

  1. Клиент сообщает, что хочет NTLM.
  2. Сервер присылает вызов — случайное число.
  3. Клиент возвращает вызов, обработанный ключом из хеша пароля. Сервер пароля не знает и пересылает ответ на DC.

В Kerberos клиент заранее получил у DC билет к этой службе. Билет зашифрован ключом службы, поэтому сервер проверяет его сам, без DC.

Когда что сработает

Ситуация Протокол
Доменный клиент, DNS-имя службы зарегистрировано в AD Kerberos
IP-адрес, имя не зарегистрировано, клиент вне домена NTLM
Нужен вызов дальше от имени пользователя только Kerberos

Ловушки

  • Тихий откат. Negotiate не сообщает, что Kerberos не получился. Протокол видно по токену в Authorization: Negotiate: NTLM в base64 начинается с TlRMTVNTUA, Kerberos — обычно с YII.
  • Привязка к соединению. Вход живёт на TCP-соединении. Прокси без закрепления соединений ломает аутентификацию.
  • HTTP/2. Windows-аутентификация поверх HTTP/2 не работает: для рукопожатия клиент переходит на HTTP/1.1.

Правило

Цель — Kerberos для всех доменных клиентов, а каждый вход через NTLM — повод искать причину.

Под капотом

  • Negotiate — механизм SPNEGO (RFC 4178), перенесённый в HTTP (RFC 4559).
  • Ответ NTLMv2 — HMAC-MD5 на ключе из NT-хеша пароля. Хеш равносилен паролю: его кража даёт pass-the-hash.
  • NTLM не проверяет сервер, поэтому ответ можно переслать на другой сервер (NTLM relay). Защита для HTTPS — Extended Protection: привязка входа к TLS-каналу.

Цена

  • Каждый вход NTLM — запрос сервера к DC через Netlogon. На тысячах пользователей это заметная нагрузка на DC.
  • NTLM — два круга 401 против одного у Kerberos.

Статус NTLM (октябрь 2026)

  1. Июнь 2024 — все версии NTLM объявлены deprecated.
  2. Windows 11 24H2 и Windows Server 2025 — NTLMv1 удалён.
  3. Январь 2026 — план Microsoft из трёх фаз:
    • аудит NTLM, уже доступен;
    • во второй половине 2026 года — IAKerb и локальный центр выдачи билетов Kerberos для сценариев, которые сейчас откатываются на NTLM;
    • в следующей версии Windows Server и клиентской Windows сетевой NTLM выключен по умолчанию, вернуть его можно только политикой.

Как проверить в проде

  • Событие 4624 в журнале Security сервера: поле Authentication Package — Kerberos или NTLM.
  • Аудит NTLM — политики «Network security: Restrict NTLM» в режиме аудита.
  • Частые причины NTLM: IP-адрес или имя без SPN, клиент вне домена, сайт не в зоне «Местная интрасеть» браузера.

Итог: NTLM — запасной протокол без проверки сервера и делегирования, который Microsoft выключает; задача сеньора — найти и устранить каждый откат на него.

Пример
Microsoft.AspNetCore.Authentication.Negotiate
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Negotiate;
var builder = WebApplication.CreateBuilder(args);
// Negotiate: сначала Kerberos, при неудаче — NTLM
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
// Без [AllowAnonymous] каждый эндпоинт требует входа
builder.Services.AddAuthorization(options =>
options.FallbackPolicy = options.DefaultPolicy);
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/whoami", (ClaimsPrincipal user) => user.Identity?.Name); // CONTOSO\alice
app.Run();

На IIS тот же код работает поверх Windows Authentication самого IIS: обработчик Negotiate видит, что сервер умеет её сам, и передаёт работу ему. В IIS включают Windows Authentication и выключают анонимный доступ.

Middle+

Как работает Kerberos и как включить его в ASP.NET Core?

Заголовок раздела «Как работает Kerberos и как включить его в ASP.NET Core?»
Ответ

Что это

Kerberos — протокол, в котором пароль проверяется один раз, а дальше пользователь предъявляет службам билеты. Билеты выдаёт KDC — служба на каждом контроллере домена.

Как это выглядит

  1. Пользователь входит в Windows и получает TGT — «билет на получение билетов».
  2. Открывает портал — Windows по TGT получает у KDC билет именно к этому порталу.
  3. Браузер отправляет билет в заголовке Authorization: Negotiate ....
  4. Портал проверяет билет своим ключом и узнаёт пользователя.

Как пользоваться

  • Посмотреть свои билеты — команда klist.
  • Портал открывают по DNS-имени, для которого администратор зарегистрировал SPN, например HTTP/portal.contoso.com.

Частая ошибка

Не следить за временем сервера. Если часы расходятся с контроллером домена больше чем на 5 минут, Kerberos отклоняет билеты.

Как устроено

  1. AS-REQ и AS-REP. Клиент шлёт KDC метку времени, зашифрованную ключом из пароля. KDC отвечает TGT, зашифрованным ключом учётки krbtgt, и сеансовым ключом.
  2. TGS-REQ и TGS-REP. Клиент предъявляет TGT и SPN службы. KDC находит учётку с этим SPN и выдаёт билет, зашифрованный её ключом.
  3. AP-REQ. Клиент передаёт серверу билет и свежую метку времени на сеансовом ключе. Сервер расшифровывает билет своим ключом и сверяет время.
  4. AP-REP. По запросу клиента сервер доказывает, что тоже знает ключ, — это взаимная аутентификация.

Ловушки

  • SPN не на той учётке. Приложение под другой учёткой не расшифрует билет: KRB_AP_ERR_MODIFIED и откат на NTLM.
  • Дубликат SPN. У SPN два владельца — KDC не выдаст билет.
  • Часы. Допуск по умолчанию — 5 минут, иначе KRB_AP_ERR_SKEW.
  • CNAME. Браузер запрашивает билет на каноническое имя из DNS, поэтому для алиаса SPN нужен на имени A-записи.

Кто проверяет билет в ASP.NET Core

Хостинг Чьим ключом
IIS, Windows Authentication учётки компьютера; учётки пула — с useAppPoolCredentials
Kestrel на Windows учётки, под которой работает процесс
Kestrel на Linux из keytab, путь в KRB5_KTNAME

Под капотом

  • Сервер не ходит к KDC: ключ службы знают только они двое. Поэтому кража ключа службы — keytab или пароля сервисной учётки — позволяет подделать билеты к ней (silver ticket), а ключа krbtgt — любые TGT (golden ticket).
  • PAC перечисляет группы пользователя, включая вложенные, и сервер строит из него Windows-токен без запроса к AD. Новое членство появится только с новым TGT, а он по умолчанию живёт 10 часов.
  • Любой пользователь домена может запросить билет к любому SPN и подбирать пароль сервисной учётки офлайн (Kerberoasting). Защита — длинные случайные пароли и AES.
  • RC4 в Kerberos объявлен deprecated в Windows Server 2025. С обновлений 2026 года (CVE-2026-20833) контроллеры домена считают, что учётка без явного msDS-SupportedEncryptionTypes поддерживает только AES.

Компромиссы

  • Kestrel на Linux. Групп из PAC в принципале нет — их читают из AD по LDAP (EnableLdap): лишние запросы и кеш.
  • Ферма за балансировщиком. Общее имя — один SPN на доменной учётке, под которой работают все узлы. SPN на учётке одного сервера сломает вход на остальных.
  • Keytab. Это ключ в файле. При смене пароля учётки меняется номер версии ключа (kvno), и старый keytab перестаёт подходить.

Как проверить в проде

  • klist get HTTP/portal.contoso.com с клиента: выдаётся ли билет и с каким шифрованием.
  • На DC события 4768 (TGT) и 4769 (сервисный билет) с кодом ошибки: 0x7 — SPN не найден.
  • Время — w32tm /query /status на сервере и DC.

Итог: Kerberos держится на правильном SPN, общем ключе и синхронном времени; почти любая авария в проде — одно из трёх.

Пример
Microsoft.AspNetCore.Authentication.Negotiate
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Negotiate;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate(options =>
{
// На Linux ключ службы берётся из keytab (переменная KRB5_KTNAME),
// а групп в принципале нет: читаем их из AD по LDAP
if (OperatingSystem.IsLinux())
options.EnableLdap("contoso.com");
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (ClaimsPrincipal user) => $"Hello, {user.Identity?.Name}")
.RequireAuthorization();
app.Run();
Регистрация SPN и диагностика билетов
Окно терминала
# SPN на учётке, под которой работает приложение; -S сначала ищет дубликаты
setspn -S HTTP/portal.contoso.com CONTOSO\svc-portal
# Все SPN учётки и поиск дубликатов по всему лесу
setspn -L CONTOSO\svc-portal
setspn -X -F
# Запросить сервисный билет и посмотреть кеш билетов
klist get HTTP/portal.contoso.com
klist
# Сбросить билеты, чтобы получить новые, например после добавления в группу
klist purge
Middle+
Ответ

Что это

ADFS — роль Windows Server для единого входа (SSO) через браузер. Приложение не проверяет пароли: оно перенаправляет пользователя на ADFS и получает от него токен.

Как это выглядит

  1. Пользователь открывает приложение, оно перенаправляет его на ADFS.
  2. ADFS проверяет пользователя: внутри сети — Windows-аутентификацией, снаружи — формой входа.
  3. ADFS выдаёт подписанный токен с claims: имя, почта, группы.
  4. Приложение проверяет подпись и создаёт сессию в cookie.

Зачем

  • Приложению не нужен доступ к AD, и ему не обязательно быть в домене.
  • Пользователь входит один раз во все подключённые приложения.

Частая ошибка

Путать ADFS с Active Directory. AD хранит учётки, а ADFS выдаёт на их основе токены.

Как устроено

  • Claims provider trust — откуда ADFS берёт пользователей. По умолчанию это AD, но может быть и ADFS партнёра — так строится федерация между организациями.
  • Relying party trust — приложение, которому ADFS выдаёт токены: идентификатор, адрес возврата, протокол.
  • Claim rules — что положить в токен: атрибуты LDAP, группы, преобразования. Правило пишется как условие, => и действие issue.

Когда что выбирать

Протокол Где уместен
WS-Federation старые приложения .NET на WIF, SharePoint
SAML 2.0 готовые продукты и SaaS
OpenID Connect (ADFS 2016+) новые веб-приложения и API

Ловушки

  • Смена сертификата подписи. ADFS сам обновляет сертификат подписи токенов. Приложение, которое читает метаданные (MetadataAddress), подхватит новый ключ, а с захардкоженным отпечатком — сломается.
  • SAML в ASP.NET Core. Встроенного обработчика SAML 2.0 нет, нужна сторонняя библиотека. WS-Federation и OIDC есть в виде пакетов Microsoft.
  • Шифрование токенов. Microsoft.AspNetCore.Authentication.WsFederation его не поддерживает, поэтому в relying party trust сертификат шифрования не задают.

Правило

Новое приложение — OIDC; WS-Federation — для совместимости со старыми.

Под капотом

  • ADFS — ферма серверов с общей конфигурацией в Windows Internal Database или SQL Server. Из интернета её публикует Web Application Proxy в DMZ.
  • Движок claims выполняет правила по порядку: add кладёт claim только во входной набор для следующих правил, issue — ещё и в токен.
  • Группы обычно выдаёт правило, которое запрашивает у хранилища Active Directory атрибут tokenGroups: это все группы пользователя, включая вложенные.

Статус ADFS (октябрь 2026)

  • ADFS есть в Windows Server 2025 и поддерживается, но Microsoft прямо советует вместо обновления переходить на Entra ID.
  • Windows Internal Database, база конфигурации ADFS по умолчанию, в Windows Server 2025 объявлена deprecated.

Путь миграции на Entra ID

  1. Entra Connect синхронизирует пользователей и группы, чтобы Entra выдавала группы в формате AD, например sAMAccountName.
  2. Отчёт AD FS application migration (нужны агенты Entra Connect Health) показывает активные приложения и правила, которые не переносятся как есть.
  3. Приложения переключают по одному: тестовый экземпляр, затем прод. Мастер переносит SAML-приложения, WS-Federation и OIDC настраивают вручную.
  4. Домен переводят с федерации на Password Hash Sync или Pass-through Authentication и выводят ADFS из эксплуатации.

Внутренние приложения с Windows-аутентификацией публикуют через Microsoft Entra application proxy: его коннектор сам получает для пользователя билет Kerberos к приложению.

Как проверить в проде

Ошибки выдачи токенов — журнал AD FS/Admin на серверах фермы; состав токена — служебная страница приложения со списком claims.

Итог: ADFS работает и поддерживается, но это тупиковая ветка: сопровождая его, планируют перенос claims и групп в Entra ID.

Пример
Microsoft.AspNetCore.Authentication.WsFederation
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.WsFederation;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
// Сессия — в cookie, вход — перенаправлением на ADFS
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddWsFederation(options =>
{
// Метаданные: адрес входа и сертификат подписи токенов
options.MetadataAddress =
"https://adfs.contoso.com/FederationMetadata/2007-06/FederationMetadata.xml";
// Идентификатор relying party trust в ADFS
options.Wtrealm = "https://portal.contoso.com/";
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Claims, которые выдали правила ADFS
app.MapGet("/claims", (ClaimsPrincipal user) => user.Claims.Select(c => $"{c.Type}: {c.Value}"))
.RequireAuthorization();
app.Run();

Для OIDC вместо AddWsFederation берут AddOpenIdConnect с Authority = "https://adfs.contoso.com/adfs"; сам протокол разобран в теме про OAuth 2.0 и OpenID Connect.

Правило claims: группы AD как роли
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname",
Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory",
types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"),
query = ";tokenGroups;{0}",
param = c.Value);

Правило берёт имя Windows-учётки пользователя, запрашивает у AD все его группы, включая вложенные, и выдаёт каждую как claim роли.

Middle+

Как использовать группы AD для авторизации в ASP.NET Core?

Заголовок раздела «Как использовать группы AD для авторизации в ASP.NET Core?»
Ответ

Что это

Группа AD — набор учёток, которому администратор выдаёт права. Приложение проверяет не имя пользователя, а членство в группе: новый сотрудник получает доступ, когда его добавят в группу.

Как пользоваться

  1. Включите Windows-аутентификацию (AddNegotiate()) или вход через ADFS.
  2. Опишите политику: options.AddPolicy("Admins", p => p.RequireRole(@"CONTOSO\Portal-Admins")).
  3. Повесьте её на эндпоинт: .RequireAuthorization("Admins").

Как это работает

При Windows-аутентификации User.IsInRole(@"CONTOSO\Portal-Admins") переводит имя группы в SID — постоянный идентификатор — и ищет его в Windows-токене пользователя. Вложенные группы там уже учтены.

Частая ошибка

Ждать доступа сразу после добавления в группу. Группы попадают в билет при входе в Windows, поэтому пользователю нужно перелогиниться.

Как устроено

Вход Как приходят группы
Windows: Negotiate, IIS SID в claims ClaimTypes.GroupSid, включая вложенные
ADFS claims по правилу, обычно tokenGroups, тоже с вложенными
Entra ID ID объектов или sAMAccountName синхронизированных групп

IClaimsTransformation

Сервис, который ASP.NET Core вызывает после аутентификации, чтобы дополнить ClaimsPrincipal. В нём группы переводят в роли приложения: SID группы → approver. Бизнес-код знает только роли, а соответствие групп ролям лежит в одном месте.

Ловушки

  • Повторные вызовы. TransformAsync вызывается на каждый AuthenticateAsync, иногда несколько раз за запрос. Проверяйте, не добавлены ли роли уже.
  • IIS без схемы. Если полагаться только на автоматический вход IIS, AuthenticateAsync не вызывается и трансформация молчит. Нужен AddAuthentication со схемой: AddNegotiate() или IISServerDefaults.AuthenticationScheme.
  • Имя вместо SID. Группу переименовали — проверка по имени сломалась, а SID остался прежним.
  • Свежесть. Права обновляются с новым билетом или токеном, а кеш добавляет свою задержку.

Правило

Группы — на входе, роли приложения — в политиках, соответствие — в IClaimsTransformation.

Под капотом

  • WindowsPrincipal.IsInRole(string) на каждый вызов переводит имя в SID через LSA и только потом смотрит обычные claims. Проверка по SID или по своим claims ролей этого запроса не делает.
  • Kerberos-билет ограничен MaxTokenSize — 48 000 байт по умолчанию. В HTTP он едет в base64, то есть на треть длиннее, и упирается в лимиты заголовков HTTP.sys (MaxFieldLength, MaxRequestBytes) и Kestrel (MaxRequestHeadersTotalSize, 32 КБ). Симптом — 400 Request Header too long.
  • Entra ID кладёт не больше 200 групп в JWT и 150 в SAML. Сверх лимита claim groups пропадает, вместо него приходит _claim_names со ссылкой на Graph.

Компромиссы

Решение Плюс Минус
Группы в токене без запросов к AD размер токена, устаревание
LDAP при входе свежие данные нагрузка на DC, нужен кеш
Группы, назначенные приложению маленький токен без вложенных групп

Linux

На Kestrel под Linux EnableLdap читает memberOf через System.DirectoryServices.Protocols (нужен libldap) и кеширует результат: 10 минут скользящий срок, 60 минут абсолютный. memberOf не содержит основную группу, обычно Domain Users. System.DirectoryServices и AccountManagement работают только на Windows.

Как проверить в проде

Служебная страница для администраторов показывает claims текущего пользователя. О больших билетах KDC предупреждает событием 31, если включена политика «Warning for large Kerberos tickets».

Итог: группы — это размер токена против свежести прав; в больших доменах в токен кладут только нужное приложению, остальное читают по LDAP с кешем.

Пример
Microsoft.AspNetCore.Authentication.Negotiate
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Negotiate;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
builder.Services.AddTransient<IClaimsTransformation, AdGroupsToAppRoles>();
// RequireClaim, а не RequireRole: WindowsPrincipal.IsInRole сначала ищет имя через LSA
builder.Services.AddAuthorization(options =>
options.AddPolicy("Approvers", p => p.RequireClaim("app_role", "approver")));
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapPost("/requests/{id:int}/approve", (int id) => $"Request {id} approved")
.RequireAuthorization("Approvers");
app.Run();
public sealed class AdGroupsToAppRoles : IClaimsTransformation
{
// SID группы → роль приложения: SID не меняется при переименовании группы
static readonly Dictionary<string, string> RolesBySid = new()
{
["S-1-5-21-1004336348-1177238915-682003330-1105"] = "approver",
};
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Вызывается на каждый AuthenticateAsync: роли добавляем один раз
if (principal.Identities.Any(i => i.Label == "app-roles"))
return Task.FromResult(principal);
var roles = principal.FindAll(ClaimTypes.GroupSid)
.Where(c => RolesBySid.ContainsKey(c.Value))
.Select(c => new Claim("app_role", RolesBySid[c.Value]));
principal.AddIdentity(new ClaimsIdentity(roles) { Label = "app-roles" });
return Task.FromResult(principal);
}
}
Senior+

Что такое проблема double hop и как работает делегирование Kerberos?

Заголовок раздела «Что такое проблема double hop и как работает делегирование Kerberos?»
Ответ

Что это

Первый прыжок — браузер → веб-приложение, второй — приложение → база или файловый сервер. На втором прыжке приложение не может представиться пользователем.

Почему так

Браузер отдал приложению билет Kerberos только к этому приложению. Пароля и TGT пользователя у него нет, поэтому билет к базе от имени пользователя не получить. База видит NT AUTHORITY\ANONYMOUS LOGON или отказывает в доступе.

Как решают

  • Проще всего ходить ко второму серверу от учётки приложения, а права пользователя проверять в самом приложении.
  • Если второй сервер должен видеть именно пользователя, администратор настраивает делегирование Kerberos.

Частая ошибка

Проверять в браузере на самом веб-сервере: там прыжок один и всё работает, а с рабочих мест — нет.

Как устроено

  1. Пользователь входит по Kerberos во фронтенд, тот получает его Windows-идентичность.
  2. Фронтенд имперсонирует пользователя (WindowsIdentity.RunImpersonatedAsync) и обращается к бэкенду.
  3. Windows просит у KDC билет к бэкенду от имени пользователя. KDC выдаёт его, только если учётке фронтенда разрешено делегирование к SPN бэкенда.

Когда что выбирать

Вид Где настраивается Что может фронтенд
Неограниченное на учётке фронтенда с TGT пользователя — что угодно
KCD на учётке фронтенда, список SPN только перечисленные службы
RBCD на учётке бэкенда, кому можно то, что разрешил владелец бэкенда

Ловушки

  • Protocol transition. Режим KCD «Use any authentication protocol» (S4U2Self) выдаёт билет даже за пользователя, вошедшего без Kerberos — через NTLM или форму. Удобно, но тогда фронтенд может выдать себя за любого пользователя перед этими службами.
  • Пул IIS. Делегирование настраивают на учётке, которая владеет SPN фронтенда: учётке компьютера или доменной учётке пула.
  • Linux. Имперсонация в ASP.NET Core есть только на Windows.

Правило

Сначала выяснить, нужен ли бэкенду именно пользователь. Если да — RBCD или KCD «Kerberos only» к конкретным SPN.

Под капотом

  • Неограниченное. Клиент пересылает серверу свой TGT, и тот лежит в памяти LSASS. Захват сервера даёт TGT всех, кто к нему заходил. Credential Guard, включённый по умолчанию на подходящих Windows 11 22H2+ и Windows Server 2025, такое делегирование блокирует.
  • KCD — S4U2Proxy. Фронтенд предъявляет KDC билет пользователя к себе и получает билет к бэкенду. KDC сверяет SPN со списком msDS-AllowedToDelegateTo фронтенда.
  • RBCD. KDC проверяет атрибут msDS-AllowedToActOnBehalfOfOtherIdentity на учётке бэкенда; в PowerShell это параметр PrincipalsAllowedToDelegateToAccount.
  • Не делегируются члены Protected Users и учётки с флагом «Account is sensitive and cannot be delegated».

Контейнеры и Linux

  • Windows-контейнер в домен не вводят: доменную идентичность ему даёт gMSA через credential spec.
  • В Linux-контейнер монтируют keytab и krb5.conf, путь к keytab задаёт KRB5_KTNAME. Имперсонации там нет, поэтому бэкенд вызывают от учётки сервиса или с OAuth-токеном.

Как проверить в проде

  1. setspn -Q и setspn -X -F — у каждого SPN ровно один владелец.
  2. События 4769 на DC: поле Transited Services показывает делегирование, Failure Code — причину отказа.
  3. Дубликат SPN KDC отмечает в журнале System событием 11.

Итог: делегирование — доверие KDC к фронтенду; выбирают самый узкий вид, а ломается оно чаще в каталоге, чем в коде.

Пример
Microsoft.AspNetCore.Authentication.Negotiate
using System.Security.Principal;
using Microsoft.AspNetCore.Authentication.Negotiate;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
builder.Services.AddAuthorization(options => options.FallbackPolicy = options.DefaultPolicy);
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/report", async (HttpContext context) =>
{
// Только Windows: на Linux имперсонации нет
var user = (WindowsIdentity)context.User.Identity!;
// Второй прыжок: файловый сервер увидит пользователя, только если учётке
// приложения разрешено делегирование к cifs/files01.contoso.com
return await WindowsIdentity.RunImpersonatedAsync(user.AccessToken,
() => File.ReadAllTextAsync(@"\\files01.contoso.com\reports\daily.txt"));
});
app.Run();

Без делегирования файловый сервер получает анонимный вход и отвечает отказом в доступе, хотя у самого пользователя права на файл есть.

RBCD и диагностика
Окно терминала
# У SPN фронтенда ровно один владелец; дубликаты по всему лесу
setspn -Q HTTP/portal.contoso.com
setspn -X -F
# RBCD: файловый сервер разрешает делегирование учётке портала (gMSA)
Set-ADComputer files01 -PrincipalsAllowedToDelegateToAccount (Get-ADServiceAccount gmsa-portal)
Get-ADComputer files01 -Properties PrincipalsAllowedToDelegateToAccount
# Выдаёт ли KDC билет к файловому серверу и с каким шифрованием
klist get cifs/files01.contoso.com