Usługa internetowa RESTful - jak uwierzytelniać żądania z innych usług?

117

Projektuję usługę internetową zgodną z REST, do której muszą mieć dostęp użytkownicy, ale także inne usługi i aplikacje internetowe. Wszystkie przychodzące żądania muszą zostać uwierzytelnione. Cała komunikacja odbywa się za pośrednictwem protokołu HTTPS. Uwierzytelnianie użytkownika będzie działało w oparciu o token uwierzytelniający, uzyskany poprzez POST przesłanie nazwy użytkownika i hasła (przez połączenie SSL) do zasobu / session udostępnianego przez usługę.

W przypadku klientów usług internetowych za usługą klienta nie stoi żaden użytkownik końcowy . Żądania są inicjowane przez zaplanowane zadania, zdarzenia lub inne operacje komputerowe. Lista usług łączących jest znana z góry (oczywiście, tak mi się wydaje). Jak mam uwierzytelnić te żądania pochodzące z innych usług (internetowych)? Chcę, aby proces uwierzytelniania był jak najłatwiejszy do wdrożenia dla tych usług, ale nie kosztem bezpieczeństwa. Jaki byłby standard i najlepsze praktyki w takim scenariuszu?

Opcje, które przychodzą mi do głowy (lub zostały mi zasugerowane):

  1. Niech usługi klienckie używają „fałszywej” nazwy użytkownika i hasła i uwierzytelniają je w taki sam sposób, jak użytkowników. Nie podoba mi się ta opcja - po prostu nie czuję się dobrze.

  2. Przypisz stały identyfikator aplikacji dla usługi klienta, być może także klucz aplikacji. O ile zrozumiałem, jest to to samo, co posiadanie nazwy użytkownika i hasła. Za pomocą tego identyfikatora i klucza mogę uwierzytelnić każde żądanie lub utworzyć token uwierzytelniający do uwierzytelniania kolejnych żądań. Tak czy inaczej, nie podoba mi się ta opcja, ponieważ każdy, kto może zdobyć identyfikator aplikacji i klucz, może podszyć się pod klienta.

  3. Mógłbym dodać sprawdzenie adresu IP do poprzedniej opcji. Utrudniłoby to wykonywanie fałszywych żądań.

  4. Certyfikaty klienta. Skonfiguruj własny urząd certyfikacji, utwórz certyfikat główny i utwórz certyfikaty klienta dla usług klienckich. Przychodzi jednak na myśl kilka kwestii: a) w jaki sposób mogę nadal zezwalać użytkownikom na uwierzytelnianie bez certyfikatów oraz b) jak skomplikowany jest ten scenariusz do wdrożenia z punktu widzenia obsługi klienta?

  5. Coś jeszcze - muszą tam być inne rozwiązania?

Moja usługa działałaby w Javie, ale celowo pominąłem informacje o tym, na jakim konkretnym frameworku będzie zbudowana, ponieważ bardziej interesują mnie podstawowe zasady, a nie szczegóły implementacji - zakładam, że najlepsze rozwiązanie będzie być możliwe do wdrożenia niezależnie od podstawowej struktury. Jednak jestem trochę niedoświadczony w tym temacie, więc konkretne wskazówki i przykłady dotyczące rzeczywistego wdrożenia (takie jak przydatne biblioteki stron trzecich, artykuły itp.) Będą również bardzo mile widziane.

Tommi
źródło
Jeśli mogę zasugerować, zapoznaj się z dużymi usługami internetowymi i wybierz, co lubisz. Twoi użytkownicy również zauważyliby podobieństwa z najlepszymi praktykami innych usług RESTful.
Yzmir Ramirez
Znalazłem inne pytanie (prawie dwa lata), które dotyka podobnego tematu: stackoverflow.com/questions/1138831/ ...
Tommi
Na jakim systemie operacyjnym są hostowane usługi (zarówno internetowe, jak i inne)? Czy działają na serwerach, które są częścią tej samej infrastruktury?
Anders Abel
Systemy operacyjne mogą się różnić: Win, * nix itp. Usługi klienckie mogą, ale nie muszą, znajdować się w tej samej infrastrukturze co moja usługa.
Tommi

Odpowiedzi:

34

Każde rozwiązanie tego problemu sprowadza się do wspólnego sekretu. Nie podoba mi się również zakodowana nazwa użytkownika i opcja hasła, ale ma tę zaletę, że jest dość prosta. Certyfikat klienta też jest dobry, ale czy naprawdę różni się znacznie? Na serwerze znajduje się certyfikat, a na kliencie. Jego główną zaletą jest to, że trudniej jest brutalna siła. Miejmy nadzieję, że masz inne zabezpieczenia, które chronią przed tym.

Nie sądzę, aby Twój punkt A dotyczący rozwiązania z certyfikatem klienta był trudny do rozwiązania. Po prostu używasz gałęzi. if (client side certificat) { check it } else { http basic auth }Nie jestem ekspertem w dziedzinie java i nigdy nie pracowałem z nim przy tworzeniu certyfikatów po stronie klienta. Jednak szybkie Google prowadzi nas do tego samouczka, który wygląda prosto w Twoją uliczkę.

Pomimo całej tej dyskusji na temat „tego, co najlepsze”, pozwólcie mi tylko zaznaczyć, że istnieje inna filozofia, która mówi, że „mniej kodu, mniej sprytu, tym lepiej”. (Osobiście trzymam się tej filozofii). Rozwiązanie dotyczące certyfikatu klienta brzmi jak dużo kodu.

Wiem, że zadałeś pytania na temat OAuth, ale propozycja OAuth2 zawiera rozwiązanie Twojego problemu o nazwie „ tokeny okaziciela ”, które muszą być używane w połączeniu z SSL. Myślę, że ze względu na prostotę wybrałbym albo zakodowanego na stałe użytkownika / przepustkę (po jednym na aplikację, aby można je było odwołać indywidualnie) lub bardzo podobne tokeny okaziciela.

newz2000
źródło
27
Certyfikaty klienta NIE są wspólnym hasłem. Dlatego istnieją. Klient ma klucz prywatny, a serwer ma klucz publiczny. Klient nigdy nie udostępnia swojej tajemnicy, a klucz publiczny nie jest tajemnicą.
Tim
5
Link do samouczka nie prowadzi do artykułu z samouczkiem, ale do jakiejś strony indeksu Java w witrynie Oracle ...
Marjan Venema
2
@MarjanVenema Cóż, to dlatego, że próbujesz odsyłacza +2 lata po odpowiedzi od newz2000, ale zawsze możesz wypróbować WayBack Machine: web.archive.org/web/20110826004236/http://java.sun.com/…
Fábio Duque Silva,
1
@MarjanVenema: Przepraszam, ale spodziewasz się, że newz2000 przyjdzie tutaj i zaktualizuje link po tym, jak zgaśnie? Jak powiedziałeś, to gnicie linków, więc prędzej czy później zdarza się. Albo spróbujesz uzyskać dostęp do archiwum, aby zobaczyć, co autor oglądał w tym czasie, albo znajdziesz nowy link i wnieś pozytywny wkład. Nie widzę, jak twój komentarz komuś pomógł. Ale tutaj, podążaj za tym linkiem: oracle.com/technetwork/articles/javase/… (uważaj, że w końcu też zgnije)
Fábio Duque Silva
2
@ FábioSilva: Nie. Nie oczekuję, że to zrobi. Przeczytałem archiwum, ale nie miałem czasu na szukanie nowego linku, więc zrobiłem następną najlepszą rzecz: dodaj komentarz, że jest martwy, aby ktoś inny ze społeczności mógł znaleźć nową lokalizację i zaktualizować Poczta. Ponieważ oczywiście miałeś czas, aby znaleźć nowy link, dlaczego nie zaktualizowałeś linku w poście, zamiast umieścić go w komentarzu, który mnie pochłania?
Marjan Venema
36

Po przeczytaniu twojego pytania powiedziałbym, że wygeneruj specjalny token, aby wykonać wymagane żądanie. Ten token będzie żył w określonym czasie (powiedzmy za jeden dzień).

Oto przykład z do generowania tokena uwierzytelniania:

(day * 10) + (month * 100) + (year (last 2 digits) * 1000)

na przykład: 3 czerwca 2011 r

(3 * 10) + (6 * 100) + (11 * 1000) = 
30 + 600 + 11000 = 11630

następnie połącz z hasłem użytkownika, na przykład „my4wesomeP4ssword!”

11630my4wesomeP4ssword!

Następnie wykonaj MD5 tego ciągu:

05a9d022d621b64096160683f3afe804

Kiedy dzwonisz do żądania, zawsze używaj tego tokena,

https://mywebservice.com/?token=05a9d022d621b64096160683f3afe804&op=getdata

Ten token jest zawsze wyjątkowy każdego dnia, więc myślę, że ten rodzaj ochrony jest więcej niż wystarczający, aby zawsze chronić naszą usługę.

Nadzieja pomaga

:)

kororo
źródło
1
Bardzo podoba mi się sposób, w jaki dołączałeś token bezpieczeństwa do każdego żądania, ale co się z tym dzieje, gdy programista utworzył już 100 stron jsp, a następnie zaimplementował zabezpieczenia na wcześniej utworzonych 100 stronach, a także na stronach, które mają zostać utworzone. W takim przypadku dołączanie tokena do każdego żądania nie jest poprawnym wyborem, w każdym razie +1 za twoją technikę. :)
Ankur Verma
4
Co się stanie, jeśli zegary nie zostaną zsynchronizowane? Czy w tym przypadku klient nie wygeneruje niewłaściwego tokena? Nawet jeśli obaj generują datę i godzinę w UTC, ich zegary mogą nadal być inne, co skutkuje codziennym oknem czasu, kiedy token nie działał?
NickG
@NickG, miałem ten problem wcześniej, jedyny sposób, aby to zabezpieczyć, żądając czasu serwera. W 99% wyeliminuje to problem UTC. Oczywiście wadą jest dodatkowe jedno połączenie z serwerem.
kororo
ale nadal mogę korzystać z usługi sieci Web przy użyciu tego tokena przez jeden dzień, prawda? nie widzę, jak to pomoże
Mina Gabriel
@MinaGabriel możesz dodać więcej ram czasowych podczas generowania tokena. (minuta * 10) + (godzina * 100) + (dzień * 1000) + (miesiąc * 10000) + (rok (ostatnie 2 cyfry) * 100000)
kororo
11

Istnieje kilka różnych podejść.

  1. Puryści RESTful będą chcieli, abyś używał uwierzytelniania BASIC i wysyłał dane uwierzytelniające przy każdym żądaniu. Ich uzasadnieniem jest to, że nikt nie przechowuje żadnego państwa.

  2. Usługa klienta może przechowywać plik cookie, który przechowuje identyfikator sesji. Osobiście nie uważam tego za tak obraźliwe, jak niektórzy purystowie, od których słyszę - wielokrotne uwierzytelnianie może być kosztowne. Wygląda jednak na to, że nie przepadasz za tym pomysłem.

  3. Z twojego opisu naprawdę wygląda na to, że możesz być zainteresowany OAuth2. Moje dotychczasowe doświadczenia, z tego, co widziałem, są takie, że jest to trochę zagmatwane i trochę krwawiące. Istnieją implementacje, ale jest ich niewiele. W Javie rozumiem, że został on zintegrowany z modułami bezpieczeństwa Spring3 . (Ich samouczek jest ładnie napisany.) Czekałem, aby zobaczyć, czy będzie rozszerzenie w Restlet , ale jak dotąd, chociaż zostało zaproponowane i może znajdować się w inkubatorze, nadal nie zostało w pełni włączone.

jwismar
źródło
Nie mam nic przeciwko opcji 2 - myślę, że jest to dobre rozwiązanie w aplikacji RESTful - ale skąd usługa klienta w pierwszej kolejności pobiera token? Jak uwierzytelniają się za pierwszym razem? Może myślę o tym źle, ale wydaje się dziwne, że usługa klienta musiałaby mieć do tego własną nazwę użytkownika i hasło.
Tommi
Jeśli użytkownik końcowy jest Twoim użytkownikiem, to usługa pośrednicząca może przekazać Ci swoje dane uwierzytelniające przy pierwszym żądaniu, a Ty możesz zwrócić plik cookie lub inny token.
jwismar
Podobnie w scenariuszu OAuth użytkownik końcowy deleguje do usługi pośredniczącej swój dostęp do usługi sieciowej.
jwismar
Wydaje się, że jest nieporozumienie - za obsługą klienta nie stoi żaden użytkownik końcowy . Zaktualizowałem moje pytanie, aby lepiej wyjaśnić sytuację.
Tommi
1
Dodam tylko, że wymieniona powyżej opcja nr 1 powinna być wykonywana tylko przez HTTPS.
mr-sk
3

Wierzę w podejście:

  1. Pierwsze żądanie, klient wysyła identyfikator / kod dostępu
  2. Wymień identyfikator / przepustkę na unikalny token
  3. Weryfikuj token przy każdym kolejnym żądaniu, dopóki nie wygaśnie

jest dość standardowe, niezależnie od sposobu wdrożenia i innych szczegółowych szczegółów technicznych.

Jeśli naprawdę chcesz pchnąć kopertę, być może możesz uznać, że klucz https klienta jest tymczasowo nieprawidłowy do momentu zweryfikowania poświadczeń, ograniczyć informacje, jeśli nigdy nie są, i przyznać dostęp po ich weryfikacji, ponownie w oparciu o wygaśnięcie.

Mam nadzieję że to pomoże

Dynrepsys
źródło
3

Jeśli chodzi o podejście do certyfikatu klienta, nie byłoby to strasznie trudne do wdrożenia, a jednocześnie pozwoliłoby użytkownikom bez certyfikatów klienta w formacie.

Gdybyś faktycznie utworzył własny urząd certyfikacji z podpisem własnym i wystawił certyfikaty klienta dla każdej usługi klienta, miałbyś łatwy sposób uwierzytelnienia tych usług.

W zależności od używanego serwera WWW powinna istnieć metoda określania uwierzytelniania klienta, która będzie akceptować certyfikat klienta, ale go nie wymaga. Na przykład w Tomcat podczas określania łącznika https można ustawić „clientAuth = want” zamiast „true” lub „false”. Następnie upewnij się, że dodałeś samopodpisany certyfikat CA do swojego magazynu zaufanych certyfikatów (domyślnie plik cacerts w używanym środowisku JRE, chyba że określono inny plik w konfiguracji serwera internetowego), więc jedynymi zaufanymi certyfikatami byłyby te wydane poza urząd certyfikacji z podpisem własnym.

Po stronie serwera pozwolisz na dostęp do usług, które chcesz chronić, tylko wtedy, gdy będziesz w stanie pobrać certyfikat klienta z żądania (nie zerowy) i pomyślnie przejdziesz testy DN, jeśli wolisz dodatkowe zabezpieczenia. Użytkownicy bez certyfikatów klienta nadal będą mogli uzyskiwać dostęp do usług, ale po prostu nie będą mieli żadnych certyfikatów w żądaniu.

Moim zdaniem jest to najbardziej „bezpieczny” sposób, ale z pewnością ma swoją krzywą uczenia się i narzut, więc niekoniecznie musi być najlepszym rozwiązaniem dla Twoich potrzeb.

bobz32
źródło
3

5. Coś jeszcze - muszą być inne rozwiązania?

Masz rację, jest! I nazywa się JWT (JSON Web Tokens).

JSON Web Token (JWT) to otwarty standard (RFC 7519), który definiuje kompaktowy i niezależny sposób bezpiecznego przesyłania informacji między stronami w postaci obiektu JSON. Te informacje można zweryfikować i zaufać, ponieważ są podpisane cyfrowo. Tokeny JWT można podpisywać za pomocą klucza tajnego (za pomocą algorytmu HMAC) lub pary kluczy publiczny / prywatny za pomocą RSA.

Gorąco polecam zajrzenie do JWT. Są znacznie prostszym rozwiązaniem problemu w porównaniu z rozwiązaniami alternatywnymi.

https://jwt.io/introduction/

justin.hughey
źródło
1

Możesz utworzyć sesję na serwerze i udostępniać ją sessionIdmiędzy klientem a serwerem przy każdym wywołaniu REST.

  1. Po pierwsze żądanie uwierzytelnienia REST: /authenticate. Zwraca odpowiedź (zgodnie z formatem klienta) z sessionId: ABCDXXXXXXXXXXXXXX;

  2. Zapisz to sessionIdw Mapaktualnej sesji. Map.put(sessionid, session)lub używać SessionListenerdo tworzenia i niszczenia kluczy dla Ciebie;

    public void sessionCreated(HttpSessionEvent arg0) {
      // add session to a static Map 
    }
    
    public void sessionDestroyed(HttpSessionEvent arg0) {
      // Remove session from static map
    }
    
  3. Uzyskaj identyfikator sesji przy każdym wywołaniu REST, na przykład URL?jsessionid=ABCDXXXXXXXXXXXXXX(lub w inny sposób);

  4. Wycofaj HttpSessionz mapy za pomocą sessionId;
  5. Zatwierdź żądanie dla tej sesji, jeśli sesja jest aktywna;
  6. Odeślij odpowiedź lub komunikat o błędzie.
arviarya
źródło
0

Użyłbym aplikacji przekierowującej użytkownika do Twojej witryny z parametrem identyfikatora aplikacji, gdy użytkownik zatwierdzi żądanie, wygeneruje unikalny token, który jest używany przez inną aplikację do uwierzytelniania. W ten sposób inne aplikacje nie obsługują poświadczeń użytkownika, a użytkownicy mogą dodawać, usuwać i zarządzać innymi aplikacjami. Foursquare i kilka innych witryn uwierzytelnia się w ten sposób i jest bardzo łatwy do wdrożenia jako inna aplikacja.

Devin M.
źródło
Hmm, nie jestem pewien, czy udało mi się zastosować wyjaśnienie. O jakim użytkowniku mówimy? Mowa o aplikacji komunikującej się z inną aplikacją. Przypuszczam, że to zrozumiałeś, ale nadal nie do końca rozumiem. Co się dzieje, gdy na przykład wygaśnie ten „token”?
Tommi
Cóż, token, który generujesz i wysyłasz z powrotem do innej aplikacji, jest tokenem trwałym, jest powiązany z użytkownikiem i aplikacją. Tutaj jest link do foursquares docs developer.foursquare.com/docs/oauth.html i jest to po prostu oauth2, więc spójrz na to, aby uzyskać dobre rozwiązanie do uwierzytelniania.
Devin M
Wydaje się, że jest nieporozumienie - za obsługą klienta nie stoi żaden użytkownik końcowy . Dokumentacja foursquare, do której utworzyłeś łącze, wspomina jednak krótko o dostępie bez użytkownika, więc była przynajmniej trochę pomocna - dzięki! Ale nadal nie jestem w stanie stworzyć pełnego obrazu tego, jak by to działało w rzeczywistości.
Tommi
Po prostu wygeneruj klucze dla aplikacji, a jeśli wszystko, co robisz, to zezwalanie na dostęp do aplikacji, wówczas proste application_id i application_key powinny działać do uwierzytelnienia. Jeśli chcesz, aby uwierzytelniali się za pomocą tokena, zajrzyj do opcji uwierzytelniania tokena devise, ponieważ byłby to tylko parametr przekazany z żądaniem adresu URL do Twojej aplikacji.
Devin M
Ale czy nie jest to dokładnie to samo, co scenariusz nazwa użytkownika + hasło = token uwierzytelniania sesji? Czy identyfikator_aplikacji i klucz_aplikacji nie są tylko synonimami nazwy użytkownika i hasła? :) Jest całkowicie w porządku, jeśli to naprawdę jest standardowa praktyka w takiej sytuacji - jak powiedziałem, jestem w tym niedoświadczony - ale pomyślałem, że mogą być inne opcje ...
Tommi
-3

Oprócz uwierzytelniania sugeruję, abyś pomyślał o szerszej perspektywie. Zastanów się, czy Twoja usługa zaplecza REST zgodna z REST bez żadnego uwierzytelniania; następnie umieść bardzo prostą usługę warstwy pośredniej wymagającą uwierzytelnienia między użytkownikiem końcowym a usługą backendu.

Dagang
źródło
Między użytkownikiem końcowym a usługą backendu? Czy nie spowodowałoby to całkowitej nieuwierzytelnienia usług klienckich? Nie tego chcę. Mogę oczywiście umieścić tę warstwę pośrednią między moją usługą internetową a usługami klienckimi, ale to wciąż pozostaje otwarte pytanie: jaki byłby rzeczywisty model uwierzytelniania?
Tommi
Warstwą środkową może być serwer WWW, taki jak Nginx, możesz tam przeprowadzić uwierzytelnianie. Model uwierzytelniania może być oparty na sesji.
Dagang
Jak próbowałem wyjaśnić w swoim pytaniu, nie chcę schematu uwierzytelniania opartego na sesji dla tych usług klienckich. (Proszę zobaczyć moje aktualizacje do pytania.)
Tommi
Sugeruję użycie białej listy adresów IP lub zakresu adresów zamiast nazwy i hasła. Zwykle adres IP obsługi klienta jest stabilny.
Dagang