Использование Auth Cookies в ASP.NET Core
Использование авторизации cookie в ASP.NET Core реализуется с простотой и гибкостью. В этой статье мы расскажем, почему она может быть хорошим выбором для вашего следующего проекта, и как использовать множество доступных опций.
Проверка подлинности на основе файлов cookie является популярным выбором для защиты веб-приложений, ориентированных на клиента. Для программистов .NET в ASP.NET Core существует хороший подход, который стоит изучить. В этом примере мы углубимся в файл cookie аутентификации, используя ASP.NET Core 2.1. Версия 2.1 является последней версией LTS на момент написания этой статьи. Итак, не стесняйтесь следовать, мы предполагаем, что вы находитесь в Visual Studio или у вас достаточно C#, чтобы использовать текстовый редактор. Мы опустим пространства имен и используем операторы, чтобы сфокусировать примеры кода. Если вы застряли, скачайте образец кода, найденный в конце.
В ASP.NET 2.1 вы можете использовать проверку подлинности на основе файлов cookie из коробки. Нет необходимости в дополнительных пакетах NuGet. Новые проекты включают в себя метапакет, в котором есть все, а именно Microsoft.AspNetCore.App. Чтобы продолжить, введите dotnet new mvc в CLI или выполните File> New Project в Visual Studio.
Для тех из вас, кто пришел из классического .NET, вам может быть известен файл OWIN auth cookie. Вы будете рады узнать, что эти навыки достаточно хорошо перенесены в ASP.NET Core. Две реализации остаются несколько похожими. В ASP.NET Core вы по-прежнему настраиваете файл cookie авторизации, настраиваете промежуточное программное обеспечение и настраиваете утверждения личности.
Настройка
Для начала, предполагается, что вы достаточно знаете об инфраструктуре ASP.NET MVC, чтобы встроить кодогенерацию в каркасное веб-приложение. Вам нужен HomeController с методами действий Index, Login, Logout и Revoke. После входа в систему логин будет перенаправлен на Index,, поэтому его не нужно просматривать. Мы не будем показывать пример кода представления, так как представления здесь не в фокусе. Если вы заблудились, обязательно скачайте демо, чтобы поиграть с ним.
Мы будем использовать журналы отладки, чтобы показать критические события в аутентификации cookie. Обязательно включите журналы отладки в appsettings.json и отключите журналы Microsoft и системы.
Наша настройка журнала выглядит так:
"LogLevel": { "Default": "Debug", "System": "Warning", "Microsoft": "Warning" } |
Теперь вы готовы создать базовое приложение с аутентификацией cookie. Мы воздержимся от HTML-форм с полями ввода имени пользователя и пароля. Эти внешние проблемы только добавляют беспорядок в то, что является более важным, что является auth cookie. Начиная со скелета, приложение показывает, насколько эффективно добавлять auth cookie с нуля. Приложение автоматически зарегистрирует вас и попадет на страницу индекса с файлом cookie авторизации. Затем вы можете выйти или отозвать доступ пользователя. Обратите внимание на то, что происходит с auth cookie, когда мы установим аутентификацию.
Параметры cookie
Начните с настройки параметров файла cookie для аутентификации через промежуточное ПО внутри класса Startup. Параметры cookie сообщают промежуточному программному обеспечению аутентификации, как cookie ведет себя в браузере. Вариантов много, но мы остановимся только на тех, которые больше всего влияют на безопасность файлов cookie.
- HttpOnly: флаг, который говорит, что cookie доступны только для серверов. Браузер только отправляет cookie, но не может получить к нему доступ через JavaScript.
- SecurePolicy: ограничивает куки для HTTPS. Рекомендуем установить это на значение Always. Оставьте значение None на местном уровне.
- SameSite: указывает, может ли браузер использовать cookie-файлы для межсайтовых запросов. Для аутентификации OAuth установите для этого параметра значение Lax. Я устанавливаю это как строгое, потому что файл cookie авторизации предназначен только для одного сайта. Установка этого значения в None не устанавливает значение заголовка cookie.
Существуют параметры cookie как для файла cookie для аутентификации, так и для глобальной политики использования файлов cookie. Будьте бдительны, поскольку политика использования файлов cookie может переопределять параметры проверки подлинности файлов cookie и наоборот. Если для HttpOnly установлено значение false в cookie -файле auth, параметры политики cookie -файлов будут переопределены. При настройке SameSite в политике cookie -файлов переопределяются параметры cookie -файла аутентификации. В своей демонстрации мы проиллюстрируем оба сценария, поэтому совершенно ясно, как это работает.
В классе Startup найдите метод ConfigureServices и введите:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = _environment.IsDevelopment() ? CookieSecurePolicy.None : CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Lax; }); |
Это создает службу промежуточного программного обеспечения с методами AddAuthentication и AddCookie. AuthenticationScheme полезна, когда существует более одного файла cookie аутентификации. Многие экземпляры аутентификации cookie позволяют защищать конечные точки множеством схем. Вы предоставляете любое строковое значение; по умолчанию установлено Cookies. Обратите внимание, что объект параметров является экземпляром класса CookieAuthenticationOptions.
SecurePolicy устанавливается через троичного оператора, который приходит из _environment. Это частное свойство, которое устанавливается в конструкторе запуска. Добавьте IHostingEnvironment в качестве параметра и позвольте внедрению зависимости делать все остальное.
В этом же методе ConfigureServices добавьте глобальную политику cookie через промежуточное ПО:
services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.Strict; options.HttpOnly = HttpOnlyPolicy.None; options.Secure = _environment.IsDevelopment() ? CookieSecurePolicy.None : CookieSecurePolicy.Always; }); |
Внимательно посмотрите на настройки SameSite и HttpOnly для обоих вариантов файлов cookie. Когда мы установим auth cookie, вы увидите, что для этого параметра установлено значение HttpOnly и Strict. Это показывает, как оба параметра перекрывают друг друга.
Вызовите это промежуточное ПО внутри конвейера запросов в методе Configure:
app.UseCookiePolicy();
app.UseAuthentication()
Промежуточное программное обеспечение политики файлов cookie чувствительно к порядку. Это означает, что это влияет только на компоненты после вызова. Запустив промежуточное программное обеспечение для аутентификации, вы получите свойство HttpContext.User. Обязательно вызовите этот метод UseAuthentication перед вызовом UseMvc.
Вход
В HomeController добавьте фильтр AllowAnonymous к методам входа и выхода. Есть только два метода действия, доступных без файла cookie авторизации.
Внутри класса Startup найдите метод расширения AddMvc и добавьте глобальный фильтр авторизации:
services.AddMvc(options => options.Filters.Add(new AuthorizeFilter()))
С безопасным приложением настройте имя файла cookie и пути входа / выхода. Найдите, где находятся остальные CookieAuthenticationOptions и сделайте следующее:
options.Cookie.Name = "SimpleTalk.AuthCookieAspNetCore"; options.LoginPath = "/Home/Login"; options.LogoutPath = "/Home/Logout"; |
Это приведет к тому, что приложение будет перенаправлено на конечную точку входа в систему для входа. Однако, прежде чем вы сможете взять это в оборот, вам нужно будет создать файл cookie аутентификации.
Сделайте это внутри метода действия Login в HomeController:
var claims = new List<Claim> { new Claim(ClaimTypes.Name, Guid.NewGuid().ToString()) }; var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties(); await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); |
AuthenticationProperties управляет поведением cookie-файлов в браузере. Например, свойство IsPersistent сохраняет cookie в сеансах браузера. Обязательно получите явное согласие пользователя при включении этого свойства. ExpiresUtc устанавливает абсолютный срок действия, обязательно включите IsPersistent и установите для него значение true. Значения по умолчанию предоставят вам файл cookie сеанса, который исчезнет, когда вы закроете вкладку или окно браузера. Мы находим значения по умолчанию в этом объекте для большинства случаев использования.
Чтобы это сделать, загрузите браузер, перейдя на главную страницу или страницу Index. Обратите внимание, что он перенаправляет на вход в систему, который перенаправляет обратно в Index с файлом cookie авторизации.
Как только это загружается, это выглядит примерно так. Обязательно внимательно посмотрите, как установлен файл cookie для аутентификации:
WT Identity Claim
Зачастую файла cookie авторизации недостаточно для защиты конечных точек API или микросервисов. Чтобы веб-приложение вызывало службу, оно может использовать токен-носитель JWT для аутентификации. Чтобы сделать токен доступа доступным, поместите его в заявку на идентификацию.
В методе действия Login в HomeController разверните список заявок с помощью JWT:
var userId = Guid.NewGuid().ToString(); var claims = new List<Claim> { new Claim(ClaimTypes.Name, userId), new Claim("access_token", GetAccessToken(userId)) }; private static string GetAccessToken(string userId) { const string issuer = "localhost"; const string audience = "localhost"; var identity = new ClaimsIdentity(new List<Claim> { new Claim("sub", userId) }); var bytes = Encoding.UTF8.GetBytes(userId); var key = new SymmetricSecurityKey(bytes); var signingCredentials = new SigningCredentials( key, SecurityAlgorithms.HmacSha256); var now = DateTime.UtcNow; var handler = new JwtSecurityTokenHandler(); var token = handler.CreateJwtSecurityToken( issuer, audience, identity, now, now.Add(TimeSpan.FromHours(1)), now, signingCredentials); return handler.WriteToken(token); } |
Должны предупредить, никогда не делайте это при продуцировании. Здесь мы используем идентификатор пользователя в качестве ключа подписи, который симметричен, чтобы упростить его. В среде prod используйте асимметричный ключ подписи с открытым и закрытым ключами. Затем клиентские приложения будут использовать хорошо известную конечную точку конфигурации для проверки JWT.
Размещение JWT в ClaimsIdentity делает его доступным через свойство HttpContex.User. Допустим, для этого приложения вы хотите поместить JWT в журнал отладки, чтобы показать этот причудливый токен доступа.
В классе Startup создайте это промежуточное ПО внутри метода Configure:
app.Use(async (context, next) => { var principal = context.User as ClaimsPrincipal; var accessToken = principal?.Claims .FirstOrDefault(c => c.Type == "access_token"); if (accessToken != null) { _logger.LogDebug(accessToken.Value); } await next(); }); |
_Logger - это еще одно частное свойство, которое вы устанавливаете через конструктор. Добавьте ILogger <Startup> в качестве параметра и позвольте внедрению зависимости делать все остальное. Обратите внимание, что ClaimsPrincipal содержит список утверждений, через которые вы можете выполнить итерации. Что мы считаем полезным, так это искать утверждение типа, например access_token, и получать значение. Поскольку это промежуточное ПО, всегда вызывайте next (), чтобы не блокировать конвейер запросов.
Выход
Чтобы выйти из веб-приложения и очистить куки-файл auth, выполните:
await HttpContext.SignOutAsync( CookieAuthenticationDefaults.AuthenticationScheme); |
Это относится к методу действия выхода из системы в HomeController. Обратите внимание, что вы указываете схему аутентификации. Это сообщает методу выхода из системы, какой файл cookie нужно удалить. Проверка заголовков ответов HTTP показывает, что заголовки Cache-Control и Pragma установлены в no-cache. Это показывает, что auth cookie отключает кэширование браузера, когда он хочет обновить cookie. Метод действия Login отвечает теми же заголовками HTTP.
Ревокация
В некоторых случаях приложение должно реагировать на изменения доступа конечного пользователя. Auth cookie защитит приложение, но остается в силе в течение всего срока действия cookie. С действительным файлом cookie конечный пользователь не увидит никаких изменений, пока он не выйдет из системы или не истечет срок действия файла cookie. В ASP.NET Core 2.1 одним из способов проверки изменений является использование событий проверки подлинности cookie. Событие проверки может выполнять внутренние поиски из утверждений личности в cookie-файле auth. Создайте событие, расширив CookieAuthenticationEvents. Переопределите метод ValidatePrincipal и установите событие в параметрах файла cookie auth.
Например:
public class RevokeAuthenticationEvents : CookieAuthenticationEvents { private readonly IMemoryCache _cache; private readonly ILogger _logger; public RevokeAuthenticationEvents( IMemoryCache cache, ILogger<RevokeAuthenticationEvents> logger) { _cache = cache; _logger = logger; } public override Task ValidatePrincipal( CookieValidatePrincipalContext context) { var userId = context.Principal.Claims .First(c => c.Type == ClaimTypes.Name); if (_cache.Get<bool>("revoke-" + userId.Value)) { context.RejectPrincipal(); _cache.Remove("revoke-" + userId.Value); _logger.LogDebug("Access has been revoked for: " + userId.Value + "."); } return Task.CompletedTask; } } |
Чтобы IMemoryCache устанавливался путем внедрения зависимостей, поместите AddMemoryCache в метод ConfigureSerices в классе Startup. Вызов RejectPrincipal имеет немедленный эффект и возвращает вас к входу в систему, чтобы получить новый файл cookie авторизации. Обратите внимание, что это зависит от постоянства в памяти, которое устанавливается в методе действия Revoke. Помните, что это событие запускается один раз для каждого запроса, поэтому вы хотите использовать эффективную стратегию кэширования. Выполнение дорогостоящего поиска при каждом запросе повлияет на производительность.
Отмените доступ, установив кэш внутри метода Revoke в HomeController:
var principal = HttpContext.User as ClaimsPrincipal; var userId = principal?.Claims .First(c => c.Type == ClaimTypes.Name); _cache.Set("revoke-" + userId.Value, true); return View(); |
После посещения конечной точки Revoke изменение не вступает в силу немедленно. После отзыва навигация домой покажет журнал отладки и перенаправит на Вход. Обратите внимание, что при повторной посадке на странице индекса появится новый файл cookie для аутентификации.
Чтобы зарегистрировать это событие, обязательно установите EventsType в CookieAuthenticationOptions. Для регистрации этого события RevokeAuthenticationEvents вам потребуется предоставить услугу с определенной областью. Оба устанавливаются внутри метода ConfigureServices в классе Startup.
Например:
options.EventsType = typeof(RevokeAuthenticationEvents); services.AddScoped<RevokeAuthenticationEvents>(); |
CookieValidatePrincipalContext в ValidatePrincipal может сделать больше, чем отзыв, если это необходимо. В этом контексте есть ReplacePrincipal для обновления принципала, затем обновите файл cookie, установив для параметра ShouldRenew значение true.
Session Store
Настройка JWT в формуле изобретения для удобного способа доступа к идентификационным данным работает хорошо. Однако каждое утверждение личности, которое вы вводите в основной каталог, попадает в файл cookie авторизации. Если вы проверите cookie, вы заметите, что он удваивается по размеру с токеном доступа. По мере того, как вы добавляете больше утверждений в основную часть, cookie-файл auth увеличивается. Вы можете поразить ограничения HTTP-заголовка в среде prod со многими файлами cookie для аутентификации. В IIS максимальный предел по умолчанию установлен в 8KB-16KB в зависимости от версии. Вы можете увеличить лимит, но это означает большую полезную нагрузку на запрос из-за файлов cookie.
Есть много способов обуздать эту проблему, например, сеанс пользователя, чтобы сохранить все JWT вне cookie-файла аутентификации. Если у вас есть текущий код, который обращается к личности через принципала, то это не идеально. Удаление идентификационных данных из принципала рискованно, поскольку может привести к полной перезаписи.
Одной из альтернатив является использование SessionStore, найденного в CookieAuthenticationOptions. OWIN, например, обладает аналогичным свойством. Реализуйте интерфейс ITicketStore и найдите способ сохранения данных в бэкэнде. Установка свойства SessionStore определяет контейнер для хранения идентичности между запросами. Только идентификатор сеанса отправляется в браузер в cookie-файле авторизации.
Скажем, вы хотите использовать постоянство в памяти вместо файла cookie:
public class InMemoryTicketStore : ITicketStore { private readonly IMemoryCache _cache; public InMemoryTicketStore(IMemoryCache cache) { _cache = cache; } public Task RemoveAsync(string key) { _cache.Remove(key); return Task.CompletedTask; } public Task<AuthenticationTicket> RetrieveAsync(string key) { var ticket = _cache.Get<AuthenticationTicket>(key); return Task.FromResult(ticket); } public Task RenewAsync(string key, AuthenticationTicket ticket) { _cache.Set(key, ticket); return Task.CompletedTask; } public Task<string> StoreAsync(AuthenticationTicket ticket) { var key = ticket.Principal.Claims .First(c => c.Type == ClaimTypes.Name).Value; _cache.Set(key, ticket); return Task.FromResult(key); } } |
Установите экземпляр этого класса в SessionStore внутри CookieAuthenticationOptions, эти параметры задаются в методе ConfigureServices в классе Startup. Один важный момент - получение экземпляра, поскольку ему нужен поставщик из BuildServiceProvider. Временный контейнер IoC здесь чувствует себя разбитым и сосновым после лучшего решения.
Лучшим подходом является использование шаблона параметров в ASP.NET Core. Сценарии после настройки устанавливают или изменяют параметры при запуске. С этим решением вы можете использовать внедрение зависимостей, не изобретая колесо заново.
Чтобы установить этот шаблон параметров, выполните
IPostConfigureOptions<CookieAuthenticationOptions>:
public class ConfigureCookieAuthenticationOptions : IPostConfigureOptions<CookieAuthenticationOptions> { private readonly ITicketStore _ticketStore; public ConfigureCookieAuthenticationOptions(ITicketStore ticketStore) { _ticketStore = ticketStore; } public void PostConfigure(string name, CookieAuthenticationOptions options) { options.SessionStore = _ticketStore; } } |
Чтобы зарегистрировать InMemoryTicketStore и ConfigureCookieAuthenticationOptions, поместите это в ConfigureServices:
services.AddTransient<ITicketStore, InMemoryTicketStore>(); services.AddSingleton<IPostConfigureOptions<CookieAuthenticationOptions>, ConfigureCookieAuthenticationOptions>(); |
Убедитесь, что вы внесли это изменение в класс Startup. Если вы загляните внутрь Configure <CookiePolicyOptions>, например, и взломаете код. Обратите внимание на шаблон; конфигурация проходит через одноэлементный объект и лямбда-выражение. ASP.NET Core использует этот же шаблон параметров под капотом. Теперь запуск этого в браузере и проверка файла cookie аутентификации будут занимать гораздо меньше места.
Заключение
Внедрение файла cookie авторизации в ASP.NET Core 2.1 без проблем. Вы настраиваете параметры файлов cookie, запускаете промежуточное программное обеспечение и задаете утверждения личности. Методы входа и выхода работают на основе схемы аутентификации. Параметры файлов cookie для аутентификации позволяют приложению реагировать на внутренние события и устанавливать хранилище сеансов. Auth cookie достаточно гибок, чтобы хорошо работать с любым корпоративным решением.