Сброс пароля RESTful

107

Как правильно структурировать ресурс RESTful для сброса пароля?

Этот ресурс предназначен для сброса пароля для тех, кто потерял или забыл свой пароль. Он аннулирует их старый пароль и отправляет им пароль по электронной почте.

У меня есть два варианта:

POST /reset_password/{user_name}

или...

POST /reset_password
   -Username passed through request body

Я уверен, что запрос должен быть POST. Я менее уверен, что выбрал подходящее имя. И я не уверен, следует ли передавать user_name через URL-адрес или тело запроса.

Крис Датроу
источник

Ответы:

54

ОБНОВЛЕНИЕ: (далее комментарий ниже)

Я бы пошел примерно так:

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Операция обновляет адресуемый элемент коллекции.

Даниэль Вассалло
источник
10
Я согласен с вашим обновленным решением (POST). Запросы PUT должны быть идемпотентными (т. Е. Повторяющиеся запросы не должны влиять на результат). Это не относится к запросам POST.
Росс Макфарлейн
16
Я бы сменил reset_password на password_reset
Ричард Кноп,
9
Подождите, ребята ... разве это не позволит ЛЮБОМУ сбрасывать чей-то пароль? Как, если это для кого-то, кто забыл текущий пароль, затронутый пользователь не может быть аутентифицирован с помощью текущего пароля. По сути, это означает, что этот API вообще не может принимать какой-либо пароль, что позволяет любому сбросить чей-то пароль, а если API вернет его, даже получить любой известный пароль пользователя ??? Или я что-то
упускаю
39
Проблема с / user / {id} / password и т.п. в том, что вы можете не знать "id" пользователя. Вы бы знали их «имя пользователя», «электронную почту» или «телефон», но не «идентификатор».
coolaj86
17
Основной недостаток этого подхода заключается в том, что он предполагает, что вы уже знаете идентификатор пользователя. Это будет верно в некоторых обстоятельствах, но как это сделать тогда, когда имя пользователя или идентификатор пользователя неизвестны, если пользователю нужно только указать адрес электронной почты для сброса.
Alappin
95

Неаутентифицированные пользователи

Мы делаем 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"
}
Уилт
источник
В первом абзаце вы говорите PUT, но в приведенном ниже примере говорится DELETE. Какая точная?
jpierson
Это раскрывает адрес электронной почты в URL-адресе, что было бы более безопасно для данных JSON.
Дуг Домени
@DougDomeny Да, вероятно, лучше было бы отправить электронное письмо в виде данных json. Я добавил это к ответу как альтернативный более безопасный вариант, иначе решение может быть таким же.
Уилт
@Wilt: Разве это не была бы операция PATCH? PUT требует отправки полного ресурса
j10
1
@jitenshah Хороший вопрос. Когда я писал это, я думал, что PUT будет лучше, но не помню, почему. Я согласен с вашими рассуждениями, что патч может быть более подходящим для этого случая.
Уилт
18

Давайте на секунду перейдем к Uber-RESTful. Почему бы не использовать действие DELETE для пароля, чтобы вызвать сброс? Имеет смысл, не так ли? В конце концов, вы фактически отказываетесь от существующего пароля в пользу другого.

Это означает, что вы бы сделали:

DELETE /users/{user_name}/password

Теперь два больших предостережения:

  1. HTTP DELETE должен быть идемпотентным (модное слово для выражения «ничего страшного, если вы сделаете это несколько раз»). Если вы делаете стандартные вещи, например, отправляете электронное письмо «Сброс пароля», то у вас могут возникнуть проблемы. Вы можете обойти это, пометив пользователя / пароль логическим флагом «Сброс». При каждом удалении вы проверяете этот флаг; если он не установлен , то вы можете сбросить пароль и отправить по электронной почте. (Обратите внимание, что наличие этого флага может иметь и другое применение.)

  2. Вы не можете использовать HTTP DELETE через форму , поэтому вам придется сделать вызов AJAX и / или туннелировать DELETE через POST.

Крейг Уокер
источник
9
Интересная идея. Однако я не считаю, DELETEчто здесь хорошо. Полагаю, вы заменили бы пароль случайно сгенерированным, так что это DELETEможет ввести вас в заблуждение. Я предпочитаю Create (POST) new reset_password action, где существительное (ресурс), над которым вы будете действовать, - это "действие reset_password". Это также хорошо подходит для отправки электронных писем, поскольку действие инкапсулирует все эти детали более низкого уровня. POSTне идемпотентный.
Daniel Vassallo
Мне нравится предложение. Проблема 1 может быть решена с помощью условных запросов, т.е. HEAD, который отправляет заголовки ETag + DELETE и If-Match. Если кто-то попытается удалить пароль, который больше не активен, он получит 412.
Whiskeysierra
1
Я бы избегал DELETE. Вы обновляете, поскольку тот же объект / концепция получит новое значение. Но на самом деле, обычно это происходит даже не сейчас, а только после отправки нового пароля в другом запросе позже (после письма со сбросом пароля). В настоящее время никто не отправляет новый пароль по почте, а только токен для его сброса в новом запросе с данный токен, верно?
antonio.fornie
11
Что, если пользователь помнит свой пароль сразу после выполнения запроса на сброс? Что насчет того, что какой-то бот пытается сбросить случайные учетные записи? В таком случае пользователю должно быть разрешено игнорировать электронное письмо для сброса (в электронном письме должно быть написано об этом), то есть ваша система не должна удалять или обновлять пароли сама по себе.
Максим Лаваль
3
@MaximeLaval Это очень хороший аргумент. На самом деле вы «создаете запрос на сброс», который будет POST.
Крейг Уокер
12

Часто вы не хотите удалять или уничтожать существующий пароль пользователя при первоначальном запросе, поскольку это могло быть инициировано (непреднамеренно или намеренно) пользователем, не имеющим доступа к электронной почте. Вместо этого обновите токен сброса пароля в записи пользователя и отправьте его по ссылке, содержащейся в электронном письме. Щелчок по ссылке подтвердит, что пользователь получил токен и хочет обновить свой пароль. В идеале это также должно быть связано со временем.

Действие RESTful в этом случае будет POST: запуск действия create на контроллере PasswordResets. Само действие обновит токен и отправит электронное письмо.

Марк Свардстрем
источник
9

На самом деле я ищу ответ, не собираясь его давать, но "reset_password" звучит для меня неправильно в контексте REST, потому что это глагол, а не существительное. Даже если вы говорите, что выполняете существительное «сбросить действие» - используя это обоснование, все глаголы являются существительными.

Кроме того, кому-то, ищущему тот же ответ, могло и не приходить в голову, что вы можете получить имя пользователя через контекст безопасности и не должны вообще отправлять его через URL-адрес или тело, что заставляет меня нервничать.

орбиш
источник
4
Возможно, reset-passwordзвучит как глагол, но вы можете легко перевернуть его ( password-reset), чтобы превратить его в существительное. И если вы смоделировали свое приложение с помощью Event Sourcing или даже если у вас просто есть какой-либо вид аудита, имеет смысл, что у вас действительно будет реальный объект с этим именем и может даже разрешить GET для пользователей или администраторов. история (очевидно, маскируя текст пароля). Совершенно не заставляет меня нервничать. А что касается автоматического выбора имени пользователя на стороне сервера - вы можете, но как тогда справляться с такими вещами, как администрирование / олицетворение?
Aaronaught
1
Нет ничего плохого в использовании глагола в REST. Пока он используется в соответствующих местах. Я думаю, что это скорее контроллер, чем ресорс, и мне reset-passwordудалось хорошо описать его эффекты.
Андерс Остман
6

Я думаю, что лучше было бы:

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)

Относительно предоставления данных:

  • Чтобы сбросить текущий пароль

    • электронная почта должна быть дана в теле.
  • Создать новый пароль (после сброса)

    • новый пароль, код активации и emailID должны быть указаны в теле.
  • Чтобы обновить пароль (для вошедшего в систему пользователя)

    • старый пароль, новый пароль должен быть указан в теле.
    • UserId в Params.
    • Токен аутентификации в заголовках.
коды снукер
источник
2
Как отмечалось в других ответах, «DELETE / api / v1 / account / password» - плохая идея, так как любой может сбросить любой пароль.
maximedupre
Нам нужен зарегистрированный адрес электронной почты, чтобы сбросить пароль. Шансы узнать идентификатор электронной почты неизвестного пользователя очень мрачны, если у нас не работает такой сайт, как Facebook, и у нас есть тонны идентификаторов электронной почты, собранные любыми способами. Тогда политика безопасности будет определена соответствующим образом. Что вы предлагаете для сброса пароля?
codenooker 02
5

Следует принять во внимание несколько соображений:

Сброс пароля не идемпотентен

Изменение пароля влияет на данные, используемые в качестве учетных данных для его выполнения, что в результате может сделать недействительными будущие попытки, если запрос будет просто повторяться дословно, в то время как сохраненные учетные данные изменились. Например, если для разрешения изменения используется временный токен сброса, как это обычно бывает в ситуации с забытым паролем, срок действия этого токена должен истечь после успешной смены пароля, что снова аннулирует дальнейшие попытки репликации запроса. Таким образом, RESTful-подход к смене пароля кажется более подходящей задачей, POSTчемPUT .

Идентификатор или адрес электронной почты в загрузке данных, вероятно, избыточны

Хотя это не противоречит REST и может иметь какое-то специальное назначение, часто нет необходимости указывать идентификатор или адрес электронной почты для сброса пароля. Подумайте, зачем вам указывать адрес электронной почты как часть данных для запроса, который должен так или иначе проходить аутентификацию? Если пользователь просто меняет свой пароль, для этого ему необходимо пройти аутентификацию (с помощью имени пользователя: пароля, электронной почты: пароля или токена доступа, предоставленного через заголовки). Следовательно, с этого шага у нас есть доступ к их учетной записи. Если бы они забыли свой пароль, им был бы предоставлен временный токен сброса (по электронной почте), который они могут использовать специально в качестве учетных данных для выполнения изменения. И в этом случае аутентификации с помощью токена должно быть достаточно для идентификации их учетной записи.

Принимая во внимание все вышеперечисленное, вот что я считаю правильной схемой для смены пароля RESTful:

Method: POST
url: /v1/account/password
Access Token (via headers): pwd_rst_token_b3xSw4hR8nKWE1d4iE2s7JawT8bCMsT1EvUQ94aI
data load: {"password": "This 1s My very New Passw0rd"}
Майкл Экока
источник
Утверждение, что заполнитель требует внеполосной информации, не совсем верно. Специальный тип мультимедиа может описывать синтаксис и семантику определенных элементов запроса или ответа. Таким образом, тип мультимедиа может определять, что URI, содержащийся в определенном поле, может определять заполнитель для определенных данных, а семантика дополнительно определяет, что закодированное электронное письмо пользователя или то, что не должно быть включено вместо заполнителя. Клиенты и серверы, поддерживающие этот тип носителя, по-прежнему будут соответствовать принципам архитектуры RESTful.
Роман Воттнер
1
Что касается POSTсравнения PUT RFC 7231, указывается, что частичное обновление может быть достигнуто за счет перекрытия данных двух ресурсов, но сомнительно /v1/account/password, действительно ли что-то подобное действительно компенсирует хороший ресурс. Как POSTи швейцарский армейский нож в Интернете, который можно использовать, если ни один из других методов невозможен, рассмотрение PATCHтакже может быть выбором для установки нового пароля.
Роман Воттнер 06
как насчет URL-адреса для запроса сброса пароля, когда они не знают своего пароля?
Дэн Картер
2

У меня не было бы чего-то, что меняет пароль и отправляет им новый, если вы решите использовать метод / users / {id} / password и будете придерживаться вашей идеи, что запрос является собственным ресурсом. т.е. / user-password-request / - это ресурс и используется PUT, информация о пользователе должна быть в теле. Я бы не стал менять пароль, я бы отправил пользователю электронное письмо, которое содержит ссылку на страницу, содержащую request_guid, которую можно передать вместе с запросом на POST / user / {id} / password /? Request_guid = ххххх

Это изменит пароль, и это не позволит кому-либо атаковать пользователя, запрашивая смену пароля.

Кроме того, первоначальный запрос PUT может завершиться ошибкой, если есть невыполненный запрос.

велосипеды
источник
0

Мы обновляем пароль зарегистрированного пользователя PUT / v1 / users / password - идентифицируем идентификатор пользователя с помощью AccessToken.

Обмен идентификатора пользователя небезопасен. Restful API должен идентифицировать пользователя с помощью AccessToken, полученного в HTTP-заголовке.

Пример в Spring-boot

@putMapping(value="/v1/users/password")
public ResponseEntity<String> updatePassword(@RequestHeader(value="Authorization") String token){
/* find User Using token */
/* Update Password*?
/* Return 200 */
}
Даппер Дэн
источник