Uwierzytelnianie REST i ujawnianie klucza API

94

Czytałem o REST i jest wiele pytań na ten temat, a także na wielu innych stronach i blogach. Chociaż nigdy nie widziałem tego konkretnego pytania ... z jakiegoś powodu nie mogę skupić się na tej koncepcji ...

Jeśli tworzę RESTful API i chcę go zabezpieczyć, jedną z metod, które widziałem, jest użycie tokenu bezpieczeństwa. Kiedy korzystałem z innych interfejsów API, był token i wspólny sekret ... ma sens. Nie rozumiem, że żądania do wykonania usługi odpoczynku są wysyłane za pośrednictwem javascript (XHR / Ajax), co ma uniemożliwić komuś wykrycie tego za pomocą czegoś prostego, takiego jak FireBug (lub „wyświetl źródło” w przeglądarce) i kopiowanie klucza API, a następnie podszywanie się pod tę osobę przy użyciu klucza i sekretu?

tjans
źródło
jedną z metod, które widziałem, jest użycie tokena bezpieczeństwa , istnieje naprawdę wiele metod. Czy masz konkretny przykład. Mogę pomyśleć, że pomyliłeś się z „REST” a „udostępniaj javascript API tylko dla zarejestrowanych użytkowników” (np. Mapy Google).
PeterMmm
1
Odkąd zapytałeś prawie 2 lata temu: czego ostatecznie wykorzystałeś?
Arjan,
Właściwie niczego nie użyłem, po prostu próbowałem ogarnąć głowę tworzeniem koncepcji. Powyższy komentarz PeterMmm jest prawdopodobnie prawdziwy ... nadal nie miałem potrzeby wdrażania tego wszystkiego, ale chciałem się poprawić ... dzięki za kontynuowanie.
tjans

Odpowiedzi:

22

klucz API nie jest przekazywany jawnie, sekret służy do generowania znaku bieżącego żądania, po stronie serwera serwer generuje znak zgodnie z tym samym procesem, jeśli dwa znaki są zgodne, żądanie jest pomyślnie uwierzytelniane - więc tylko znak jest przekazywany poprzez żądanie, a nie sekret.

James.Xu
źródło
9
Więc jeśli to tylko przekazany znak ... czy to nie jest nadal widoczne w javascript ... więc jeśli umieszczę migotanie na mojej stronie internetowej za pośrednictwem ich interfejsu API (wywoływanego przez javascript), a ty odwiedzasz moją stronę, nie t Czy ujawniam swój klucz API każdemu, kto odwiedza moją stronę?
tjans
6
Nie sądzę, że zadaję swoje pytanie poprawnie ... prawdopodobnie część powodu, dla którego nie znalazłem tego, czego szukałem. kiedy wykonuję moje wywołanie ajax, powiedzmy używając jquery, musiałbym osadzić klucz API w wywołaniu ajax, aby został przekazany do serwera ... w tym momencie ktoś może zobaczyć klucz API. Jeśli rozumiem to źle, w jaki sposób klucz API jest wysyłany z żądaniem, jeśli nie jest osadzony w skrypcie klienta?
tjans
4
podsumowując: ludzie otrzymają parę apikey + apisecret przed użyciem openapi / restapi, znak apikey + zostanie przeniesiony na stronę serwera, aby upewnić się, że serwer wie, kto wysyła żądanie, apisecret nigdy nie zostanie przeniesiony na serwer ze względów bezpieczeństwa .
James.Xu,
7
Stwierdzenie @ James.Xu, że „sekret służy do generowania znaku bieżącego żądania” jest FAŁSZ! Ponieważ klient nie zna sekretu, ponieważ wysłanie go do niego byłoby niebezpieczne (i jak inaczej mógłby to wiedzieć?) „Sekret”, który jest technicznie „kluczem prywatnym”, jest używany TYLKO PRZEZ SERWER (ponieważ nikt inny tego nie wie) w celu wygenerowania znaku, który ma być porównany ze znakiem klienta. Więc pytanie: jakie dane są łączone z „kluczem API”, którego nikt inny nie zna poza klientem i serwerem? Znak = api_key + co?
ACs
1
Masz rację, @ACs. Nawet jeśli oba serwery (strona internetowa i zewnętrzny interfejs API) znają ten sam sekret, nie można obliczyć jakiegoś podpisu na serwerze witryny, a następnie umieścić wynik w HTML / JavaScript, a następnie zmusić przeglądarkę do przekazania go do API. W ten sposób każdy inny serwer mógłby zażądać tego kodu HTML z pierwszego serwera WWW, pobrać podpis z odpowiedzi i użyć go w kodzie HTML we własnej witrynie internetowej. (Naprawdę uważam, że powyższy post nie odpowiada na pytanie, w jaki sposób publiczny klucz API w HTML może być bezpieczny.)
Arjan
62

Udostępniamy interfejs API, którego partnerzy mogą używać tylko w domenach, które zarejestrowali u nas. Jego zawartość jest częściowo publiczna (ale najlepiej, aby była wyświetlana tylko w znanych nam domenach), ale w większości jest prywatna dla naszych użytkowników. Więc:

  • Aby określić, co jest wyświetlane, nasz użytkownik musi być u nas zalogowany, ale jest to obsługiwane osobno.

  • Aby określić, gdzie dane są wyświetlane, używany jest publiczny klucz API w celu ograniczenia dostępu do znanych nam domen, a przede wszystkim w celu zapewnienia, że ​​prywatne dane użytkownika nie są narażone na CSRF .

Ten klucz API jest rzeczywiście widoczny dla każdego, nie uwierzytelniamy naszego partnera w żaden inny sposób i nie potrzebujemy REFERERA . Mimo to jest bezpieczny:

  1. Gdy get-csrf-token.js?apiKey=abc123zostaniesz o to poproszony:

    1. Wyszukaj klucz abc123w bazie danych i uzyskaj listę prawidłowych domen dla tego klucza.

    2. Poszukaj pliku cookie walidacji CSRF. Jeśli nie istnieje, wygeneruj bezpieczną wartość losową i umieść ją w pliku cookie sesji HTTP . Jeśli plik cookie istniał, pobierz istniejącą losową wartość.

    3. Utwórz token CSRF na podstawie klucza API i losowej wartości z pliku cookie i podpisz go . (Zamiast przechowywać listę tokenów na serwerze, podpisujemy wartości. Obie wartości będą czytelne w podpisanym tokenie, to dobrze).

    4. Ustaw odpowiedź tak, aby nie była buforowana, dodaj plik cookie i zwróć skrypt, taki jak:

      var apiConfig = apiConfig || {};
      if(document.domain === 'expected-domain.com' 
            || document.domain === 'www.expected-domain.com') {
      
          apiConfig.csrfToken = 'API key, random value, signature';
      
          // Invoke a callback if the partner wants us to
          if(typeof apiConfig.fnInit !== 'undefined') {
              apiConfig.fnInit();
          }
      } else {
          alert('This site is not authorised for this API key.');
      }
      

    Uwagi:

    • Powyższe nie zapobiega sfałszowaniu żądania przez skrypt po stronie serwera, a jedynie zapewnia dopasowanie domeny, jeśli zażąda tego przeglądarka.

    • Ta sama zasada pochodzenia dla JavaScript gwarantuje, że przeglądarka nie może używać XHR (Ajax) do załadowania, a następnie sprawdzenia źródła JavaScript. Zamiast tego zwykła przeglądarka może załadować go tylko przy użyciu <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">(lub dynamicznego odpowiednika), a następnie uruchomi kod. Oczywiście Twój serwer nie powinien obsługiwać udostępniania zasobów między źródłami ani JSONP dla wygenerowanego kodu JavaScript.

    • Skrypt przeglądarki może zmienić wartość document.domainprzed załadowaniem powyższego skryptu. Ale ta sama polityka pochodzenia pozwala tylko na skrócenie domeny przez usunięcie prefiksów, na przykład przepisywanie subdomain.example.comdo just example.comlub myblog.wordpress.comto wordpress.com, aw niektórych przeglądarkach nawet bbc.co.ukna co.uk.

    • Jeśli plik JavaScript jest pobierany za pomocą skryptu po stronie serwera, serwer również otrzyma plik cookie. Jednak serwer strony trzeciej nie może przypisać przeglądarce użytkownika tego pliku cookie do naszej domeny. Dlatego token CSRF i plik cookie walidacji, które zostały pobrane za pomocą skryptu po stronie serwera, mogą być używane tylko przez kolejne wywołania po stronie serwera, a nie w przeglądarce. Jednak takie wywołania po stronie serwera nigdy nie będą zawierać pliku cookie użytkownika, a zatem mogą pobierać tylko dane publiczne. Są to te same dane, które skrypt po stronie serwera mógłby pobrać bezpośrednio z witryny internetowej partnera.

  2. Kiedy użytkownik się loguje, ustaw plik cookie użytkownika w dowolny sposób. (Użytkownik mógł się już zalogować, zanim zażądano JavaScript).

  3. Wszystkie kolejne żądania API do serwera (w tym żądania GET i JSONP) muszą zawierać token CSRF, plik cookie walidacji CSRF i (jeśli jest zalogowany) plik cookie użytkownika. Serwer może teraz określić, czy żądanie ma być zaufane:

    1. Obecność prawidłowego tokenu CSRF gwarantuje, że JavaScript został załadowany z oczekiwanej domeny, jeśli zostanie załadowany przez przeglądarkę.

    2. Obecność tokenu CSRF bez pliku cookie walidacji wskazuje na fałszerstwo.

    3. Obecność zarówno tokena CSRF, jak i pliku cookie walidacji CSRF niczego nie gwarantuje: może to być sfałszowane żądanie po stronie serwera lub prawidłowe żądanie z przeglądarki. (Nie mogło to być żądanie przeglądarki wysłane z nieobsługiwanej domeny).

    4. Obecność pliku cookie użytkownika zapewnia jego zalogowanie, ale nie gwarantuje, że jest on członkiem danego partnera, ani że przegląda poprawną stronę internetową.

    5. Obecność pliku cookie użytkownika bez pliku cookie walidacji CSRF wskazuje na fałszerstwo.

    6. Obecność pliku cookie użytkownika zapewnia, że ​​aktualne żądanie jest wysyłane za pośrednictwem przeglądarki. (Zakładając, że użytkownik nie wprowadziłby swoich danych uwierzytelniających na nieznanej stronie internetowej i zakładając, że nie obchodzi nas, że użytkownicy używają własnych poświadczeń do wysyłania żądań po stronie serwera). Jeśli mamy również plik cookie walidacji CSRF, to ten plik cookie walidacji CSRF otrzymane również za pomocą przeglądarki. Następnie, jeśli mamy również token CSRF z ważnym podpisem, alosowa liczba w pliku cookie walidacji CSRF jest zgodna z numerem w tym tokenie CSRF, wówczas kod JavaScript dla tego tokenu został również odebrany podczas tego samego wcześniejszego żądania, podczas którego ustawiono plik cookie CSRF, a więc również przy użyciu przeglądarki. Oznacza to również, że powyższy kod JavaScript został wykonany przed ustawieniem tokenu i że w tym czasie domena była ważna dla danego klucza API.

      A więc: serwer może teraz bezpiecznie używać klucza API z podpisanego tokena.

    7. Jeśli w dowolnym momencie serwer nie ufa żądaniu, zwracany jest błąd 403 Forbidden. Widżet może na to zareagować, wyświetlając użytkownikowi ostrzeżenie.

Podpisywanie pliku cookie walidacji CSRF nie jest wymagane, ponieważ porównujemy go z podpisanym tokenem CSRF. Brak podpisania pliku cookie sprawia, że ​​każde żądanie HTTP jest krótsze, a walidacja serwera jest nieco szybsza.

Wygenerowany token CSRF jest ważny przez czas nieokreślony, ale tylko w połączeniu z plikiem cookie walidacji, a więc efektywnie do momentu zamknięcia przeglądarki.

Moglibyśmy ograniczyć czas życia podpisu tokena. Możemy usunąć plik cookie walidacji CSRF po wylogowaniu użytkownika, aby spełnić zalecenie OWASP . Aby nie udostępniać losowej liczby na użytkownika wielu partnerom, można dodać klucz API do nazwy pliku cookie. Ale nawet wtedy nie można łatwo odświeżyć pliku cookie walidacji CSRF, gdy żądany jest nowy token, ponieważ użytkownicy mogą przeglądać tę samą witrynę w wielu oknach, współużytkując jeden plik cookie (który podczas odświeżania byłby aktualizowany we wszystkich oknach, po czym Token JavaScript w innych oknach nie będzie już pasował do tego pojedynczego pliku cookie).

Dla tych, którzy używają OAuth, zobacz także OAuth i Widżety po stronie klienta , z których wziąłem pomysł na JavaScript. W przypadku korzystania z interfejsu API po stronie serwera , w którym nie możemy polegać na kodzie JavaScript w celu ograniczenia domeny, używamy tajnych kluczy zamiast publicznych kluczy API.

Arjan
źródło
1
Podczas korzystania CORS, może można bezpiecznie rozszerzyć to. Zamiast tego, podczas obsługi OPTIONSżądania wysłanego z wyprzedzeniem z pewnym publicznym kluczem API w adresie URL, serwer może poinformować przeglądarkę, które domeny są dozwolone (lub anulować żądanie). Uważaj jednak, że niektóre żądania nie wymagają żądania przed wysłaniem lub w ogóle nie używają CORS , a CORS wymaga IE8 +. Jeśli w IE7 używana jest jakaś rezerwa Flash, być może jakaś dynamika crossdomain.xmlpomoże osiągnąć to samo. Nie próbowaliśmy jeszcze CORS / Flash.
Arjan
11

To pytanie ma akceptowaną odpowiedź, ale dla wyjaśnienia, wspólne tajne uwierzytelnianie działa w następujący sposób:

  1. Klient ma klucz publiczny, można go udostępnić każdemu, nie ma to znaczenia, więc możesz go osadzić w javascript. Służy do identyfikacji użytkownika na serwerze.
  2. Serwer ma tajny klucz i ten sekret MUSI być chroniony. Dlatego uwierzytelnianie za pomocą klucza współdzielonego wymaga, abyś mógł chronić swój tajny klucz. Tak więc publiczny klient javascript, który łączy się bezpośrednio z inną usługą, nie jest możliwy, ponieważ potrzebujesz pośrednika serwera do ochrony sekretu.
  3. Serwer podpisuje żądanie za pomocą jakiegoś algorytmu, który zawiera tajny klucz (tajny klucz jest czymś w rodzaju soli), a najlepiej znacznikiem czasu, a następnie wysyła żądanie do usługi. Sygnatura czasowa ma zapobiec atakom typu „powtórka”. Podpis żądania jest ważny tylko przez około n sekund. Możesz to sprawdzić na serwerze, pobierając nagłówek znacznika czasu, który powinien zawierać wartość znacznika czasu zawartego w podpisie. Jeśli ten znacznik czasu wygasł, żądanie nie powiedzie się.
  4. Usługa otrzymuje żądanie zawierające nie tylko podpis, ale także wszystkie pola, które zostały podpisane zwykłym tekstem.
  5. Następnie usługa podpisuje żądanie w ten sam sposób, używając wspólnego tajnego klucza i porównuje podpisy.
chris
źródło
To prawda, ale zgodnie z projektem Twoja odpowiedź nie ujawnia klucza API. Jednak w niektórych interfejsach API klucz API jest publicznie widoczny i właśnie o to chodziło: „żądania do operacji usługi resztowej [...] wykonane przez javascript (XHR / Ajax)” . (Wydaje mi się, że przyjęta odpowiedź jest błędna; twój punkt 2 jest jasny, dobrze.)
Arjan,
2

Postaram się odpowiedzieć na to pytanie w jego oryginalnym kontekście. Pytanie brzmi więc „Czy klucz tajny (API) jest bezpieczny do umieszczenia w JavaScript.

Moim zdaniem jest to bardzo niebezpieczne, ponieważ niweczy cel uwierzytelniania między systemami. Ponieważ klucz zostanie ujawniony użytkownikowi, użytkownik może odzyskać informacje, do których nie jest upoważniony. Ponieważ w typowej spoczynku uwierzytelnianie komunikacji opiera się wyłącznie na kluczu API.

Moim zdaniem rozwiązaniem jest to, że wywołanie JavaScript zasadniczo przekazuje żądanie do wewnętrznego komponentu serwera, który jest odpowiedzialny za wykonanie połączenia resztowego. Wewnętrzny komponent serwera, powiedzmy, że serwlet odczyta klucz API z zabezpieczonego źródła, takiego jak system plików oparty na uprawnieniach, wstawi go do nagłówka HTTP i wykona zewnętrzne wywołanie rest.

Mam nadzieję, że to pomoże.

MG Developer
źródło
1

Przypuszczam, że masz na myśli klucz sesji, a nie klucz API. Ten problem jest dziedziczony z protokołu http i znany jako przechwytywanie sesji . Normalnym „obejściem” jest zmiana na https, jak w każdej witrynie internetowej.

Aby bezpiecznie uruchomić usługę REST, musisz włączyć protokół https i prawdopodobnie uwierzytelnianie klienta. Ale w końcu to wykracza poza ideę REST. REST nigdy nie mówi o bezpieczeństwie.

Peter Mmm
źródło
8
Właściwie miałem na myśli klucz. Jeśli dobrze pamiętam, aby użyć API, przekazujesz klucz API i sekret do reszty usługi w celu uwierzytelnienia, prawda? Wiem, że kiedy zostanie przesłany przez sieć, zostanie zaszyfrowany przez SSL, ale zanim zostanie wysłany, jest to doskonale widoczne po kodzie klienta, który go używa ...
tjans
1

To, co chcesz zrobić po stronie serwera, to wygenerowanie identyfikatora wygasającej sesji, który jest wysyłany z powrotem do klienta podczas logowania lub rejestracji. Klient może następnie użyć tego identyfikatora sesji jako wspólnego hasła do podpisywania kolejnych żądań.

Identyfikator sesji jest przekazywany tylko raz i MUSI być za pośrednictwem protokołu SSL.

Zobacz przykład tutaj

Użyj numeru nonce i sygnatury czasowej podczas podpisywania żądania, aby zapobiec przejęciu sesji.

Iain Porter
źródło
1
Ale jak może być logowanie, gdy strona trzecia korzysta z Twojego interfejsu API? Jeśli użytkownik ma się zalogować, sprawa jest prosta: po prostu skorzystaj z sesji? Ale kiedy inne witryny muszą uwierzytelniać się w Twoim interfejsie API, to nie pomaga. (Poza tym pachnie to trochę jak promowanie twojego bloga.)
Arjan