简体   繁体   English

REST API 响应基于身份验证,最佳实践?

[英]REST API responses based on authentication, best practices?

I have an API with endpoint GET /users/{id} which returns a User object.我有一个带有端点GET /users/{id}的 API,它返回一个User object。 The User object can contain sensitive fields such as cardLast4 , cardBrand , etc. User object 可以包含cardLast4cardBrand等敏感字段。

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

If the user calls that endpoint with their own ID, all fields should be visible.如果用户使用自己的 ID 调用该端点,则所有字段都应该可见。 However, if it is someone elses ID then cardLast4 and cardBrand should be hidden.但是,如果是其他人的 ID,则应该隐藏cardLast4cardBrand

I want to know what are the best practices here for designing my response.我想知道这里设计我的回复的最佳实践是什么。 I see three options:我看到三个选项:

Option 1. Two DTOs, one with all fields and one without the hidden fields:选项 1. 两个 DTO,一个包含所有字段,一个不包含隐藏字段:

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

I can see this becoming out of hand with DTOs based on role, what if now I have UserDTOForAdminRole , UserDTOForAccountingRole , etc... It looks like it quickly gets out of hand with the number of potential DTOs.我可以看到这对于基于角色的 DTO 来说已经失控了,如果现在我有UserDTOForAdminRoleUserDTOForAccountingRole等等……看起来它很快就会随着潜在 DTO 的数量而失控。

Option 2. One response object being the User , but null out the values that the user should not be able to see.选项 2。一个响应 object 是User ,但 null 是用户看不到的值。

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

Option 3. Create another endpoint such as /payment-methods?userId={userId} even though PaymentMethod is not an entity in my database.选项 3. 创建另一个端点,例如/payment-methods?userId={userId}即使PaymentMethod不是我数据库中的实体。 This will now require 2 api calls to get all the data.这现在需要 2 个 api 调用来获取所有数据。 If the userId is not their own, it will return 403 forbidden.如果 userId 不是自己的,则返回 403 禁止。

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

What are the best practices here?这里有哪些最佳实践?

You're gonna get different opinions about this, but I feel that doing a GET request on some endpoint, and getting a different shape of data depending on the authorization status can be confusing.你会对此有不同的看法,但我觉得在某个端点上执行GET请求,并根据授权状态获取不同形状的数据可能会令人困惑。

So I would be tempted, if it's reasonable to do this, to expose the privileged data via a secondary endpoint.因此,如果这样做是合理的,我会很想通过辅助端点公开特权数据。 Either by just exposing the private properties there, or by having 2 distinct endpoints, one with the unprivileged data and a second that repeats the data + the new private properties.要么只是在那里公开私有属性,要么有 2 个不同的端点,一个带有非特权数据,另一个重复数据 + 新的私有属性。

I tend to go for option 1 here, because an API endpoint is not just a means to get data.我倾向于 go 在这里的选项 1,因为 API 端点不仅仅是获取数据的手段。 The URI is an identity, so I would want /users/123 to mean the same thing everywhere, and have a second /users/123/secret-properties URI 是一个身份,所以我希望/users/123在任何地方都表示相同的意思,并有第二个/users/123/secret-properties

I have an API with endpoint GET /users/{id} which returns a User object.我有一个带有端点 GET /users/{id} 的 API,它返回一个用户 object。

In general, it may help to reframe your thinking -- resources in REST are generalizations of documents (think "web pages"), not generalizations of objects .一般来说,它可能有助于重新构建您的思维 - REST 中的资源是文档的概括(想想“网页”),而不是对象的概括。 "HTTP is an application protocol whose application domain is the transfer of documents over a network" -- Jim Webber, 2011 “HTTP 是一种应用协议,其应用领域是通过网络传输文档” ——Jim Webber,2011

If the user calls that endpoint with their own ID, all fields should be visible.如果用户使用自己的 ID 调用该端点,则所有字段都应该可见。 However, if it is someone elses ID then cardLast4 and cardBrand should be hidden.但是,如果是其他人的 ID,则应该隐藏 cardLast4 和 cardBrand。

Big picture view: in HTTP, you've got a bit of tension between privacy (only show documents with sensitive information to people allowed access) and caching (save bandwidth and server pressure by using copies of documents to satisfy more than one request).大图视图:在 HTTP 中,您在隐私(仅向允许访问的人显示包含敏感信息的文档)和缓存(通过使用文档副本来满足多个请求来节省带宽和服务器压力)之间有些紧张。

Cache is an important architectural constraint in the REST architectural style ;缓存是REST架构风格中重要的架构约束; that's the bit that puts the "web scale" in the world wide web.这就是将“网络规模”置于全球 web 中的一点。

OK, good news first -- HTTP has special rules for caching web requests with Authorization headers.好的,首先是好消息——HTTP 具有用于缓存带有授权标头的 web 请求的特殊规则 Unless you deliberately opt-in to allowing the responses to be re-used, you don't have to worry the caching.除非您故意选择允许重复使用响应,否则您不必担心缓存。


Treating the two different views as two different documents, with different identifiers, makes almost everything easier -- the public documents are available to the public, the sensitive documents are locked down, operators looking at traffic in the log can distinguish the two different views because the logged identifier is different, and so on.将两个不同的视图视为两个不同的文档,具有不同的标识符,几乎可以让一切变得更容易——公共文档可供公众使用,敏感文档被锁定,查看日志中流量的操作员可以区分这两个不同的视图,因为记录的标识符不同,依此类推。

The thing that isn't easier: the case where someone is editing (POST/PUT/PATCH) one document and expecting to see the changes appear in the other.不太容易的事情是:有人正在编辑(POST/PUT/PATCH)一个文档并期望看到更改出现在另一个文档中。 Cache-invalidation is one of the two hard problems in computer science.缓存失效是计算机科学中的两个难题之一。 HTTP doesn't have a general purpose mechanism that allows the origin server to mark arbitrary documents for invalidation - successful unsafe requests will invalidate the effective-target-uri, the Location, the Content-Location, and that's it... and all three of those values have other important uses, making them more challenging to game. HTTP 没有通用机制,允许源服务器将任意文档标记为无效 - 成功的不安全请求将使有效目标 uri、位置、内容位置无效,仅此而已......以及所有三个这些值中有其他重要用途,使它们对游戏更具挑战性。

Documents with different absolute-uri are different documents, and those documents, once copied from the origin server, can get out of sync.具有不同absolute-uri的文档是不同的文档,这些文档一旦从源服务器复制,可能会不同步。

This is the option I would normally choose - a client looking at cached copies of a document isn't seeing changes made by the server这是我通常会选择的选项 - 查看文档缓存副本的客户端没有看到服务器所做的更改


OK, you decide that you don't like those trade offs.好的,您决定不喜欢这些权衡。 Can we do it with just one resource identifier?我们可以只使用一个资源标识符吗? You immediately lose some clarity in your general purpose logs, but perhaps a bespoke logging system will get you past that.您会立即在通用日志中失去一些清晰度,但也许定制的日志系统会让您超越这一点。

You probably also have to dump public caching at this point.此时您可能还必须转储公共缓存。 The only general purpose header that changes between the user allowed to look at the sensitive information and the user who isn't?在允许查看敏感信息的用户和不允许查看敏感信息的用户之间进行更改的唯一通用 header? That's the authorization header, and there's no "Vary" mechanism on authorization.那就是授权header,授权上没有“Vary”机制。

You've also got something of a challenge for the user who is making changes to the sensitive copy, but wants to now review the public copy (to make sure nothing leaked? or to make sure that the publicly visible changes took hold?)对于正在对敏感副本进行更改但想要现在查看公共副本的用户来说,您也面临一些挑战(以确保没有泄漏?或确保公开可见的更改生效?)

There's no general purpose header for "show me the public version", so either you need to use a non standard header (which general purpose components will ignore), or you need to try standardizing something and then driving adoption by the implementors of general purpose components.没有通用 header 用于“向我展示公共版本”,因此您需要使用非标准 header(通用组件将忽略),或者您需要尝试标准化某些东西,然后推动通用实现者采用成分。 It's doable (PATCH happened, after all) but it's a lot of work.这是可行的(毕竟发生了补丁),但工作量很大。


The other trick you can try is to play games with Content-Type and the Accept header -- perhaps clients use something normal for the public version (ex application/json), and a specialized type for the sensitive version (application/prs.example-sensitive+json).您可以尝试的另一个技巧是使用 Content-Type 和 Accept header 玩游戏——也许客户使用公共版本(例如 application/json)的正常类型,以及敏感版本的专用类型(application/prs.example -敏感+json)。

That would allow the origin server to use the Vary header to indicate that the response is only suitable if the same accept headers are used.这将允许源服务器使用Vary header 来指示响应仅适用于使用相同的接受标头时。

Once again, general purpose components aren't going to know about your bespoke content-type, and are never going to ask for it.再一次,通用组件不会知道您的定制内容类型,也永远不会要求它。

The standardization route really isn't going to help you here, because the thing you really need is that clients discriminate between the two modes, where general purpose components today are trying to use that channel to advertise all of the standardized representations that they can handle.标准化路线在这里确实对您没有帮助,因为您真正需要的是客户端区分这两种模式,今天的通用组件正试图使用该渠道来宣传他们可以处理的所有标准化表示.

I don't think this actually gets you anywhere that you can't fake more easily with a bespoke header.我不认为这实际上可以让您使用定制的 header 更容易伪造。


REST leans heavily into the idea of using readily standardizable forms; REST 非常倾向于使用易于标准化的 forms 的想法; if you think this is a general problem that could potentially apply to all resources in the world, then a header is the right way to go.如果您认为这是一个可能适用于世界上所有资源的普遍问题,那么 header 是 go 的正确方法。 So a reasonable approach would be to try a custom header, and get a bunch of experience with it, then try writing something up and getting everybody to buy in.因此,一个合理的方法是尝试定制 header,并获得大量经验,然后尝试编写一些东西并让每个人都购买。

If you want something that just works with the out of the box web that we have today, use two different URI and move on to solving important problems.如果您想要一些仅适用于我们今天拥有的开箱即用 web 的东西,请使用两个不同的 URI 并继续解决重要问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM