RESTfully zaprojektować / zalogować lub / zarejestrować zasoby?

122

Projektowałem aplikację internetową, a potem przestałem myśleć o tym, jak mój interfejs API powinien być zaprojektowany jako usługa internetowa RESTful. Na razie większość moich identyfikatorów URI ma charakter ogólny i może dotyczyć różnych aplikacji internetowych:

GET  /logout   // destroys session and redirects to /
GET  /login    // gets the webpage that has the login form
POST /login    // authenticates credentials against database and either redirects home with a new session or redirects back to /login
GET  /register // gets the webpage that has the registration form
POST /register // records the entered information into database as a new /user/xxx
GET  /user/xxx // gets and renders current user data in a profile view
POST /user/xxx // updates new information about user

Mam wrażenie, że robię dużo źle po tym, jak poszperałem w SO i Google.

Zaczynając od /logout, być może, ponieważ tak naprawdę GETnic nie robię - bardziej odpowiednie może być POSTżądanie /logout, zniszczenie sesji, a następnie GETprzekierowanie. I czy /logouttermin powinien pozostać?

A co z /logini /register. Mogę zmienić /registersię /registration, ale to nie zmienia zasadniczo jak mój serwis działa - jeśli to ma głębsze problemy.

Teraz zauważam, że nigdy nie udostępniam /userzasobu. Być może można to jakoś spożytkować. Na przykład weź użytkownika myUser:

foo.com/user/myUser

lub

foo.com/user

Użytkownik końcowy nie wymaga dodatkowej szczegółowości w identyfikatorze URI. Jednak który z nich jest bardziej atrakcyjny wizualnie?

Zauważyłem tutaj kilka innych pytań dotyczących SO na temat tego biznesu REST, ale naprawdę byłbym wdzięczny za wskazówki dotyczące tego, co tutaj przedstawiłem, jeśli to możliwe.

Dzięki!

AKTUALIZACJA:

Chciałbym również uzyskać opinie na temat:

/user/1

vs

/user/myUserName
Qcom
źródło

Odpowiedzi:

63

Jedna rzecz wyróżnia się w szczególności jako niespełniająca REST: użycie żądania GET do wylogowania.

(z http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods )

Niektóre metody (na przykład HEAD, GET, OPTIONS i TRACE) są zdefiniowane jako bezpieczne, co oznacza, że ​​są przeznaczone tylko do wyszukiwania informacji i nie powinny zmieniać stanu serwera. Innymi słowy, nie powinny mieć skutków ubocznych, poza stosunkowo nieszkodliwymi skutkami, takimi jak logowanie, buforowanie, wyświetlanie banerów reklamowych lub zwiększanie licznika w sieci. […]

[... H] andling [żądań GET] przez serwer nie jest w żaden sposób ograniczony technicznie. Dlatego nieostrożne lub celowe programowanie może spowodować nietrywialne zmiany na serwerze. Jest to odradzane, ponieważ może powodować problemy z buforowaniem sieci Web, wyszukiwarkami i innymi automatycznymi agentami [...]

Jeśli chodzi o wylogowywanie się i przekierowywanie, możesz wysłać wiadomość do swojego identyfikatora URI wylogowania z odpowiedzią 303 przekierowującą na stronę po wylogowaniu.

http://en.wikipedia.org/wiki/Post/Redirect/Get

http://en.wikipedia.org/wiki/HTTP_303

Edytuj, aby rozwiązać problemy związane z projektem adresu URL:

„Jak zaprojektować zasoby?” to dla mnie ważne pytanie; „Jak zaprojektować adresy URL?” należy wziąć pod uwagę w dwóch obszarach:

Adresy URL, które zobaczą użytkownicy, nie powinny być zbyt brzydkie i sensowne, jeśli to możliwe; Jeśli chcesz, aby pliki cookie były wysyłane w żądaniach do niektórych zasobów, ale nie do innych, będziesz chciał uporządkować swoje ścieżki i ścieżki plików cookie.

Jeśli JRandomUserchcesz spojrzeć na swój własny profil i chcesz, aby adres URL był ładniejszy niż foo.com/user/JRandomUserlub foo.com/user/(JRandom's numeric user id here), możesz utworzyć oddzielny adres URL tylko po to, aby użytkownik mógł przeglądać własne informacje:

GET foo.com/profile /*examines cookies to figure out who 
                     * is logged in (SomeUser) and then 
                     * displays the same response as a
                     * GET to foo.com/users/SomeUser.
                     */

Wolałbym twierdzić, że ignorancja jest dużo łatwiejsza niż mądrość na ten temat, ale oto kilka rozważań dotyczących projektowania zasobów:

  1. Konsument: jakie zasoby mają być przeglądane bezpośrednio w przeglądarce, ładowane przez XHR lub dostępne dla innego rodzaju klienta?
  2. Dostęp / tożsamość: czy odpowiedź zależy od plików cookie lub stron odsyłających?
ellisbben
źródło
1
Świetna odpowiedź, dzięki! Gdybym miał zaimplementować twoją oddzielną sugestię adresu URL ( GET foo.com/profile/), czy byłoby to częścią, jak sugeruje momo, warstwy prezentacji? Innymi słowy, co dokładnie powinna GETzwrócić ta prośba? Strona internetowa czy jakiś JSON?
Qcom
2
Ach, myślę, że teraz rozumiem. Odpowiedź Momo naprawdę wyjaśniła sprawę. Więc relaksującego API jest skonstruowany tak, aby umożliwić wielu platform GET, POST, PUToraz DELETEzasobów. Strona internetowa to po prostu kolejna platforma uzyskująca dostęp do API. Innymi słowy, projekt adresu URL witryny jest zupełnie inny niż projekt RESTful API. Proszę powiedz mi, czy nadal się mylę haha.
Qcom
Tak, uczyń swój REST API jednym zestawem adresów URL, a swoją witryną innym zestawem. Następnie adres URL Twojej witryny powinien zwracać odpowiedni kod HTML + Javascript, aby strona wysyłała odpowiednie XmlHttpRequests do adresów URL interfejsu API, aby działać jako klient.
ellisbben
129

RESTful może służyć jako wskazówka przy tworzeniu adresów URL, a także możesz tworzyć sesje i zasoby użytkowników :

  • GET /session/new pobiera stronę internetową, na której znajduje się formularz logowania
  • POST /session uwierzytelnia poświadczenia w bazie danych
  • DELETE /session niszczy sesję i przekierowuje do /
  • GET /users/new pobiera stronę internetową, na której znajduje się formularz rejestracyjny
  • POST /users zapisuje wprowadzone informacje do bazy danych jako nowy / użytkownik / xxx
  • GET /users/xxx // pobiera i renderuje aktualne dane użytkownika w widoku profilu
  • POST /users/xxx // aktualizuje nowe informacje o użytkowniku

Mogą to być liczby mnogie lub pojedyncze (nie jestem pewien, który z nich jest poprawny). Zwykle używałem /usersdla strony indeksu użytkownika (zgodnie z oczekiwaniami) i/sessions aby zobaczyć, kto jest zalogowany (zgodnie z oczekiwaniami).

Użycie nazwy w adresie URL zamiast liczby ( /users/43vs./users/joe ) jest zwykle powodowane chęcią bycia bardziej przyjaznym dla użytkowników lub wyszukiwarek, a nie wymaganiami technicznymi. Jedno i drugie jest w porządku, ale radzę być konsekwentny.

Myślę, że jeśli pójdziesz z rejestracją / logowaniem / wylogowaniem lub sign(in|up|out), nie działa to tak dobrze z spokojną terminologią.

ndp
źródło
6
Niesamowite! Podoba mi się sposób, w jaki nazwałeś te zasoby; to jest całkiem czyste. Chociaż, z tego, co słyszałem, nie dołącza się /newdo GET /session/nie RESTful? Słyszałem, że czasowniki są zazwyczaj lewej do czasowników HTTP ( GET, POSTitp).
Qcom
2
@Zach nowy nie jest czasownikiem. W tym przypadku jest to podźródło sesji.
Kugel
Jak określić, którą sesję usunąć w DELETE / session? Curl nie wysyła plików cookie ani żadnych parametrów w żądaniu DELETE. Zakładam - po prostu użyć DELETE / session / sessionId? Inną kwestią jest to, jak zwrócić identyfikator sesji w POST / sesji i w jakim formacie.
Tvaroh
9
Odpoczynek rzeczywiście jest sposobem na unieszczęśliwienie i marnowanie czasu na rzeczy, które w ogóle nie mają znaczenia.
Jian Chen
6
Osobiście nie podoba mi się pomysł posiadania tras, które zwracają formularz (/ nowy). Zrywa to separację między widokiem a logiką biznesową. Powiedziałeś, że bez / nowych tras sugerowana jedna wygląda idealnie.
Scadge
60

Sesje nie są RESTful

  • Tak, wiem. Robi się to zwykle z OAuth, ale tak naprawdę sesje nie są RESTful. Nie powinieneś mieć zasobu / login / logout głównie dlatego, że nie powinieneś mieć sesji.

  • Jeśli masz zamiar to zrobić, zrób to RESTful. Zasoby to rzeczowniki, a / login i / logout nie są rzeczownikami. Poszedłbym z / sesją. To sprawia, że ​​tworzenie i usuwanie jest bardziej naturalną czynnością.

  • POST vs. GET dla sesji jest łatwe. Jeśli wysyłasz użytkownika / hasło jako zmienne, użyłbym POST, ponieważ nie chcę, aby hasło było wysyłane jako część URI. Pojawi się w dziennikach i prawdopodobnie zostanie odsłonięty przez drut. Istnieje również ryzyko, że oprogramowanie ulegnie awarii w przypadku ograniczeń GET argumentów.

  • Zwykle używam uwierzytelniania podstawowego lub bez uwierzytelniania z usługami REST.

Tworzenie użytkowników

  • To jeden zasób, więc nie powinieneś potrzebować / rejestrować.

    • POST / user - tworzy użytkownika, jeśli żądający nie może określić identyfikatora
    • PUT / user / xxx - Utwórz lub zaktualizuj użytkownika zakładając, że znasz wcześniej identyfikator
    • GET / user - wyświetla x identyfikatory użytkowników
    • GET / user / xxx - pobiera dane użytkownika o identyfikatorze xxx
    • DELETE / user / xxx - Usuń użytkownika o identyfikatorze xxx
  • Jakiego rodzaju identyfikatora użyć, to trudne pytanie. Musisz pomyśleć o wymuszeniu niepowtarzalności, o ponownym wykorzystaniu starych identyfikatorów, które zostały USUNIĘTE. Na przykład nie chcesz używać tych identyfikatorów jako kluczy obcych na zapleczu, jeśli identyfikatory mają zostać przetworzone (jeśli w ogóle to możliwe). Możesz jednak sprawdzić konwersję identyfikatora zewnętrznego / wewnętrznego, aby złagodzić wymagania zaplecza.

dietbuddha
źródło
6
To najlepsza odpowiedź. / login i / logout nie są zasobami i przełamują ideę REST.
wle8300
5
Uwierzytelnianie! = Sesja
dietbuddha
1
Tak, teza Fieldinga stwierdza w sekcji 5.1.3, że „stan sesji jest [...] całkowicie zachowany na kliencie”. Ponadto uważałbym, że w idealnym przypadku uwierzytelnianie powinno być również bezstanowe po stronie serwera, tj. Zamiast przechowywać aktywne „bilety uwierzytelniające” w bazie danych, serwer powinien być w stanie zweryfikować poświadczenie uwierzytelniające na podstawie samego poświadczenia, np. za pomocą samodzielnego tokena kryptograficznego w połączeniu z kluczem prywatnym. Tak więc zamiast zasobu / session można wprowadzić zasób / authentication, ale to też tak naprawdę nie rozwiązuje problemu ...
raner
Właściwie / login i / logout to rzeczowniki. Zakładam, że myślisz o / log_in i / log_out.
TiggerToo
21

Opowiem po prostu z mojego doświadczenia w integracji różnych usług REST Web Services dla moich klientów, niezależnie od tego, czy są one używane do aplikacji mobilnych, czy do komunikacji serwer-serwer, a także budowania REST API dla innych. Oto kilka obserwacji, które zebrałem z REST API innych ludzi, a także z tych, które sami zbudowaliśmy:

  • Kiedy mówimy o API, zwykle odnosi się do zestawu interfejsu programistycznego, a nie do warstwy prezentacji. REST jest również skoncentrowany na danych, a nie na prezentacji. To powiedziawszy, większość REST zwraca dane w postaci JSON lub XML i rzadko zwraca określoną warstwę prezentacji. Ta cecha (zwracania danych, a nie bezpośredniej strony internetowej) dała REST możliwość dostarczania wielokanałowego. Oznacza to, że ta sama usługa internetowa może być renderowana w formacie HTML, iOS, Android lub nawet używana jako połączenie serwera z serwerem.
  • Bardzo rzadko łączy się zarówno HTML, jak i REST jako adres URL. Domyślnie REST są przemyśleniami jako usługami i nie mają warstwy prezentacji. Zadaniem tych, którzy korzystają z usług sieciowych, jest udostępnianie danych z usług, które wywołują, zgodnie z tym, czego chcą. W tym momencie poniższy adres URL nie jest zgodny z większością projektów opartych na REST, z którymi do tej pory się spotkałem (ani ze standardami, takimi jak te, które pochodzą z Facebooka lub Twittera)
GET / register // pobiera stronę internetową, na której znajduje się formularz rejestracyjny
  • Kontynuując od poprzedniego punktu, jest również rzadkie (i nie spotkałem się), aby usługa oparta na REST wykonywała przekierowania, takie jak te sugerowane poniżej:
GET / logout // niszczy sesję i przekierowuje do /
POST / login // uwierzytelnia poświadczenia w bazie danych i albo przekierowuje do domu z nową sesją, albo przekierowuje z powrotem do / login
 

Ponieważ usługi REST są zaprojektowane jako usługi, funkcje takie jak logowanie i wylogowanie zwykle zwracają wynik sukcesu / niepowodzenia (zwykle w formacie danych JSON lub XML), który następnie zinterpretowałby konsument. Taka interpretacja może obejmować przekierowanie do odpowiedniej strony internetowej, jak wspomniałeś

  • W REST adres URL oznacza podejmowane działania. Z tego powodu powinniśmy usunąć jak najwięcej niejasności. Chociaż w twoim przypadku uzasadnione jest posiadanie zarówno GET, jak i POST, które mają tę samą ścieżkę (np. / Register), które wykonują różne czynności, taki projekt wprowadza niejednoznaczność w świadczonych usługach i może zmylić konsumenta z twoimi usługami. Na przykład adresy URL, takie jak ten, który wprowadzisz poniżej, nie są idealne dla usług opartych na REST
GET / register // pobiera stronę internetową, na której znajduje się formularz rejestracyjny
POST / register // zapisuje wprowadzone informacje do bazy danych jako nowy / user / xxx

Oto kilka punktów z tego, z czym miałem do czynienia. Mam nadzieję, że dostarczy ci pewnych informacji.

Jeśli chodzi o implementację twojego REST, są to typowe implementacje, z którymi się spotkałem:

  • GET / logout  
    

    Wyloguj się z zaplecza i zwróć kod JSON w celu oznaczenia sukcesu / niepowodzenia operacji

  • POST / login
    

    Prześlij dane logowania do zaplecza. Zwróć sukces / porażkę. Jeśli się powiedzie, zwykle zwróci token sesji, a także informacje o profilu.

  • POST / register
    

    Prześlij rejestrację do zaplecza. Zwróć sukces / porażkę. Jeśli się powiedzie, zwykle traktowane tak samo, jak udane logowanie lub możesz wybrać rejestrację jako odrębną usługę

  • GET / user / xxx
    

    Pobierz profil użytkownika i zwróć format danych JSON dla profilu użytkownika

  • POST / user / xxx 
    // zmieniono nazwę na 
    POST / updateUser / xxx
    

    Opublikuj zaktualizowane informacje profilowe w formacie JSON i zaktualizuj informacje w zapleczu. Zwróć powodzenie / niepowodzenie dzwoniącemu

momo
źródło
3
Tak, jeśli integrujesz swój REST API z aplikacją opartą na HTML (przez Javascript i AJAX), odniesiesz ogromne korzyści, ponieważ JSON jest analizowany natywnie przez Javascript. W Androidzie / Javie JSON jest łatwiejszy i prostszy do przeanalizowania w porównaniu do XML.
momo
15
GET / logout jest niebezpieczne. GET powinno być idempotentne. Również przeglądarki lubią pobierać wstępnie <a> hrefs, co spowoduje wylogowanie!
Kugel
4

Uważam, że jest to podejście RESTful do uwierzytelniania. Do logowania używasz HttpPut. Ta metoda HTTP może być używana do tworzenia, gdy dostarczony jest klucz, a powtarzane wywołania są idempotentne. W przypadku LogOff należy określić tę samą ścieżkę w ramach HttpDeletemetody. Nie użyto czasowników. Prawidłowa liczba mnoga kolekcji. Metody HTTP wspierają ten cel.

[HttpPut]
[Route("sessions/current")]
public IActionResult LogIn(LogInModel model) { ... }

[HttpDelete]
[Route("sessions/current")]
public IActionResult LogOff() { ... }

Jeśli chcesz, możesz zastąpić prąd aktywny.

Chad Kuehn
źródło
1

Zalecałbym użycie adresu URL konta użytkownika podobnego do Twittera, gdzie adres URL konta użytkownika byłby podobny do tego, foo.com/myUserNamejak można dostać się na moje konto na Twitterze za pomocą adresu URL adresem https://twitter.com/joelbyler

Nie zgadzam się z wylogowaniem wymagającym POST. W ramach swojego API, jeśli zamierzasz utrzymywać sesję, identyfikator sesji w postaci UUID może być czymś, co można wykorzystać do śledzenia użytkownika i potwierdzenia, że ​​podejmowane działanie jest autoryzowane. Wtedy nawet GET może przekazać identyfikator sesji do zasobu.

Krótko mówiąc, radzę zachować prostotę, adresy URL powinny być krótkie i łatwe do zapamiętania.

joelbyler
źródło
Pytanie dotyczy zasobów API. Twoja odpowiedź dotyczy warstwy prezentacji.
Henno,