关于如何防止用户更新发布到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应用程序中的单个事件-我不希望它仅限于拉取单个用户ID的事件。
我也有与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,我对方法#1有点犹豫,以便可以将其插入其他应用程序而不必在Web应用程序中编写复杂的逻辑。一种方法比另一种更好吗?在设计外部模型时,是否可以采用更好的方法?对设计的任何帮助将非常有帮助。