Страницы

Поиск по вопросам

четверг, 9 января 2020 г.

Алгоритм работы Windows аутентификации в веб

#c_sharp #windows #веб_программирование


Как работает в Windows аутентификации в вебе.

Вот я создал Web Api для теста.

Обращаюсь к ValuesController и каким-то магическим образом мои учетные данные попадают
в контроллер.

Как они там оказываются? Их браузер передает? Откуда он знает, что он их должен приложить?
    


Ответы

Ответ 1



Как договорятся браузер и сервер о протоколе во время хендшейка. Очень любопытно ковырять исходники owin или какого-нибудь стороннего веб-сервера попроще (я люблю NancyFX). Вот например, сервер может работать в одном из следующих режимов: Anonymous Basic Digest IntegratedWindowsAuthentication Negotiate None Ntlm Пример кода NancyFX для доменной авторизации: using System.Net; using Owin; namespace NancyWebApp.Nancy.Owin { public class Startup { public void Configuration(IAppBuilder app) { var listener = (HttpListener) app.Properties["System.Net.HttpListener"]; listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication; app.UseNancy(); } } } Вероятнее всего будет Kerberos или NTLM. Да-да, я сам был удивлён, что старик ntlm до сих пор жив. Я как-то разбирался в похожем вопросе - можете посмотреть ссылки на MSDN, где оооочень подробно расписаны все шаги по установлению протокола и формату пересылаемых сообщений. Отдельно надо иметь ввиду, что если вы в браузере рассматриваете заголовки -- будьте готовы к тому, что хром обманывает и показывает не все. Также отдельно нужно понимать, что во многих случаях в случае CORS-запросов перед реальным POST-запросом браузер любит отправлять предварительные заголовки OPTIONS - и это тоже поначалу сбивает с толку при отладке WebAPI. (Возможно, вы уже сталкивались? Не знаю, но упомяну) Очень любопытно моделировать настоящий браузер из HttpClient'а и сравнивать поведение с реальным браузером - как это выглядит всё в fiddler'е (Прямо говоря, установка fiddler'а - это ещё тот танец с бубном когда речь заходит о HTTPS-протоколе, там штуки три подводных камня есть, которые придётся пройти). Вот например для NTLM: public class WebRequestHelper { public WebRequestHelper(string userName, string password, string domain) { var credentials = new NetworkCredential(userName, password, domain); var handler = new HttpClientHandler { Credentials = credentials, UseDefaultCredentials = false }; this.Client = new HttpClient(handler); } public HttpClient Client { get; set; } public async Task GetAsync(string uri) { return await this.Client.GetStringAsync(uri); } public async Task PostFormAsync(string uri, Dictionary data) { var content = new FormUrlEncodedContent(data); var response = await this.Client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task PostAsync(string uri, string jsonString) { var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); var response = await this.Client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } } Готовим WebApi, обстреливаем: var unit = new WebRequestHelper("AK", "password", "domain"); unit.GetAsync("https://localhost:44395/api/values").Result.Dump(); // Expected: ["value1","value2"] А вот - для JWT мой linqpad'овский скрипт: public class WebRequestHelper { public async Task GetAsync(string uri) { var client = new HttpClient(); return await client.GetStringAsync(uri); } public async Task PostFormAsync(string uri, Dictionary data) { var client = new HttpClient(); var content = new FormUrlEncodedContent(data); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task PostAsync(string uri, string jsonString) { var client = new HttpClient(); var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task GetToken(string tokenUrl, string username, string password) { var data = new Dictionary { { "username", username }, { "password", password }, }; var answer = await this.PostFormAsync(tokenUrl, data); if(string.Equals(answer, "Invalid username or password.")) return null; return JsonConvert.DeserializeObject(answer); } public async Task GetWithJwtAsync(string uri, Token token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); return await client.GetStringAsync(uri); } public async Task PostFormWithJwtAsync(string uri, Dictionary data, Token token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("JWT", token.Value); var content = new FormUrlEncodedContent(data); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } public async Task PostWithJwtAsync(string uri, string jsonString, Token token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); var content = new StringContent(jsonString, Encoding.UTF8, "application/json"); var response = await client.PostAsync(uri, content); return await response.Content.ReadAsStringAsync(); } } public class Token { [JsonProperty("access_token")] public string Value { get; set; } [JsonProperty("username")] public string UserName { get; set; } } И очень хорошо подключать swagger к серверной части, чтобы смотреть за тем API, которое вы создаёте.

Комментариев нет:

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