Привет, дорогой друг! <br/> <br/> Не судите строго. Позвольте поделиться полной историей: <br/> <br/> У меня есть backend на C# (.NET), который использует сторонний сервис для аутентификации и авторизации. Также имеется frontend, обслуживающий все сервисы. <br/> <br/> В этом случае backend на C# (.NET) выступает в роли клиента OAuth2 для сервера аутентификации. <br/> <br/> <pre><code class="cs">self.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Events.OnValidatePrincipal = context => { var descriptor = context.HttpContext.GetEndpoint()?.Metadata.OfType<ControllerActionDescriptor>().FirstOrDefault(); if (descriptor != null && DISABLEDCONTROLLER.TryGetValue(descriptor.ControllerTypeInfo, out var exception) && exception.Contains(descriptor.MethodInfo.Name)) { return Task.CompletedTask; } var principalValidator = context.HttpContext.RequestServices.GetRequiredService<PrincipalValidator>(); return principalValidator.ValidateAsync(context); }; options.SessionStore = self.BuildServiceProvider().GetRequiredService<ITicketStore>(); options.Cookie.HttpOnly = true; options.Cookie.Name = authorizationService.CookieName; options.Cookie.Domain = authorizationService.CookieDomain; options.Events.OnRedirectToAccessDenied = context => {
В обработчике события `OnValidatePrincipal`, предоставляемом в конфигурации аутентификации Cookie в ASP.NET Core, вы можете проверить активность сессии пользователя на стороннем сервере аутентификации. Для этого вам нужно будет осуществить HTTP-запрос к вашему серверу аутентификации для получения информации о пользователе. Ниже приведён пример того, как это можно реализовать. Предположим, у вас есть метод для проверки состояния сессии на стороннем сервере, например `IsSessionActive`, который возвращает `true`, если сессия пользователя активна, и `false` в противном случае. Вот пример кода: ```csharp self.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Events.OnValidatePrincipal = async context => { var descriptor = context.HttpContext.GetEndpoint()?.Metadata .OfType<ControllerActionDescriptor>().FirstOrDefault(); if (descriptor != null && DISABLEDCONTROLLER.TryGetValue(descriptor.ControllerTypeInfo, out var exception) && exception.Contains(descriptor.MethodInfo.Name)) { return; } var principalValidator = context.HttpContext.RequestServices.GetRequiredService<PrincipalValidator>(); var result = await principalValidator.ValidateAsync(context); // Допустим, у вас есть доступ к HttpClient для совершения запросов к вашему серверу аутентификации var httpClient = context.HttpContext.RequestServices.GetRequiredService<HttpClient>(); var userId = context.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (userId != null) { // Здесь вызываете метод для проверки активности сессии var isActive = await IsSessionActive(httpClient, userId); if (!isActive) { context.RejectPrincipal(); // Отклоняем текущий Principal, т.е. пользователь не авторизован await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } } }; options.SessionStore = self.BuildServiceProvider().GetRequiredService<ITicketStore>(); options.Cookie.HttpOnly = true; options.Cookie.Name = authorizationService.CookieName; options.Cookie.Domain = authorizationService.CookieDomain; options.Events.OnRedirectToAccessDenied = context => { context.Response.StatusCode = StatusCodes.Status403Forbidden; return Task.CompletedTask; }; }); // Пример метода для проверки активности сессии private async Task<bool> IsSessionActive(HttpClient httpClient, string userId) { var response = await httpClient.GetAsync($"https://your-auth-server/api/session/status/{userId}"); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadAsStringAsync(); // Считаем, что ответ JSON и возвращает поле "isActive" return JsonConvert.DeserializeObject<SessionStatusResponse>(result).IsActive; } return false; } // Вспомогательный класс для десериализации ответа public class SessionStatusResponse { public bool IsActive { get; set; } } ``` В этом примере: - Мы используем `HttpClient` для выполнения GET-запроса на сервер аутентификации с информацией о пользователе (например, его ID). - Метод `IsSessionActive` возвращает `true`, если сессия активна, или `false`, если нет. - Если сессия не активна, мы отклоняем текущий `ClaimsPrincipal` и вызываем выход пользователя из системы. Не забудьте установить `HttpClient` в контейнере DI, если вы его ещё не сделали.
Подскажите, как делают refresh_token взрослые дяди в многопоточке.
Если нужен ответ на это конкретный вопрос, причем если имеется в виду не просто многопоточка, а асинхронная, с await без блокировки потока - то на это есть такой SemaphoreSlim. Делается примерно так (надеюсь, идея будет понятна)
//Попадаем сюда после получения существующего access token и выявления, что он просрочен SemaphoreSlim sem = GetSemaphore(clientId); await sem.WaitAsync(); //timeot и CancellationToken добавить по вкусу try { //Получаем существующий access token повторно - вдруг его уже кто-нибудь до нас обновил // (используем double check pattern) //Если это не так, выполняем тут всю логику обновления access token } finally { sem.Release(); //SemaphoreSlim - не мьютекс, сам не освободится в случае чего }
GetSemaphore реализовать можно по-разному. Можно один на все приложение: static или Singleton - это если нагрузка небольшая.
А можно кэшировать семафоры по одному для каждого пользователя (т.е. свой семафор для каждого clientID), чтобы пользователи не толклись около одного семафора на всех.
Главное, чтобы этот семафор создавался с начальным значением 1 - тогда он будет пускать пользователей по одному.
Ну, а если все делать без асинхронности, в одном потоке, блокируя его при необходимости (т.е. без await), то способов много. Простейший - блок lock вокруг кода обновления маркера доступа (access token), есть такде Monitor, Mutex, тот же Semaphore (хоть со Slim, хоть без)...