Привет, дорогой друг! <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, хоть без)...