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

125
11 ноября 2019, 00:10

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

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

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

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

Answer 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<string> GetAsync(string uri)
    {
        return await this.Client.GetStringAsync(uri);
    }
    public async Task<string> PostFormAsync(string uri, Dictionary<string, string> data)
    {
        var content = new FormUrlEncodedContent(data);
        var response = await this.Client.PostAsync(uri, content);
        return await response.Content.ReadAsStringAsync();
    }
    public async Task<string> 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<string> GetAsync(string uri)
    {
        var client = new HttpClient();
        return await client.GetStringAsync(uri);
    }
    public async Task<string> PostFormAsync(string uri, Dictionary<string, string> 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<string> 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<Token> GetToken(string tokenUrl, string username, string password)
    {
        var data = new Dictionary<string, string> {
            { "username", username },
            { "password", password },
        };
        var answer = await this.PostFormAsync(tokenUrl, data);
        if(string.Equals(answer, "Invalid username or password."))
            return null;
        return JsonConvert.DeserializeObject<Token>(answer);
    }
    public async Task<string> 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<string> PostFormWithJwtAsync(string uri, Dictionary<string, string> 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<string> 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, которое вы создаёте.

READ ALSO
Структура объектов Unity C#

Структура объектов Unity C#

У меня есть игра, в которой есть разный муссорОни обладают полями типа: - тип (как индикатор, который указывает на то какие параметры будут...

108
Создание библиотеки DLL на C++ по примеру C#

Создание библиотеки DLL на C++ по примеру C#

Есть инструкция по написании необходимой библиотеки на C#Она гласит следующее:

113
C++ clr Hosting - ICLRMetaHost GetRuntime уточнение версии до 4.7.2

C++ clr Hosting - ICLRMetaHost GetRuntime уточнение версии до 4.7.2

При использовании конфигурации appexe

97
Лишние папки после сборки

Лишние папки после сборки

После установки CefCharp в папке сборки появились папки "x64", "x86", "GPUCache", "blob_storage"

113