首页 > 解决方案 > Web API 设计问题 - 阻止用户使用 PUT/PATCH 更新用户 ID

问题描述

我遇到了一些设计问题,如何防止用户更新发布到 API 的数据中的用户 ID 字段。我正在使用 ASP.Net Core 2.1 和 Entity Framework Core。此 API 将与 Web 应用程序一起使用(我也将构建它),并且这两个应用程序都需要身份验证以及策略授权。

我有一个事件类,它引用了创建它的用户(用户 id + 用户到我的用户实体的导航属性)。每当使用 PUT/PATCH/DELETE 调用 API 时,我都希望防止用户能够更新用户 ID 字段并删除其他用户事件。

可以使用所有 HTTP 动词调用 API,例如api/events列出所有事件的 GET 请求或api/events创建事件的 POST。

我的活动实体:

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.

目前我采用的方法是将用户 ID 显式传递给 API。在操作中,我从数据库中获取事件,将传入的用户 ID 的值与从数据库中获取的用户 ID 进行比较。如果它相同,我允许更新发生。如果它不同,我会返回 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}为我的控制器采取另一种方式,因为我想访问所有用户的所有事件或我的 Web 应用程序中的单个事件 - 我不希望它仅限于拉动单个事件用户身份。

我也有与 DELETE 相同的问题,因为我不希望用户删除其他用户的帖子。所以它的设置如下:

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteEvent([FromRoute] Guid id, [FromQuery] Guid userId)

请注意,我的更新模型不包含 UserId 只是因为我想防止它被覆盖。我想出了 3 种不同的方法来解决这个问题,我想知道哪一种是正确的方法,以及是否有更好的设计。

1)从 API 中删除任何对用户 ID 验证的检查,并将逻辑放入将调用 API 的 Web 应用程序中。使用这种方法,每次用户尝试在应用程序中编辑事件时,我首先必须从 API 获取事件,将当前用户 ID 与从 API 获取的用户 ID 进行比较,然后让他们继续或抛出错误。我对此的唯一问题是必须进行一次额外的 API 之旅而不是一次(首先获取事件,然后可能发布事件)。使用这种方法,我的事件更新模型可以安全地在其中包含 userid 字段,并且我可以在没有查询字符串参数的情况下简化我的 API 调用。

2)稍微修改我上面的方法,不使用查询字符串参数,只使用路由参数。与其调用api/events/1?id=1,不如将其修改为api/events/1/1第一个参数是 eventId,第二个参数是 userId。我觉得在应用程序中创建这些类型的 URL 会更容易,但会偏离正确的 REST 实践。

3)保持我目前的做法。

由于可能的性能问题以及希望拥有一个自包含的 API 以便可以将其插入其他应用程序而无需在 Web 应用程序中编写复杂的逻辑,我对方法 1 有点犹豫。一种方法比另一种更好吗?有没有更好的方法来设计我的外向模型?对设计的任何帮助都会非常有帮助。

标签: c#asp.netasp.net-web-api

解决方案


推荐阅读