Я работаю над проектом с клиентом Angular и несколькими веб-API, где я случайно наткнулся на проблему. И на данный момент, я не знаю, КАК решить эту проблему.
Сначала, что работает:
- пользователь входит в систему, получает токен и обновляет sh токен
- пользователь вызывает защищенный маршрут веб-API от клиента, например, [Authorize (Roles = "Admin")]
- , если срок действия токена истек, пользователь получает новый токен и refre * 1076. * token
- повторный вызов от клиента
Работает как задумано, и поэтому я еще не видел проблему.
Что не не работает:
- пользователь входит в систему, получает токен и обновляет sh токен
- пользователь вызывает незащищенный маршрут Web API, который возвращает открытый заголовок из функции разбивки на страницы
- , если токен истек тем временем
, здесь начинается проблема.
Вот как токены обрабатываются в API :
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8
.GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
context.Response.Headers.Add("Access-Control-Expose-Headers", "Token-Expired");
}
return Task.CompletedTask;
}
};
});
В случае токена с истекшим сроком действия на защищенном маршруте API 401 с открытым заголовком возвращается нед. Клиент Angular реагирует на эту ошибку через перехватчик ошибок, запрашивает новый токен в моем пользовательском API и повторяет вызов исходного API.
Проблема возникает, если вызывается незащищенный маршрут (с токен с истекшим сроком действия), который также имеет открытый заголовок в результате. В моем случае это возвращает нумерованный список и информация о нумерации добавляется в заголовок:
public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages)
{
var paginationHeader = new PaginationHeader(currentPage, itemsPerPage, totalItems, totalPages);
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
response.Headers.Add("Pagination", JsonConvert.SerializeObject(paginationHeader, camelCaseFormatter));
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
}
В этом случае поток выглядит так:
- событие OnAuthenticationFailed вызывается и Token -Expired добавляется и отображается в заголовке ответа.
- Нет 401 возвращается (я полагаю, потому что маршрут не защищен) клиенту
- он продолжается до изначально называется незащищенным маршрутом и добавляет заголовок пагинации и пытается также разоблачить это
- , и это приводит к ошибке 500
2020-03-27 01:32:54.8663|1|ERROR|Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware|An unhandled exception has occurred while executing the request. System.ArgumentException: An item with the same key has already been added.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowDuplicateKeyException()
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.System.Collections.Generic.IDictionary<System.String,Microsoft.Extensions.Primitives.StringValues>.Add(String key, StringValues value)
На данный момент я понятия не имею, как чтобы обойти это, потому что
- Почему я вообще получаю событие OnAuthenticationFailed, если не вызывается защищенный маршрут? Да, токен истек, но для незащищенного маршрута аутентификация не требуется.
- Как в этом случае избежать DuplicateKeyException?
Спасибо за помощь.
РЕДАКТИРОВАТЬ:
На данный момент я решил проблему с помощью того, что я проверяю уже существующий ключ в заголовках и удаляю его, но я не знаю, если это правильный путь:
public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages)
{
var paginationHeader = new PaginationHeader(currentPage, itemsPerPage, totalItems, totalPages);
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
StringValues locationHeaderValue = string.Empty;
if (response.Headers.TryGetValue("Access-Control-Expose-Headers", out locationHeaderValue))
{
response.Headers.Remove("Access-Control-Expose-Headers");
}
response.Headers.Add("Pagination", JsonConvert.SerializeObject(paginationHeader, camelCaseFormatter));
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
}