【问题标题】:REST API responses based on authentication, best practices?基于身份验证、最佳实践的 REST API 响应?
【发布时间】:2020-12-10 21:59:58
【问题描述】:

我有一个端点为GET /users/{id} 的API,它返回一个User 对象。 User对象可以包含cardLast4cardBrand等敏感字段。

{
    firstName: ...,
    lastName: ...,
    cardLast4: ...,
    cardBrand: ...
}

如果用户使用自己的 ID 调用该端点,则所有字段都应该可见。但是,如果是其他人的 ID,则应隐藏 cardLast4cardBrand

我想知道这里设计我的回复的最佳做法是什么。我看到三个选项:

选项 1。两个 DTO,一个包含所有字段,一个不包含隐藏字段:

// OtherUserDTO
{
    firstName: ...,
    lastName: ...,  // cardLast4 and cardBrand hidden
}

我可以看到这对于基于角色的 DTO 来说已经失控了,如果现在我有 UserDTOForAdminRoleUserDTOForAccountingRole 等等……看起来它很快就会随着潜在 DTO 的数量而失控。

选项 2。一个响应对象是 User,但将用户不应该看到的值清空。

{
    firstName: ...,
    lastName: ...,
    cardLast4: null, // hidden
    cardBrand: null  // hidden
}

选项 3. 创建另一个端点,例如 /payment-methods?userId={userId},即使 PaymentMethod 不是我数据库中的实体。这现在需要 2 次 api 调用来获取所有数据。如果userId不是自己的,则返回403禁止。

{
    cardLast4: ...,
    cardBrand: ...
}

这里有哪些最佳做法?

【问题讨论】:

    标签: api rest


    【解决方案1】:

    你会对此有不同的看法,但我觉得在某个端点上执行GET 请求,并根据授权状态获得不同形状的数据可能会令人困惑。

    因此,如果这样做是合理的,我会很想通过辅助端点公开特权数据。通过只是在此处公开私有属性,或者通过具有 2 个不同的端点,一个具有非特权数据,另一个重复数据 + 新的私有属性。

    我倾向于选择选项 1,因为 API 端点不仅仅是获取数据的一种方式。 URI 是一个身份,所以我希望 /users/123 在任何地方都表示相同的意思,并有第二个 /users/123/secret-properties

    【讨论】:

    • 感谢您的回复。这让我想到了第三个选项,我已经编辑了我的问题以包括在内。
    【解决方案2】:

    我有一个带有端点 GET /users/{id} 的 API,它返回一个用户对象。

    一般而言,重新构建您的思维可能会有所帮助——REST 中的资源是文档(想想“网页”)的概括,而不是对象的概括。 “HTTP 是一种应用协议,其应用领域是通过网络传输文档” -- Jim Webber, 2011

    如果用户使用自己的 ID 调用该端点,则所有字段都应该可见。但是,如果是其他人的 ID,则应该隐藏 cardLast4 和 cardBrand。

    全局视图:在 HTTP 中,您在隐私(仅向允许访问的人显示包含敏感信息的文档)和缓存(通过使用文档副本来满足多个人的需求来节省带宽和服务器压力)之间存在一些矛盾请求)。

    缓存是REST architectural style中一个重要的架构约束;这就是将“网络规模”置于万维网中的一点。

    好的,首先是好消息——HTTP has special rules 用于缓存带有授权标头的 Web 请求。除非您有意选择允许重复使用响应,否则您不必担心缓存。


    将两个不同的视图视为两个不同的文档,使用不同的标识符,几乎可以让一切变得更容易——公共文档可供公众使用,敏感文档被锁定,查看日志中流量的操作员可以区分这两者不同的视图,因为记录的标识符不同,等等。

    不简单的事情是:有人正在编辑(POST/PUT/PATCH)一个文档并希望看到更改出现在另一个文档中。 Cache-invalidation 是计算机科学中的两个难题之一。 HTTP 没有允许源服务器将任意文档标记为无效的通用机制——成功的不安全请求将使有效目标 uri、位置、内容位置无效,仅此而已......以及所有三个其中一些值还有其他重要用途,使它们对游戏更具挑战性。

    具有不同absolute-uri 的文档是不同的文档,这些文档一旦从源服务器复制,可能会不同步。

    这是我通常会选择的选项 - 查看文档缓存副本的客户端没有看到服务器所做的更改


    好的,您决定不喜欢这些取舍。我们可以只使用一个资源标识符吗?您会立即在通用日志中失去一些清晰度,但也许定制的日志系统会让您超越这一点。

    此时您可能还必须转储公共缓存。在允许查看敏感信息的用户和不允许查看敏感信息的用户之间更改的唯一通用标题?那是授权标头,授权没有“Vary”机制。

    对于正在对敏感副本进行更改但想要现在查看公共副本(以确保没有泄漏?或确保公开可见的更改生效)的用户来说,您也面临一些挑战?)

    “向我展示公共版本”没有通用标头,因此您需要使用非标准标头(通用组件将忽略该标头),或者您需要尝试标准化某些内容,然后推动实施者采用通用组件。这是可行的(毕竟发生了补丁),但工作量很大。


    您可以尝试的另一个技巧是使用 Content-Type 和 Accept 标头玩游戏——也许客户端对公共版本(例如 application/json)使用正常的东西,而对敏感版本(application/ prs.example-sensitive+json)。

    这将允许源服务器使用Vary 标头来指示响应仅适用于使用相同的接受标头。

    再一次,通用组件不会知道您的定制内容类型,也永远不会要求它。

    标准化路线在这方面确实对您没有帮助,因为您真正需要的是客户端区分这两种模式,今天的通用组件正试图使用​​该渠道来宣传所有标准化的表示他们可以处理。

    我认为这实际上无法让您使用定制的标题更容易伪造。


    REST 非常倾向于使用易于标准化的形式;如果您认为这是一个可能适用于世界上所有资源的普遍问题,那么标题是正确的方法。所以一个合理的方法是尝试自定义标题,并获得大量经验,然后尝试编写一些内容并让每个人都接受。

    如果您想要一些仅适用于我们今天拥有的开箱即用网络的东西,请使用两个不同的 URI 并继续解决重要问题。

    【讨论】:

      猜你喜欢
      • 2020-11-12
      • 1970-01-01
      • 2012-08-26
      • 2021-05-24
      • 2014-01-23
      • 2013-01-17
      • 2014-06-23
      • 1970-01-01
      • 2017-06-26
      相关资源
      最近更新 更多