AcceptSecurityContext завершается ошибкой, когда приложение работает как служба
У меня есть простой HTTP-сервер, который аутентифицирует клиентов по протоколу Negotiate. Он использует вызовы SSPI для получения учетных данных сервера и установления контекста безопасности. Сервер находится в домене и работает от имени пользователя домена. Все работает нормально, и я получаю ответ HTTP 200, если я запускаю сервер в режиме консоли. Однако, когда я запускаю его как сервис, я получаю ошибку SEC_E_INVALID_HANDLE. Вот что происходит, когда я запускаю его в режиме консоли:
1.Клиент отправляет HTTP-запрос Get http://localhost:8082/
2. Сервер отвечает WWW-Authenticate: согласовать заголовок.
3.Клиент отправляет заголовок авторизации и включает в себя следующие данные:
60 73 06 06 2B 06 01 05 05 02 A0 69 30 67 A0 30 `s..+..... i0g 0
30 2E 06 0A 2B 06 01 04 01 82 37 02 02 0A 06 09 0...+....7.....
2A 86 48 82 F7 12 01 02 02 06 09 2A 86 48 86 F7 *H÷......*H÷
12 01 02 02 06 0A 2B 06 01 04 01 82 37 02 02 1E ......+....7...
A2 33 04 31 4E 54 4C 4D 53 53 50 00 01 00 00 00 ¢3.1NTLMSSP.....
97 B2 08 E2 04 00 04 00 2D 00 00 00 05 00 05 00 ².â....-.......
28 00 00 00 06 01 B1 1D 00 00 00 0F 50 41 43 45 (.....±.....PACE
4D 42 4C 41 48 MBLAH
4. Сервер отвечает с ошибкой HTTP 401 и согласовывает заголовок, предлагая продолжить:
A1 81 CE 30 81 CB A0 03 0A 01 01 A1 0C 06 0A 2B ¡Î0Ë ....¡...+
06 01 04 01 82 37 02 02 0A A2 81 B5 04 81 B2 4E ....7...¢µ.²N
54 4C 4D 53 53 50 00 02 00 00 00 08 00 08 00 38 TLMSSP.........8
00 00 00 15 C2 89 E2 B0 3B BE 20 45 33 FD 92 80 ....Ââ°;¾ E3ý
04 E7 01 00 00 00 00 72 00 72 00 40 00 00 00 06 .ç.....r.r.@....
01 B1 1D 00 00 00 0F 42 00 4C 00 41 00 48 00 02 .±.....B.L.A.H..
00 08 00 42 00 4C 00 41 00 48 00 01 00 0A 00 50 ...B.L.A.H.....P
00 41 00 43 00 45 00 4D 00 04 00 10 00 62 00 6C .A.C.E.M.....b.l
00 61 00 68 00 2E 00 63 00 6F 00 6D 00 03 00 1C .a.h...c.o.m....
00 50 00 61 00 63 00 65 00 6D 00 2E 00 62 00 6C .P.a.c.e.m...b.l
00 61 00 68 00 2E 00 63 00 6F 00 6D 00 05 00 10 .a.h...c.o.m....
00 62 00 6C 00 61 00 68 00 2E 00 63 00 6F 00 6D .b.l.a.h...c.o.m
00 07 00 08 00 5D B3 C5 9A 0F 17 D1 01 00 00 00 .....]³Å..Ñ....
00 .
5. Клиент отправляет заголовок авторизации:
A1 77 30 75 A0 03 0A 01 01 A2 5A 04 58 4E 54 4C ¡w0u ....¢Z.XNTL
4D 53 53 50 00 03 00 00 00 00 00 00 00 58 00 00 MSSP.........X..
00 00 00 00 00 58 00 00 00 00 00 00 00 58 00 00 .....X.......X..
00 00 00 00 00 58 00 00 00 00 00 00 00 58 00 00 .....X.......X..
00 00 00 00 00 58 00 00 00 15 C2 88 E2 06 01 B1 .....X....Ââ..±
1D 00 00 00 0F C0 BD 0C 5B F5 F9 35 FE 78 6D 08 .....À½.[õù5þxm.
BF 7B D9 CC E3 A3 12 04 10 01 00 00 00 F5 17 A7 ¿{ÙÌã£.......õ.§
50 2D 22 9A 84 00 00 00 00 P-"....
6. Сервер отвечает HTTP 200 и заголовок согласования:
A1 1B 30 19 A0 03 0A 01 00 A3 12 04 10 01 00 00 ¡.0. ....£......
00 43 87 E0 88 C1 36 E3 A9 00 00 00 00 .CàÁ6ã©....
Теперь, если я запускаю приложение как службу, я получу почти идентичные ответы, но AcceptSecurityContext завершится с ошибкой на шаге 6 и вернет ошибку SEC_E_INVALID_HANDLE. Интересно, почему бы это не получилось, если бы я запустил одно и то же приложение и указал того же пользователя в качестве идентификатора входа службы? Может ли это быть как-то связано с изоляцией сеанса 0? Также есть способ решить проблему лучше, я не вижу никаких сообщений об ошибках в средстве просмотра событий, и ошибка недействительного дескриптора мало что говорит о том, что отсутствует.
Вот код сервера для аутентификации:
public static WinAuthResult Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)
{
if (clientTokenBytes == null || clientTokenBytes.Length == 0)
{
ClearContext(clientId);
throw new Win32Exception(Secur32.SEC_E_INVALID_TOKEN);
}
var serverCredExpiry = new Secur32.SECURITY_INTEGER();
var serverCredHandle = new Secur32.SecHandle();
var acquireResult = Secur32.AcquireCredentialsHandle(null, securityPackage, Secur32.SECPKG_CRED_INBOUND, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, out serverCredHandle, out serverCredExpiry);
if (acquireResult != Secur32.SEC_E_OK)
throw new Win32Exception(acquireResult);
var oldContextExists = contexts.ContainsKey(clientId);
var oldContextHandle = GetContextHandle(clientId);
var newContextHandle = new Secur32.SecHandle();
var clientToken = new Secur32.SecBufferDesc(clientTokenBytes);
var outputToken = new Secur32.SecBufferDesc(61440);
var contextAttributes = (uint)0;
var outputCresExpiry = new Secur32.SECURITY_INTEGER();
int acceptResult;
if (!oldContextExists)
{
acceptResult = Secur32.AcceptSecurityContext(
ref serverCredHandle,
IntPtr.Zero,
ref clientToken,
0,
Secur32.SECURITY_NATIVE_DREP,
ref newContextHandle,
ref outputToken,
out contextAttributes,
out outputCresExpiry);
}
else
{
acceptResult = Secur32.AcceptSecurityContext(
ref serverCredHandle,
ref oldContextHandle,
ref clientToken,
0,
Secur32.SECURITY_NATIVE_DREP,
ref newContextHandle,
ref outputToken,
out contextAttributes,
out outputCresExpiry);
}
if (acceptResult == Secur32.SEC_E_OK)
{
ClearContext(clientId);
return new WinAuthResult(false, outputToken.GetSecBufferByteArray());
}
else if (acceptResult == Secur32.SEC_I_CONTINUE_NEEDED)
{
contexts[clientId] = newContextHandle;
return new WinAuthResult(true, outputToken.GetSecBufferByteArray());
}
else
{
ClearContext(clientId);
throw new Win32Exception(acceptResult);
}
}
В обоих случаях я пытаюсь получить доступ к веб-странице с того же компьютера, на котором работает сервер, и с одним и тем же пользователем домена. Также я использую одного и того же пользователя домена для запуска консольного приложения и службы Windows. Эта проблема не воспроизводится в Windows Server 2003, что заставляет меня думать, что это связано с новыми функциями безопасности.
1 ответ
Прошло много времени, но я думаю, что видел подобные проблемы, когда для параметра " Загрузить профиль пользователя" было установлено значение " false" в расширенных настройках пула приложений. Этот параметр является новым для IIS 7.0. В документации сказано, что ложное значение соответствует поведению Windows Server 2003, но я напоминаю, что отсутствие загрузки профиля как-то мешает подсистеме SSPI. И вы правы, там очень мало сообщений об ошибках, мне пришлось прыгнуть через несколько обручей, чтобы дразнить ответ.
ОБНОВИТЬ
В конечном итоге эти функции зависят от реализации клиента Kerberos, большая часть которой находится в процессе lsass.exe. Вот хорошая ссылка для устранения неполадок всей подсистемы: http://blogs.msdn.com/b/canberrapfe/archive/2012/01/02/kerberos-troubleshooting.aspx устранение неполадок.aspx
Кроме того, я помню, что однажды у клиента возникли проблемы с аутентификацией, которые мы в конечном итоге проследили до некоторого несоответствия протокола между клиентом, работающим на Server 2008 (или что-то в этом роде, важным фактом является то, что версия была выше, чем 2003), подключенным к вторичному контроллеру домена. работает на Server 2003. Больше не отслеживал, клиент только что обновил DC.
ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ
Хорошо, я смог воспроизвести проблему и фактически смог заставить ее работать. Ваш Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)
метод вызывается как минимум дважды из-за первого вызова AcceptSecurityContext
возврате SEC_I_CONTINUE_NEEDED
, Каждый раз Authenticate
получает новый дескриптор учетных данных, вызывая AcquireCredentialsHandle
функция. Это работало для меня в консоли и внутри службы, работающей как LocalSystem, но не в том случае, если служба работала под учетной записью домена, как вы сказали.
Итак, я вытащил AcquireCredentialsHandle
вызвать из Authenticate
чтобы я мог получить его один раз, а затем повторно использовать для последующих входящих звонков. Это исправило сервис для меня.
В связанной заметке вы должны освободить дескриптор учетных данных, используя FreeCredentialsHandle
вызовите, иначе вы можете получить утечку памяти в lsass.exe, которая потребует перезагрузки сервера. См. Раздел " Примечания " в описании MSDN AcquireCredentialsHandle