"这个电子邮件已被注册"应该使用哪个HTTP响应码? 答案:使用HTTP响应码"409 Conflict"。

103

我正在创建一个RESTful API,用于创建用户并强制唯一的电子邮件地址:

成功的POST /users请求:HTTP 201 Created

如果我再次POST相同的电子邮件地址,应该返回什么响应代码?409 Conflict 是否是适当的响应代码?


我认为这不是一个好主意。如果连接是通过某个代理进行的,结果可能是不可预测的。 - Cheery
1
我认为这是个好主意!因为HTTP有状态码,所以要利用它们!当你后来需要调试并且只能访问文件或其他日志文件时,它也非常方便,但如果有HTTP代码... :) - powtac
2
“409 - 冲突” 是这种响应的非常好的选择。 - Brian Kelly
@Cheery 不用担心代理的行为不当,因为这是一个私有API :) 即使我把它公开,我也更愿意尽我自己的一份力量去阻止糟糕的代理存在。 - thatmarvin
1
在我的 REST API 中,我返回 HTTP 422,但对于现有资源(例如版本问题),使用 HTTP 409 更好。 - Artegon
@Artegon HTTP 409并不仅限于版本问题,HTTP规范并没有说明。在我看来,在描述的情况下,这是最合适的响应代码。我在422和409之间做出了同样的选择,并在此处提供了理由:https://dev59.com/NW865IYBdhLWcg3wat6l#70371989 - wombatonfire
7个回答

126

是的,409 是这里最适合的响应代码。即使你成功时可能会返回201,但你仍然在向被描述为集合的资源进行POST操作,并且重复的电子邮件肯定与“资源的当前状态”作为集合存在冲突。如果可能的话,你应该返回一个带有问题描述和帮助解决问题的超链接的响应体。


感谢您确认我的猜测并进行解释!我没有考虑到超链接部分,一定会想出一个好的方法来包含它。 - thatmarvin
当我使用409状态码时,我无法访问响应内容。我改成了200,然后就可以访问属性了。 - Wendel
虽然我理解这个答案的解释,但它仍然与许多主要供应商(Google、Microsoft)处理情况的方式存在冲突(返回2xx)。 - ViBoNaCci

87

我认为返回409冲突并不是一个客户端错误,因此并不是很满意。让我们看看一些大型科技公司是如何处理这种情况的(至少在他们的 WEB 网站 API 中是如何处理的)。

Gmail(Google) 返回一个200 OK和一个 JSON 对象,其中包含指示该电子邮件已经注册的代码。

Facebook 也返回200 OK,但重新渲染内容以显示恢复页面,使用户有选择恢复其现有帐户的选项。

Twitter 通过 AJAX 调用验证现有电子邮件。电子邮件验证资源的响应始终为200 OK。响应包含一个 JSON 对象,其中包含一个标志,指示该电子邮件是否已经注册。

Amazon 与 Facebook 采用相同的方式。返回200 OK并重新渲染内容到通知页面,告知用户该帐户已存在,并提供他/她进一步操作的可能性,例如登录或更改密码。

因此,所有这些 API 都始终返回200 OK,并向客户端/用户呈现额外的内容以恢复其帐户或由响应正文引发的错误消息。


7
我点赞这个观点,因为在我看来,这不是HTTP协议层面的失败,而是应用程序中的逻辑错误。您的应用程序可能允许或不允许重复的电子邮件ID。但是,一次HTTP请求的往返是成功的。因此,在这种情况下,我认为HTTP响应代码应该是200 OK。 - Mahesh
5
我没有直接测试过GMail API,但是谷歌API设计指南似乎指向了409:https://cloud.google.com/apis/design/errors409 ALREADY_EXISTS 客户端尝试创建的资源已经存在。 - kakoma
3
已经使用Google用户管理API https://developers.google.com/admin-sdk/directory/v1/reference/users/insert 进行了测试,对于已存在的电子邮件地址,它返回409。 - kakoma
这种方法有助于避免我们的记录器应用程序中的虚假日志。 - krypru
1
@kakoma 我刚尝试使用我的帐户名创建了一个 Gmail 用户,并返回了“200 OK”,其中正文建议使用另一个帐户名。 - Sven Anton

30

虽然被接受的答案在显示正确任务状态码方面是正确的,但我想要补充说明,您正在引入一种安全漏洞。

如果您为帐户注册返回409,则只是暴露了一个用于帐户枚举的服务。

根据应用程序,api是否公开等情况,即使未创建帐户,您可能仍希望返回201。


2
客户如何区分已注册的电子邮件和成功注册的电子邮件?对于新闻通讯的注册,这个解决方案可能还可以,但对于账户注册则不行。 - Paul Wasilewski
你告诉他他很快会收到一封电子邮件。如果他没有收到邮件,那么他需要重新注册。 - Bart Calixto
2
除了安全漏洞之外,这也是一种隐私漏洞。如果我知道你的电子邮件地址,一个服务不应该向我暴露(通过状态代码或其他方式)你是否已经在他们那里注册。 - akivajgordon
3
我同意,但除了限制IP的请求速率,是否还有其他真正的替代方案呢?在忘记密码页面,可以通过类似以下的消息轻松地解决问题:“如果该账户存在,将发送一封电子邮件包含恢复详细信息。”然而,在注册页面上,如果您没有显示重复账户的错误,并且说用户忘记他们已经使用该电子邮件地址注册过,这将导致可怕的用户体验。如果您显示了一个错误,无论返回的状态代码如何,都可能被枚举帐户详细信息的机器人爬取。 - Clark Brent
如果用户已经注册,您将向他发送一封“欢迎邮件”,并提供一个登录链接。由于他实际上期望收到该邮件,因此用户体验会非常好。 - Bart Calixto

8
为了安全起见,我同意Bart的回答。通常情况下,对于已经存在的资源,状态码409是一个很好的选择。但是,在用户账户/认证/授权等环境中,我倾向于不暴露数据库中现有的用户账户。
当然,这里有其他处理安全问题的机制。如果你不介意暴露一小部分账户,那么你可以在应用程序中添加一个行为,使其在大量来自同一个IP的409事件上返回401或403。
另一种选项(一般情况下)是定义一个自己的状态码,以便有一个与现有标准2xx变体不同的2xx状态码。如果你不想将“已经存在”视为错误处理,这可能是一个选项。然而,这被认为是非标准的,并且在具体例子中和409一样不安全。

3
我经常使用(WebDAV扩展)HTTP 422无法处理的实体

该请求格式良好,但由于语义错误而无法跟随


这部分让我觉得422不合适:“…但无法处理包含的指令”。在这里,指令的语义没有歧义——服务器理解了请求,只是不会满足它。(而且如果可以的话,我宁愿使用RFC 2616代码) - thatmarvin

1
409 => Conflict
That mean.

由于冲突,请求无法完成。 例如,如果父位置中已经存在给定的文件或文件夹名称,则POST ContentStore Folder API无法完成。

-2

注册需要一个不同于成功200代码但不是错误4xx代码的代码。

HTTP响应代码POST资源已存在时所建议的,看看3XX:

302 Found
303 See Other

特别是

根据RFC 7231,如果处理POST的结果等同于现有资源的表示,则可以使用303 See Other。

对于现有地址暴露给枚举机器人的担忧可以通过不同的方式解决,例如验证码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接