Answers:
更新:(以下进一步评论)
我会去这样的事情:
POST /users/:user_id/reset_password
您有一个用户集合,其中单个用户由指定{user_name}
。然后,您将指定要执行的操作,在这种情况下为reset_password
。这就像说“为(创建(POST
)一个新reset_password
动作){user_name}
”。
先前的答案:
我会去这样的事情:
PUT /users/:user_id/attributes/password
-- The "current password" and the "new password" passed through the body
您将有两个集合,一个用户集合,以及每个用户的一个属性集合。用户由指定,:user_id
属性由指定password
。该PUT
操作将更新集合中已寻址的成员。
我们PUT
在api/v1/account/password
端点上进行请求,并要求使用带有相应帐户电子邮件的参数来标识用户要重置(更新)密码的帐户:
PUT : /api/v1/account/password?email={email@example.com}
注意: 正如@DougDomeny在他的评论中提到的那样,将电子邮件作为URL中的查询字符串传递会带来安全风险。使用时不会暴露GET参数https
(您应该始终https
对此类请求使用适当的连接),但是还涉及其他安全风险。您可以在此博客文章中阅读有关此主题的更多信息。
在请求正文中传递电子邮件将是将其作为GET参数传递的一种更安全的选择:
PUT : /api/v1/account/password
要求正文:
{
"email": "email@example.com"
}
该响应具有202
可接受的响应含义:
该请求已被接受以进行处理,但是处理尚未完成。该请求最终可能会执行,也可能不会最终执行,因为在实际进行处理时可能会不允许该请求。没有从这种异步操作中重新发送状态代码的功能。
用户将在处收到一封电子邮件,email@example.com
并根据对电子邮件中的链接所采取的操作来处理更新请求。
https://example.com/password-reset?token=1234567890
打开此电子邮件中的链接将定向到前端应用程序上的重置密码表单,该表单使用链接中的重置密码令牌作为隐藏输入字段的输入(令牌是链接的一部分,作为查询字符串)。另一个输入字段允许用户设置新密码。确认新密码的第二个输入将用于前端的验证(防止输入错误)。
注意: 在电子邮件中,我们还可以提及的是,如果用户未初始化任何密码重置,则他/她可以忽略电子邮件并继续使用当前密码继续使用该应用程序
当使用新密码和令牌作为输入表单时,将执行重置密码过程。表单数据将PUT
再次发送请求,但这次包括令牌,我们将用新值替换资源密码:
PUT : /api/v1/account/password
要求正文:
{
"token":"1234567890",
"new":"password"
}
响应将为204
无内容响应
服务器已完成请求,但不需要返回实体,可能要返回更新的元信息。响应可以包括实体标题形式的新的或更新的元信息,如果存在,则应与所请求的变量相关联。
对于想要更改密码的经过身份验证的用户,PUT
可以立即执行该请求,而无需发送电子邮件(服务器知道我们要为其更新密码的帐户)。在这种情况下,表单将提交两个字段:
PUT : /api/v1/account/password
要求正文:
{
"old":"password",
"new":"password"
}
让我们获得uber-RESTful一秒钟。为什么不对密码使用DELETE操作来触发重置?有道理,不是吗?毕竟,您实际上是在抛弃现有密码,转而使用另一个密码。
这意味着您可以:
DELETE /users/{user_name}/password
现在,有两个重要警告:
HTTP DELETE应该是幂等的(一个花哨的词,说“如果多次执行,没什么大不了的”)。如果您正在执行标准操作(例如发送“密码重置”电子邮件),那么您将遇到问题。您可以通过使用布尔值“正在重置”标记用户/密码来解决此问题。每次删除时,都检查此标志;如果未设置,则可以重置密码并发送电子邮件。(请注意,具有此标志可能还会有其他用途。)
您不能通过form使用HTTP DELETE,因此您必须进行AJAX调用和/或通过POST隧道化DELETE。
DELETE
这里不合适。我猜您将用随机生成的密码代替该密码,因此DELETE
可能会引起误解。我更喜欢使用Create (POST) new reset_password action
,其中要作用的名词(资源)是“ reset_password操作”。这也适合发送电子邮件,因为该操作封装了所有这些较低级别的详细信息。POST
不是幂等的。
通常,您不想在初始请求上删除或销毁用户的现有密码,因为该密码可能是由无法访问电子邮件的用户(无意或有意)触发的。相反,请更新用户记录上的重置密码令牌,然后通过电子邮件中包含的链接发送该令牌。单击该链接将确认用户已收到令牌并希望更新其密码。理想情况下,这也是时间敏感的。
在这种情况下,RESTful操作将是POST:在PasswordResets控制器上触发create操作。动作本身将更新令牌并发送电子邮件。
我实际上是在寻找答案,并不是要提供一个答案-但在REST环境中,“ reset_password”对我来说听起来是错误的,因为它是动词,而不是名词。即使您说您正在执行“重置动作”名词-使用此对齐方式,所有动词都是名词。
同样,有人搜索相同的答案可能不会发生,您可能可以通过安全上下文获取用户名,而根本不必通过url或正文发送用户名,这使我感到紧张。
reset-password
听起来像一个动词,但是你可以很容易地扭转它(password-reset
),使之成为名词。而且,如果您使用事件源对应用程序进行建模,或者即使您仅进行了某种形式的审核,也可以说您实际上拥有一个具有此名称的真实实体,甚至可以允许用户或管理员查看GET历史记录(显然会掩盖密码文本)。一点都不让我紧张。至于在服务器端自动获取用户名,您可以,但是您如何处理管理/模拟这样的事情呢?
reset-password
设法很好地描述了它的作用。
我认为更好的主意是:
DELETE /api/v1/account/password - To reset the current password (in case user forget the password)
POST /api/v1/account/password - To create new password (if user has reset the password)
PUT /api/v1/account/{userId}/password - To update the password (if user knows is old password and new password)
关于提供数据:
重设当前密码
创建新密码(重置后)
更新密码(对于已登录用户)
有一些注意事项:
密码更改会影响用作凭据的数据,因此,如果在存储的凭据已更改的情况下简单逐字重复请求,则可能会使以后的尝试无效。例如,如果使用临时重置令牌来允许更改(如在忘记密码的情况下所惯用的那样),则在成功更改密码后该令牌应过期,这再次使复制请求的进一步尝试无效。因此,使用RESTful更改密码的方法似乎POST
比做得更好PUT
。
尽管这与REST无关,并且可能有一些特殊用途,但通常不必指定ID或电子邮件地址来重置密码。想一想,为什么您将电子邮件地址作为数据的一部分提供给应该以某种方式通过身份验证的请求?如果用户只是在更改密码,则他们需要进行身份验证(通过用户名:密码,电子邮件:密码或通过标头提供的访问令牌)。因此,我们可以从该步骤访问他们的帐户。如果他们忘记了密码,则会获得一个临时重置令牌(通过电子邮件),可以将它们专门用作执行更改的凭据。在这种情况下,通过令牌进行身份验证应该足以识别其帐户。
考虑到上述所有因素,以下是我认为是RESTful密码更改的正确方案:
Method: POST
url: /v1/account/password
Access Token (via headers): pwd_rst_token_b3xSw4hR8nKWE1d4iE2s7JawT8bCMsT1EvUQ94aI
data load: {"password": "This 1s My very New Passw0rd"}
如果您决定使用/ users / {id} / password方法,并且坚持您的想法,即请求是其自身的资源,那么我不会更改密码并向其发送新密码。即/ user-password-request /是资源,并且使用PUT,用户信息应在正文中。不过,我不会更改密码,我会向用户发送一封电子邮件,该电子邮件包含指向包含request_guid的页面的链接,该链接可以与对POST / user / {id} / password /?request_guid =的请求一起传递xxxxxx
这将更改密码,并且不允许某人通过请求更改密码来欺骗用户。
另外,如果有未解决的请求,则初始PUT可能会失败。
我们更新记录的用户密码PUT / v1 / users / password-使用AccessToken标识用户ID。
交换用户ID是不安全的。Restful API必须使用HTTP标头中接收的AccessToken标识用户。
spring-boot中的示例
@putMapping(value="/v1/users/password")
public ResponseEntity<String> updatePassword(@RequestHeader(value="Authorization") String token){
/* find User Using token */
/* Update Password*?
/* Return 200 */
}