heap.tech
лаборатория велосипедов
×

NTLM авторизация, net.core и JWT-токен

27 октября
В статье я расскажу как подружить net.core версии 2 и выше с доменной NTLM-авторизацей так, чтобы все пользователи (читать как сотрудники организации) имели доступ к приложению и для каждого пользователя формировалась сессия на основе его доменной учетки



Вводная


Выполнять NTLM-авторизацию (т.е. проверку прав пользователя; не путать с аутентификацией, её выполняет контроллер домена предпрития проверяя логин и пароль пользователя при входе в систему) в контексте .Net Core версии 2.2 (или выше). И, если пользователь авторизован – формировать валидный JWT-токен с логином, ID пользователя и списком ролей

Проблемы


Обычно приложения не net.core работает с собственным веб-сервером kestrel. Kestrel - это очень скромный и шустрый веб-сервер, который, увы, не умеет NTLM. Отсюда вытекает решение – использовать kestrel совместно с IIS. Такое себе решение, но даже если так – то разработчику все равно не получить доменный логин пользователя. Ведь все, что сделает IIS, это добавит заголовки на запрос NTLM-аутентификации.

Выход


Использование драйвера http.sys в контексте net.core приложения. При этом не требуется размещение приложения в IIS, равно как и сам Internet Information Service.

Что такое http.sys


Http.sys – это низкоуровневый драйвер прослушивающий tcp-порты на предмет http-сессий и умеющий работать с ними. Является одним из главных компонентов IIS и, фактически, это самостоятельный http-сервер умеющий в NTLM. То что нужно!

Как использовать http.sys в .net core


Нужно просто указать приложению что мы будем использовать не собственную реализацию веб-сервера kestrel, а низкоуровневый виндовый http.sys драйвер. Выглядит это так

using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using System; using System.IO; using System.Threading; namespace Common.Auth.Api { public class Program { public static void Main(string[] args) { var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{environment}.json", optional: true) .Build(); CreateWebHostBuilder(args) .Build() .Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) { var webHostBuilder = WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseHttpSys(options => { options.Authentication.Schemes = Microsoft.AspNetCore.Server.HttpSys.AuthenticationSchemes.NTLM; options.Authentication.AllowAnonymous = false; }); return webHostBuilder; } } }

На строке 27 мы объявляем UseHttpSys - мы будем хостить наше приложение c помощью http.sys драйвера. Ниже мы производим настройки веб-сервера - указываем, что все пользователи должны быть авторизованы с помощью NTLM-механизма. Готово.

Как получить ActiveDirectory логин пользователя


Еще проще, чем использовать http.sys вместо kestrel. В нужном место просто обращаемся к http-контексту, вот так
[HttpGet("auth")] public IActionResult Auth() { var userLogin = HttpContext?.User?.Identity?.Name; return Ok(userLogin); }

Если вы хотите получать пользователя из сервиса, который ничего не знает о Controller или HttpContext - то вполне можно использовать встроенный IHttpContextAccessor и его реализацию HttpContextAccessor. Только не забудьте добавить зависимость в Startup

services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();


Далее просто объявляете в конструкторе сервиса IHttpContextAccessor и готово, вы можете получить доступ к контексту запроса и вытащить из него AD login пользователя.

Как формировать JWT


Описание формирование JWT-токена тянет на отдельную статью. Но буду краток, ведь задача очень атомарна – нужно взять доменный логин пользователя, проверить его в собственной базе данных и, если все ок, сформировать валидный JWT-токен. А если пользователя нет в бд, то можно плюнуть ему в лицо вернуть 403 ответ.
Метод генерации JWT-токена
private string GenerateAccessToken(int userId, string userLogin, string[] userRoles) { if (userId == 0 || string.IsNullOrEmpty(userLogin)) { return null; } var claims = new List<Claim> { new Claim("id", userId.ToString()), new Claim("login", userLogin) }; if (userRoles != null && userRoles.Any()) { foreach (var r in userRoles) { claims.Add(new Claim(ClaimTypes.Role, r)); } } var SECRET_JWT_KEY = "mysecretkey"; var symmetricKey = new SymmetricSecurityKey(System.Text.Encoding.UTF32.GetBytes(SECRET_JWT_KEY)); var claimsIdentity = new ClaimsIdentity(claims, "Token", JwtClaims.USER_LOGIN, ClaimTypes.Role); var utcNow = DateTime.UtcNow; var jwt = new JwtSecurityToken( issuer: "my_super_app_name", audience: "_anyone_who_supports_LDAP-auth", notBefore: utcNow, claims: claimsIdentity.Claims, expires: utcNow.Add(TimeSpan.FromMinutes(1)), signingCredentials: new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256)); var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); return encodedJwt; }

Метод GenerateAccessToken надо вызывать с параметрами userId, userLogin и userRoles. Все эти параметры, обычно, лежат в БД приложения.
Таким образом процесс генерации JWT-токена на основе доменного логина пользователя выглядит так: мы получили NTLM-логин пользователя (читать как логин от доменной учетки), далее пошли в БД нашего приложения, нашли там пользователя по строгому совпадению логина и получили его ID и список ролей. Все это добро мы запихиваем в JWT токен (это основные составляющие JWT-токена).
Далее мы передаем сгенерированный токен в приложение-потребитель и подписываем им необходимые запросы к серверу. Уже на сервере мы проверяем валидность JWT-токена.

That is all, folks!
 
37
0

Оставлять комментарии могут только зарегистрированные пользователи

пока никто не оставлял комментариев

Последние статьи

Сборка на водяном охлаждении. Часть 1

Компьютер на водяном охлаждении

Основы моддинга

Моддинг. Домашние компьютеры больше не унылые