Uwierzytelnianie oparte na tokenach interfejsu API REST

122

Tworzę REST API, które wymaga uwierzytelnienia. Ponieważ samo uwierzytelnianie odbywa się za pośrednictwem zewnętrznej usługi sieciowej za pośrednictwem protokołu HTTP, doszedłem do wniosku, że będziemy wydawać tokeny, aby uniknąć wielokrotnego wywoływania usługi uwierzytelniania. Co prowadzi mnie zgrabnie do mojego pierwszego pytania:

Czy to naprawdę lepsze niż wymaganie od klientów używania podstawowego uwierzytelniania HTTP przy każdym żądaniu i buforowanie wywołań po stronie serwera usługi uwierzytelniania?

Zaletą rozwiązania Basic Auth jest to, że nie wymaga pełnej podróży w obie strony do serwera przed rozpoczęciem żądań treści. Tokeny mogą potencjalnie mieć bardziej elastyczny zakres (tj. Przyznawać prawa tylko do określonych zasobów lub akcji), ale wydaje się to bardziej odpowiednie w kontekście OAuth niż mój prostszy przypadek użycia.

Obecnie tokeny są pozyskiwane w następujący sposób:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

Plik api_key, timestampI verifiersą wymagane przez wszystkich żądań. „Weryfikator” jest zwracany przez:

sha1(timestamp + api_key + shared_secret)

Moim zamiarem jest zezwalanie na połączenia tylko od znanych osób i zapobieganie wielokrotnemu używaniu połączeń.

Czy to wystarczy? Underkill? Overkill?

Z tokenem w ręku klienci mogą pozyskiwać zasoby:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Dla najprostszego możliwego połączenia wydaje się to okropnie rozwlekłe. Biorąc pod uwagę, że shared_secrettestament zostanie osadzony (przynajmniej) w aplikacji na iOS, z której zakładam, że można ją wyodrębnić, czy oferuje to w ogóle coś poza fałszywym poczuciem bezpieczeństwa?

cantlin
źródło
2
Zamiast używać sha1 (timestamp + api_key + shard_secret), powinieneś użyć hmac (shared_secret, timpestamp + api_key) dla lepszego haszowania zabezpieczeń en.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco
@ MiguelA.Carrasco Wydaje się, że w 2017 roku panował konsensus, że bCrypt jest nowym narzędziem haszującym.
Shawn

Odpowiedzi:

94

Oddzielę wszystko i rozwiążę każdy problem z osobna:

Poświadczenie

Jeśli chodzi o uwierzytelnianie, baseauth ma tę zaletę, że jest dojrzałym rozwiązaniem na poziomie protokołu. Oznacza to, że „może pojawić się później” problemów, które , zostało już rozwiązanych. Na przykład w przypadku BaseAuth agenci użytkownika wiedzą, że hasło jest hasłem, więc nie buforują go.

Obciążenie serwera uwierzytelniania

Jeśli wydajesz token użytkownikowi, zamiast buforować uwierzytelnianie na serwerze, nadal robisz to samo: buforujesz informacje uwierzytelniające. Jedyna różnica polega na tym, że odpowiedzialność za buforowanie przekazujesz użytkownikowi. Wydaje się, że jest to niepotrzebna praca dla użytkownika bez żadnych korzyści, więc polecam załatwić to w sposób przejrzysty na serwerze, tak jak zasugerowałeś.

Bezpieczeństwo transmisji

Jeśli możesz użyć połączenia SSL, to wszystko, połączenie jest bezpieczne *. Aby zapobiec przypadkowemu wielokrotnemu wykonaniu, możesz odfiltrować wiele adresów URL lub poprosić użytkowników o dołączenie do adresu URL elementu losowego („jednorazowego”).

url = username:[email protected]/api/call/nonce

Jeśli nie jest to możliwe, a przesyłane informacje nie są tajne, polecam zabezpieczyć żądanie hashem, tak jak zasugerowałeś w podejściu tokenowym. Ponieważ hash zapewnia bezpieczeństwo, możesz poinstruować użytkowników, aby podali skrót jako hasło baseauth. Aby zwiększyć niezawodność, zalecam użycie losowego ciągu znaków zamiast znacznika czasu jako „jednorazowego”, aby zapobiec atakom typu „powtórka” (w ciągu tej samej sekundy można by wysłać dwa uzasadnione żądania). Zamiast udostępniać oddzielne pola „shared secret” i „api key”, możesz po prostu użyć klucza API jako wspólnego sekretu, a następnie użyć soli, która się nie zmienia, aby zapobiec atakom tęczowej tablicy. Pole nazwy użytkownika wydaje się dobrym miejscem na umieszczenie nonce, ponieważ jest częścią autoryzacji. Więc teraz masz czysty telefon w ten sposób:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:[email protected]/api/call

Prawdą jest, że jest to trochę pracochłonne. Dzieje się tak, ponieważ nie używasz rozwiązania na poziomie protokołu (takiego jak SSL). Dlatego dobrym pomysłem może być udostępnienie użytkownikom pewnego rodzaju SDK, aby przynajmniej nie musieli przez to samodzielnie przechodzić. Jeśli musisz to zrobić w ten sposób, uważam, że poziom bezpieczeństwa jest odpowiedni (po prostu zabij).

Bezpieczne tajne przechowywanie

Zależy, komu próbujesz udaremnić. Jeśli uniemożliwiasz osobom mającym dostęp do telefonu użytkownika korzystanie z usługi REST w imieniu użytkownika, dobrym pomysłem byłoby znalezienie jakiegoś API do obsługi kluczy w docelowym systemie operacyjnym i przechowywanie przez SDK (lub implementatora) klucz tam. Jeśli nie jest to możliwe, możesz przynajmniej nieco utrudnić uzyskanie sekretu, szyfrując go i przechowując zaszyfrowane dane i klucz szyfrowania w oddzielnych miejscach.

Jeśli próbujesz uniemożliwić innym dostawcom oprogramowania uzyskanie klucza API, aby zapobiec tworzeniu alternatywnych klientów, prawie działa tylko metoda szyfrowania i przechowywania osobno . To jest krypto whitebox i do tej pory nikt nie wymyślił naprawdę bezpiecznego rozwiązania problemów tej klasy. Jedyne, co możesz zrobić, to nadal wydawać jeden klucz dla każdego użytkownika, aby móc blokować nadużywane klucze.

(*) EDYCJA: połączenia SSL nie powinny być już uważane za bezpieczne bez podejmowania dodatkowych kroków w celu ich weryfikacji .

cmc
źródło
Dzięki cmc, wszystkie dobre punkty i świetne miejsce do przemyśleń. Skończyło się na tym, że podjąłem podejście token / HMAC podobne do tego, które omówiłeś powyżej, podobnie jak mechanizm uwierzytelniania S3 REST API .
cantlin
Jeśli buforujesz token na serwerze, czy nie jest on zasadniczo taki sam jak stary dobry identyfikator sesji? Identyfikator sesji jest krótkotrwały i jest również dołączony do szybkiego magazynu pamięci podręcznej (jeśli go zaimplementujesz), aby uniknąć uderzenia w bazę danych przy każdym żądaniu. Prawdziwy projekt RESTful i bezstanowy nie powinien mieć sesji, ale jeśli używasz tokena jako identyfikatora, a następnie nadal trafiasz do bazy danych, czy nie lepiej byłoby zamiast tego użyć identyfikatora sesji? Alternatywnie możesz wybrać tokeny internetowe JSON, które zawierają zaszyfrowane lub podpisane informacje dla całych danych sesji, aby uzyskać prawdziwy projekt bezstanowy.
JustAMartin
16

Czysty interfejs API RESTful powinien korzystać z podstawowych funkcji standardowych protokołów:

  1. W przypadku protokołu HTTP interfejs RESTful API powinien być zgodny z istniejącymi standardowymi nagłówkami HTTP. Dodanie nowego nagłówka HTTP narusza zasady REST. Nie wymyślaj ponownie koła, używaj wszystkich standardowych funkcji standardów HTTP / 1.1 - w tym kodów odpowiedzi stanu, nagłówków i tak dalej. Usługi sieciowe RESTFul powinny wykorzystywać standardy HTTP i polegać na nich.

  2. Usługi RESTful MUSZĄ BYĆ BEZSTATELOWE. Wszelkie sztuczki, takie jak uwierzytelnianie oparte na tokenach, próbujące zapamiętać stan poprzednich żądań REST na serwerze, naruszają zasady REST. Ponownie, jest to MUSI; to znaczy, jeśli serwer WWW zapisuje jakiekolwiek informacje związane z kontekstem żądania / odpowiedzi na serwerze, próbując nawiązać na serwerze jakąkolwiek sesję, wówczas usługa sieciowa NIE jest bezstanowa. A jeśli NIE jest bezpaństwowy, NIE jest to RESTFul.

Konkluzja: Do celów uwierzytelniania / autoryzacji należy używać standardowego nagłówka autoryzacji HTTP. Oznacza to, że należy dodać nagłówek autoryzacji / uwierzytelniania HTTP w każdym kolejnym żądaniu, które wymaga uwierzytelnienia. REST API powinien być zgodny ze standardami HTTP Authentication Scheme.Szczegóły dotyczące formatowania tego nagłówka są zdefiniowane w standardach RFC 2616 HTTP 1.1 - sekcja 14.8 Autoryzacja RFC 2616 oraz w RFC 2617 HTTP Authentication: Basic and Digest Access Authentication .

Opracowałem usługę RESTful dla aplikacji Cisco Prime Performance Manager. Wyszukaj w Google dokument interfejsu API REST, który napisałem dla tej aplikacji, aby uzyskać więcej informacji na temat zgodności z RESTFul API tutaj . W tej implementacji zdecydowałem się użyć schematu autoryzacji HTTP „Basic”. - sprawdź wersję 1.5 lub nowszą tego dokumentu REST API i wyszukaj autoryzację w dokumencie.

Rubens Gomes
źródło
8
„Dodanie nowego nagłówka HTTP narusza zasady REST”. Jak to? A jeśli już to zrobisz, możesz być tak uprzejmy, aby wyjaśnić, jaka dokładnie jest różnica (dotycząca zasad) między hasłem, które wygasa po pewnym czasie, a tokenem, który wygasa po pewnym okresie.
lepszy oliver
6
Nazwa użytkownika + hasło to token (!), Który jest wymieniany między klientem a serwerem przy każdym żądaniu. Ten token jest przechowywany na serwerze i ma określony czas życia. Jeśli hasło wygaśnie, muszę zdobyć nowe. Wygląda na to, że „token” jest powiązany z „sesją serwera”, ale to nieprawidłowa konkluzja. Nie ma to nawet znaczenia, ponieważ byłby to szczegół implementacji. Twoja klasyfikacja tokenów innych niż nazwa użytkownika / hasło jako stanowe jest czysto sztuczna, imho.
lepszy oliver
1
Myślę, że powinieneś zmotywować, dlaczego warto wykonać implementację z RESTful zamiast Basic Authentication, która jest częścią pierwotnego pytania. Może mógłbyś również podać link do dobrych przykładów z dołączonym kodem. Jako osoba początkująca w tym temacie teoria wydaje się wystarczająco jasna i zawiera wiele dobrych zasobów, ale metoda implementacji nie jest, a przykłady są zawiłe. To frustrujące, że wydaje mi się, że wdrożenie na czas niestandardowego kodowania czegoś, co zostało zrobione tysiące razy.
JPK
13
-1 „Wszelkie sztuczki, takie jak uwierzytelnianie oparte na tokenach, które próbują zapamiętać stan poprzednich żądań REST na serwerze, naruszają zasady REST”. Uwierzytelnianie oparte na tokenach nie ma nic wspólnego ze stanem poprzednich żądań REST i nie narusza bezstanowości REST .
Kerem Baydoğan
1
Zatem, zgodnie z tym, tokeny sieciowe JSON naruszają REST, ponieważ mogą przechowywać stan użytkownika (oświadczenia)? W każdym razie wolę naruszyć REST i użyć starego dobrego identyfikatora sesji jako „tokena”, ale początkowe uwierzytelnienie jest wykonywane przy użyciu nazwy użytkownika + hasła, podpisane lub zaszyfrowane przy użyciu wspólnego tajnego i bardzo krótkotrwałego znacznika czasu (więc nie powiedzie się, jeśli ktoś spróbuje powtórzyć że). W aplikacji „korporacyjnej” trudno jest odrzucić korzyści z sesji (unikając trafiania do bazy danych w przypadku niektórych danych potrzebnych w prawie każdym żądaniu), dlatego czasami musimy poświęcić prawdziwą bezpaństwowość.
JustAMartin
2

W sieci protokół stanowy opiera się na posiadaniu tymczasowego tokena, który jest wymieniany między przeglądarką a serwerem (poprzez nagłówek pliku cookie lub przepisywanie identyfikatora URI) przy każdym żądaniu. Ten token jest zwykle tworzony po stronie serwera i jest to fragment nieprzezroczystych danych, który ma określony czas życia, a jego jedynym celem jest identyfikacja określonego internetowego agenta użytkownika. Oznacza to, że token jest tymczasowy i staje się STANEM, który serwer WWW musi utrzymywać w imieniu klienta użytkownika w czasie trwania tej konwersacji. Dlatego komunikacja przy użyciu tokena w ten sposób jest STANOWA. A jeśli konwersacja między klientem a serwerem jest STANOWA, to nie jest RESTful.

Nazwa użytkownika / hasło (wysyłane w nagłówku Authorization) są zwykle utrwalane w bazie danych w celu identyfikacji użytkownika. Czasami użytkownik może mieć na myśli inną aplikację; jednakże nazwa użytkownika / hasło NIGDY nie służy do identyfikowania konkretnego klienta użytkownika WWW. Konwersacja między agentem internetowym a serwerem oparta na użyciu nazwy użytkownika / hasła w nagłówku autoryzacji (po autoryzacji podstawowej HTTP) jest BEZSTANOWA, ponieważ front-end serwera WWW nie tworzy ani nie obsługuje żadnych informacji STANcokolwiek w imieniu konkretnego klienta internetowego klienta. Opierając się na moim zrozumieniu REST, protokół jasno stwierdza, że ​​konwersacja między klientami a serwerem powinna być BEZSTANOWA. Dlatego jeśli chcemy mieć prawdziwą usługę RESTful, powinniśmy użyć nazwy użytkownika / hasła (patrz RFC wspomniany w moim poprzednim poście) w nagłówku Authorization dla każdego połączenia, a NIE rodzaj tokena typu sension (np. Tokeny sesji utworzone na serwerach internetowych , Tokeny OAuth utworzone na serwerach autoryzacji i tak dalej).

Rozumiem, że kilku zwanych dostawców REST używa tokenów, takich jak tokeny akceptacji OAuth1 lub OAuth2, które mają być przekazywane jako „Authorization: Bearer” w nagłówkach HTTP. Jednak wydaje mi się, że używanie tych tokenów do usług RESTful naruszyłoby prawdziwe znaczenie STATELESS, które obejmuje REST; ponieważ te tokeny są tymczasowymi fragmentami danych utworzonymi / utrzymywanymi po stronie serwera w celu zidentyfikowania konkretnego klienta użytkownika sieci WWW na czas trwania konwersacji tego klienta / serwera. Dlatego żadna usługa korzystająca z tych tokenów OAuth1 / 2 nie powinna nazywać się REST, jeśli chcemy trzymać się PRAWDZIWEGO znaczenia protokołu STATELESS.

Rubens

Rubens Gomes
źródło