У меня возникла небольшая проблема с дизайном, связанная с тем, как запретить пользователям обновлять поле идентификатора пользователя в данных, публикуемых в API. Я использую ASP.Net Core 2.1 и Entity Framework Core. Этот API будет использоваться с веб-приложением (я тоже буду его создавать), и для обоих приложений потребуется аутентификация и авторизация политики.
У меня есть класс событий, который ссылается на то, кем был пользователь, который его создал (идентификатор пользователя + свойство навигации пользователя для моей сущности пользователя). Каждый раз, когда API вызывается с помощью PUT / PATCH / DELETE, я хочу запретить пользователям возможность обновлять поле идентификатора пользователя и удалять события других пользователей.
API можно вызывать со всеми глаголами HTTP, например, запросом GET на api/events
для получения списка всех событий или запросом POST на api/events
для создания события.
Моя сущность события:
public Guid Id { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public string Url { get; set; }
public DateTime Created { get; set; }
public DateTime ScheduledTime { get; set; }
public Guid UserId { get; set; }
public User CreatedBy { get; set; }
Модель данных обновления событий (для запросов PUT / PATCH):
public string Title { get; set; }
public string Message { get; set; }
public string Url { get; set; }
public DateTime? ScheduledTime { get; set; }
Мой пользовательский объект
public Guid Id { get; set; }
public string Email { get; set; }
public List<Event> Events { get; set; } = new List<Event>();
Маршрут для моего контроллера очень прост: [Route("api/[controller]")]
, поэтому к нему обращается api/events
.
В настоящее время я выбрал явный способ передачи идентификатора пользователя в API. В действии я извлекаю событие из базы данных, сравниваю значения идентификатора пользователя, которые были переданы, с идентификатором пользователя, извлеченным из базы данных. Если это то же самое, я разрешаю обновление. Если он отличается, я возвращаю 403 запрещено вместе с сообщением об ошибке.
т.е. Мое действие по обновлению в API:
[HttpPut("{id}")]
public async Task<IActionResult> UpdateEvent([FromRoute] Guid id, [FromQuery] Guid userId,[FromBody] EventUpdateDto eventToUpdate)
{
//compare userId passed to action by fetching event from database and compare values
//return 403 if not equal else continue
}
Проблема в том, что я использую как параметры маршрута, так и параметры строки запроса. Я могу упростить маршрут до {eventid}/{userid}
, но я не знаю, является ли это наилучшей практикой. Это будет означать, что я вызываю мой API, чтобы обновить определенное событие, а затем углубиться в конкретного пользователя, что не имеет особого смысла. Я также не хотел идти другим путем api/{userid}/events/{eventid}
для моего контроллера, потому что я хочу получить доступ ко всем событиям для всех пользователей или одному событию в моем веб-приложении - я не хочу, чтобы оно ограничивалось только событиями извлечения для одного пользователя.
У меня также есть та же проблема с DELETE, поскольку я не хочу, чтобы пользователь удалял сообщения других пользователей. Так что его настройки, как показано ниже:
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteEvent([FromRoute] Guid id, [FromQuery] Guid userId)
Обратите внимание, что моя модель обновления не содержит UserId только потому, что я хотел предотвратить ее переопределение. Я придумал 3 отдельных подхода к этой проблеме, и мне интересно, какой из них является правильным путем и есть ли лучший дизайн для этого.
1) Удалите все проверки для проверки идентификатора пользователя из API и поместите логику в веб-приложение, которое будет вызывать API. При таком подходе каждый раз, когда пользователь пытается отредактировать событие в приложении, мне сначала нужно извлечь событие из API, сравнить текущий идентификатор пользователя с идентификатором пользователя, полученным из API, и либо позволить им продолжить, либо выдать ошибку. Единственная проблема, с которой я столкнулся, заключалась в том, что вместо одного (вместо события «сначала извлекается событие», а затем, возможно, после события) должна быть проведена дополнительная поездка к API. При таком подходе моя модель обновления событий может безопасно содержать поле ИД пользователя, и я могу упростить мой вызов API без параметра строки запроса.
2) Немного измените мой подход выше, чтобы не использовать параметры строки запроса и использовать только параметры маршрута. Вместо вызова api/events/1?id=1
измените его так, чтобы оно выглядело как api/events/1/1
, где первый параметр - это идентификатор события, а второй - идентификатор пользователя. Я чувствую, что создание URL-адресов такого типа будет проще в приложении, но отклонится от правильной практики REST.
3) Сохраняйте мой текущий подход.
Я немного сомневаюсь в подходе № 1 из-за возможных проблем с производительностью, а также из-за необходимости иметь отдельный API, чтобы его можно было подключать к другим приложениям без необходимости писать сложную логику в веб-приложении. Один подход лучше другого? Есть ли лучший подход, который я могу использовать при проектировании моих моделей наружной облицовки? Любая помощь по дизайну будет очень полезна.