RESTful resetowanie hasła

107

Jaki jest właściwy sposób zorganizowania zasobu RESTful do resetowania hasła?

Ten zasób ma służyć do resetowania hasła dla kogoś, kto zgubił lub zapomniał hasła. Unieważnia ich stare hasło i wysyła je e-mailem.

Dwie opcje, które mam, to:

POST /reset_password/{user_name}

lub...

POST /reset_password
   -Username passed through request body

Jestem prawie pewien, że żądanie powinno być POST. Jestem mniej przekonany, że wybrałem odpowiednią nazwę. I nie jestem pewien, czy nazwa_użytkownika powinna być przekazywana za pośrednictwem adresu URL, czy treści żądania.

Chris Dutrow
źródło

Odpowiedzi:

54

AKTUALIZACJA: (dodatkowo komentarz poniżej)

Poszedłbym na coś takiego:

POST /users/:user_id/reset_password

Masz kolekcję użytkowników, w której pojedynczy użytkownik jest określony przez {user_name}. Następnie określasz akcję, na której chcesz wykonać operację, co w tym przypadku jest reset_password. To tak, jakby powiedzieć „Utwórz ( POST) nową reset_passwordakcję dla {user_name}”.


Poprzednia odpowiedź:

Poszedłbym na coś takiego:

PUT /users/:user_id/attributes/password
    -- The "current password" and the "new password" passed through the body

Będziesz mieć dwie kolekcje, kolekcję użytkowników i kolekcję atrybutów dla każdego użytkownika. Użytkownik jest określony przez, :user_ida atrybut jest określony przez password. PUTOperacja aktualizuje skierowane członka kolekcji.

Daniel Vassallo
źródło
10
Zgadzam się ze zaktualizowanym rozwiązaniem (POST). Żądania PUT powinny być idempotentne (tj. Powtarzające się żądania nie powinny wpływać na wynik). Nie dotyczy to żądań POST.
Ross McFarlane
16
Zmieniłbym reset_password na password_reset
Richard Knop
9
Poczekajcie chłopaki ... czy to zasadniczo nie pozwoliłoby KAŻDEMU zresetować czyjeś hasło? Ponieważ jeśli dotyczy to kogoś, kto zapomniał bieżącego hasła, użytkownika, którego dotyczy problem, nie można uwierzytelnić przy użyciu bieżącego hasła. Zasadniczo oznacza to, że ten interfejs API w ogóle nie mógł zaakceptować żadnego hasła - w ten sposób umożliwiając każdemu zresetowanie czyjegoś hasła, a jeśli API je zwróci, nawet zdobędzie hasło dowolnego znanego użytkownika ??? A może czegoś mi brakuje
transient_loop
39
Problem z / user / {id} / password i tym podobnymi polega na tym, że możesz nie znać "id" użytkownika. Znasz ich „nazwę użytkownika”, „e-mail” lub „telefon”, ale nie znasz „identyfikatora”.
coolaj86
17
Podstawową wadą tego podejścia jest to, że zakłada ono, że znasz już identyfikator użytkownika. Będzie to prawdą w niektórych okolicznościach, ale jak to zrobić, gdy nazwa użytkownika lub identyfikator użytkownika nie są znane, jeśli użytkownik musi tylko podać adres e-mail do resetowania.
Alappin
95

Nieuwierzytelnieni użytkownicy

Wykonujemy PUTżądanie na api/v1/account/passwordpunkcie końcowym i wymagamy parametru z odpowiednim adresem e-mail konta, aby zidentyfikować konto, dla którego użytkownik chce zresetować (zaktualizować) hasło:

PUT : /api/v1/account/password?email={[email protected]}

Uwaga: Jak wspomniał @DougDomeny w swoim komentarzu, przekazywanie wiadomości e-mail jako ciągu zapytania w adresie URL stanowi zagrożenie bezpieczeństwa. Parametry GET nie są ujawniane podczas używania https(i httpsdo takich żądań należy zawsze używać odpowiedniego połączenia), ale istnieją inne zagrożenia bezpieczeństwa. Możesz przeczytać więcej na ten temat w tym wpisie na blogu tutaj .

Przekazanie e-maila w treści żądania byłoby bezpieczniejszą alternatywą dla przekazania go jako parametru GET:

PUT : /api/v1/account/password

Treść żądania:

{
    "email": "[email protected]"
}

Odpowiedź ma 202zaakceptowaną odpowiedź, co oznacza:

Żądanie zostało przyjęte do realizacji, ale przetwarzanie nie zostało zakończone. Żądanie może ostatecznie zostać zrealizowane lub nie, ponieważ może zostać odrzucone, gdy faktycznie ma miejsce przetwarzanie. Nie ma możliwości ponownego wysłania kodu stanu z operacji asynchronicznej, takiej jak ta.

Użytkownik otrzyma wiadomość e-mail na adres, [email protected]a przetwarzanie żądania aktualizacji zależy od działań podjętych za pomocą łącza z wiadomości e-mail.

https://example.com/password-reset?token=1234567890

Otwarcie linku z tego e-maila spowoduje przekierowanie do formularza resetowania hasła w aplikacji frontonu, który używa tokena resetowania hasła z linku jako danych wejściowych dla ukrytego pola wejściowego (token jest częścią łącza jako ciąg zapytania). Kolejne pole wejściowe umożliwia użytkownikowi ustawienie nowego hasła. Drugie wejście w celu potwierdzenia nowego hasła zostanie użyte do weryfikacji na interfejsie użytkownika (aby zapobiec literówkom).

Uwaga: w e-mailu możemy również wspomnieć, że jeśli użytkownik nie zainicjował żadnego resetowania hasła, może zignorować wiadomość e-mail i normalnie korzystać z aplikacji z obecnym hasłem

Po przesłaniu formularza z nowym hasłem i tokenem jako danymi wejściowymi nastąpi proces resetowania hasła. Dane formularza zostaną ponownie wysłane z PUTżądaniem, ale tym razem z tokenem i zastąpimy hasło zasobu nową wartością:

PUT : /api/v1/account/password

Treść żądania:

{
    "token":"1234567890",
    "new":"password"
}

Odpowiedź będzie 204brak zawartości odpowiedź

Serwer spełnił żądanie, ale nie musi zwracać treści encji i może chcieć zwrócić zaktualizowane metainformacje. Odpowiedź MOŻE zawierać nowe lub zaktualizowane metainformacje w postaci nagłówków encji, które, jeśli są obecne, POWINNY być skojarzone z żądanym wariantem.

Uwierzytelnieni użytkownicy

W przypadku uwierzytelnionych użytkowników, którzy chcą zmienić swoje hasło, PUTżądanie może zostać zrealizowane natychmiast, bez wiadomości e-mail (konto, dla którego aktualizujemy hasło, jest znane serwerowi). W takim przypadku formularz będzie zawierał dwa pola:

PUT : /api/v1/account/password

Treść żądania:

{
    "old":"password",
    "new":"password"
}
Więdnąć
źródło
W pierwszym akapicie mówisz PUT, ale poniższy przykład mówi DELETE. Która jest dokładna?
jpierson
Spowoduje to ujawnienie adresu e-mail w adresie URL, co byłoby bezpieczniejsze dla danych JSON.
Doug Domeny
@DougDomeny Tak, wysłanie wiadomości e-mail jako danych json prawdopodobnie byłoby lepsze. Dodałem to do odpowiedzi jako alternatywną, bezpieczniejszą opcję, w przeciwnym razie rozwiązanie może być takie samo.
Wilt
@Wilt: Czy to nie byłaby operacja PATCH? PUT wymaga przesłania całego zasobu
j10
1
@jitenshah Słuszna uwaga. Pisząc to myślałem, że PUT będzie lepszy, ale nie pamiętam dokładnie dlaczego. Zgadzam się z twoim rozumowaniem, że łatka może być bardziej odpowiednia w tym przypadku.
Wilt
18

Uzyskajmy Uber-RESTful na sekundę. Dlaczego nie użyć akcji USUŃ dla hasła, aby wywołać reset? To ma sens, prawda? W końcu skutecznie odrzucasz istniejące hasło na rzecz innego.

Oznacza to, że zrobiłbyś:

DELETE /users/{user_name}/password

Teraz dwa duże zastrzeżenia:

  1. HTTP DELETE ma być idempotentne (wymyślne słowo na określenie „nic wielkiego, jeśli robisz to wiele razy”). Jeśli wykonujesz standardowe czynności, takie jak wysyłanie wiadomości e-mail „Resetowanie hasła”, napotkasz problemy. Możesz obejść ten problem, oznaczając użytkownika / hasło logiczną flagą „Czy resetowano”. Przy każdym usunięciu zaznaczasz tę flagę; jeśli nie jest ustawiona , a następnie można zresetować hasło i wysyłać wiadomości e-mail. (Zauważ, że posiadanie tej flagi może mieć również inne zastosowania).

  2. Nie możesz użyć HTTP DELETE przez formularz , więc będziesz musiał wykonać wywołanie AJAX i / lub tunelować DELETE przez POST.

Craig Walker
źródło
9
Ciekawy pomysł. Jednak nie widzę tutaj DELETEodpowiedniego dopasowania. Myślę, że zastąpiłbyś hasło losowo wygenerowanym hasłem, więc DELETEmoże być mylące. Wolę Create (POST) new reset_password action, gdzie rzeczownik (zasób), na którym będziesz działać, to akcja „reset_password”. Pasuje to również do wysyłania e-maili, ponieważ akcja zawiera wszystkie te szczegóły niższego poziomu. POSTnie jest idempotentny.
Daniel Vassallo
Podoba mi się ta propozycja. Problem 1 można rozwiązać za pomocą żądań warunkowych, tj. HEAD, który wysyła ETag + DELETE i nagłówek If-Match. Jeśli ktoś spróbuje usunąć hasło, które nie jest już aktywne, otrzyma 412.
whiskeysierra
1
Unikałbym DELETE. Aktualizujesz, ponieważ ta sama jednostka / koncepcja otrzyma nową wartość. Ale w rzeczywistości zwykle to się nawet nie dzieje, ale dopiero po wysłaniu nowego hasła w późniejszym innym żądaniu (po e-mailu resetującym hasło) - Obecnie nikt nie wysyła nowego hasła pocztą, ale token do zresetowania go w nowym żądaniu z dany token, prawda?
antonio.fornie
11
Co się stanie, jeśli użytkownik zapamięta swoje hasło tuż po wykonaniu żądania resetowania? A co z jakimś botem, który próbuje zresetować losowe konta? W takim przypadku użytkownik powinien mieć możliwość zignorowania wiadomości e-mail dotyczącej resetowania (e-mail powinien o tym mówić), co oznacza, że ​​system nie powinien sam usuwać ani aktualizować haseł.
Maxime Laval
3
@MaximeLaval To bardzo dobra uwaga. Naprawdę „tworzysz żądanie resetowania”, które byłoby POST.
Craig Walker
12

Często nie chcesz usuwać ani niszczyć istniejącego hasła użytkownika przy pierwszym żądaniu, ponieważ mogło to zostać uruchomione (nieumyślnie lub celowo) przez użytkownika, który nie ma dostępu do wiadomości e-mail. Zamiast tego zaktualizuj token resetowania hasła w rekordzie użytkownika i wyślij go w łączu zawartym w wiadomości e-mail. Kliknięcie linku potwierdzałoby, że użytkownik otrzymał token i chciał zaktualizować swoje hasło. W idealnym przypadku byłoby to również wrażliwe na czas.

Akcją RESTful w tym przypadku byłoby POST: wyzwalanie akcji tworzenia na kontrolerze PasswordResets. Sama akcja zaktualizowałaby token i wysłała wiadomość e-mail.

Mark Swardstrom
źródło
9

Właściwie szukam odpowiedzi, nie mam zamiaru jej udzielić - ale „reset_password” brzmi dla mnie źle w kontekście REST, ponieważ jest to czasownik, a nie rzeczownik. Nawet jeśli powiesz, że wykonujesz rzeczownik „resetuj akcję” - używając tego uzasadnienia, wszystkie czasowniki są rzeczownikami.

Ponadto komuś szukającemu tej samej odpowiedzi mogło nie przyjść do głowy, że możesz uzyskać nazwę użytkownika w kontekście bezpieczeństwa i nie musisz w ogóle przesyłać jej przez adres URL lub treść, co mnie denerwuje.

orbfish
źródło
4
Być może reset-passwordbrzmi jak czasownik, ale można go łatwo odwrócić ( password-reset), aby uczynić go rzeczownikiem. A jeśli modelowałeś swoją aplikację przy użyciu funkcji Event Sourcing lub nawet jeśli masz po prostu jakiś rodzaj audytu, sensowne jest, że faktycznie masz prawdziwą jednostkę o tej nazwie i możesz nawet zezwolić na GET dla użytkowników lub administratorów historia (oczywiście maskowanie tekstu hasła). W ogóle mnie to nie denerwuje. A jeśli chodzi o automatyczne pobieranie nazwy użytkownika po stronie serwera - możesz, ale jak radzisz sobie z takimi rzeczami, jak administracja / podszywanie się?
Aaronaught,
1
Nie ma nic złego w używaniu czasownika w REST. Tak długo, jak jest używany w odpowiednich miejscach. Myślę, że to bardziej kontroler niż resorce i reset-passworddobrze opisuję jego efekty.
Anders Östman
6

Myślę, że lepszym pomysłem byłoby:

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)

W zakresie podania danych:

  • Aby zresetować aktualne hasło

    • email należy podać w treści.
  • Tworzenie nowego hasła (po zresetowaniu)

    • w treści należy podać nowe hasło, kod aktywacyjny oraz emailID.
  • Aby zaktualizować hasło (dla zalogowanego użytkownika)

    • stare hasło, w treści należy zamieścić nowe hasło.
    • UserId w Params.
    • Auth Token w nagłówkach.
Codesnooker
źródło
2
Jak skomentowano w innych odpowiedziach, „USUŃ / api / v1 / konto / hasło” to zły pomysł, ponieważ każdy może zresetować hasło.
maximedupre
Potrzebujemy zarejestrowanego adresu e-mail, aby zresetować hasło. Szanse na poznanie identyfikatora e-mail nieznanego użytkownika są bardzo ponure, chyba że prowadzimy witrynę taką jak Facebook i mamy mnóstwo identyfikatorów e-mail zbieranych w jakikolwiek sposób. Następnie zostaną odpowiednio zdefiniowane zasady bezpieczeństwa. Jaka jest Twoja sugestia, aby zresetować czyjeś hasło?
Codesnooker
5

Należy wziąć pod uwagę kilka kwestii:

Resetowanie haseł nie jest idempotentne

Zmiana hasła wpływa na dane używane jako poświadczenia do jej wykonania, co w rezultacie może unieważnić przyszłe próby, jeśli żądanie zostanie po prostu powtórzone dosłownie, podczas gdy zapisane poświadczenia ulegną zmianie. Na przykład, jeśli tymczasowy token resetowania jest używany w celu umożliwienia zmiany, jak to jest zwykle w sytuacji zapomnienia hasła, ten token powinien wygasnąć po pomyślnej zmianie hasła, co ponownie unieważnia dalsze próby replikacji żądania. Dlatego podejście RESTful do zmiany hasła wydaje się być pracą lepiej dostosowaną POSTniż PUT.

Identyfikator lub e-mail w ładowaniu danych jest prawdopodobnie zbędny

Chociaż nie jest to sprzeczne z REST i może mieć jakiś specjalny cel, często nie jest konieczne określanie identyfikatora lub adresu e-mail w celu zresetowania hasła. Pomyśl o tym, dlaczego miałbyś podawać adres e-mail jako część danych do żądania, które ma przejść przez uwierzytelnienie w taki czy inny sposób? Jeśli użytkownik po prostu zmienia swoje hasło, musi się uwierzytelnić, aby to zrobić (poprzez nazwę użytkownika: hasło, e-mail: hasło lub token dostępu podany w nagłówkach). Dlatego na tym etapie mamy dostęp do ich konta. Gdyby zapomnieli hasła, otrzymaliby tymczasowy token resetowania (za pośrednictwem poczty e-mail), którego mogą użyć w szczególności jako poświadczeń do wykonania zmiany. I w tym przypadku uwierzytelnienie za pomocą tokena powinno wystarczyć do zidentyfikowania konta.

Biorąc pod uwagę wszystkie powyższe kwestie, oto, co uważam za właściwy schemat zmiany hasła RESTful:

Method: POST
url: /v1/account/password
Access Token (via headers): pwd_rst_token_b3xSw4hR8nKWE1d4iE2s7JawT8bCMsT1EvUQ94aI
data load: {"password": "This 1s My very New Passw0rd"}
Michael Ekoka
źródło
Stwierdzenie, że symbol zastępczy wymaga informacji poza pasmem, nie jest do końca prawdziwe. Specjalny typ nośnika może opisywać składnię i semantykę niektórych elementów żądania lub odpowiedzi. Jest więc możliwe, aby typ mediów zdefiniował, że URI zawarty w pewnym polu może definiować element zastępczy dla pewnych danych, a semantyka dodatkowo definiuje, że zakodowany e-mail użytkownika lub co nie powinno być zawarte zamiast symbolu zastępczego. Klienci i serwery szanujące ten typ nośnika nadal będą zgodne z zasadami architektury RESTful.
Roman Vottner
1
Odnośnie POSTdo PUT RFC 7231 określa, że ​​częściową aktualizację można osiągnąć poprzez nakładanie się danych z dwóch zasobów, ale wątpliwe jest, czy coś takiego /v1/account/passwordnaprawdę nadrabia dobry zasób. Podobnie jak w przypadku POSTsieci swiss-army-kniff, której można użyć, jeśli żadna z innych metod nie jest wykonalna, rozważenie PATCHrównież może być wyborem dla ustawienia nowego hasła.
Roman Vottner
A co z adresem URL, aby zażądać resetowania hasła, gdy nie znają swojego hasła?
dan carter
2

Nie chciałbym mieć czegoś, co zmienia hasło i wyśle ​​im nowe, jeśli zdecydujesz się użyć metody / users / {id} / password i trzymasz się swojego pomysłu, że żądanie jest własnym zasobem. tj. / user-password-request / jest zasobem i używa PUT, informacje o użytkowniku powinny znajdować się w treści. Nie chciałbym jednak zmieniać hasła, wyślę e-mail do użytkownika, który zawiera link do strony zawierającej request_guid, który można przekazać wraz z żądaniem do POST / user / {id} / password /? Request_guid = xxxxx

To spowodowałoby zmianę hasła i nie pozwala komuś nakłonić użytkownika do żądania zmiany hasła.

Ponadto początkowe PUT może się nie powieść, jeśli istnieje oczekujące żądanie.

bpeikes
źródło
0

Aktualizujemy hasło zalogowanego użytkownika PUT / v1 / users / password - zidentyfikuj identyfikator użytkownika za pomocą AccessToken.

Wymiana identyfikatora użytkownika nie jest bezpieczna. Restful API musi identyfikować użytkownika za pomocą AccessToken otrzymanego w nagłówku HTTP.

Przykład w butach wiosennych

@putMapping(value="/v1/users/password")
public ResponseEntity<String> updatePassword(@RequestHeader(value="Authorization") String token){
/* find User Using token */
/* Update Password*?
/* Return 200 */
}
Dapper Dan
źródło