Strategia generowania unikalnych i bezpiecznych identyfikatorów do użycia w aplikacji internetowej „czasami offline”

47

Mam projekt internetowy, który pozwala użytkownikom pracować zarówno w trybie online, jak i offline, i szukam sposobu na wygenerowanie unikalnych identyfikatorów dla rekordów po stronie klienta. Chciałbym, aby podejście, które działa, gdy użytkownik jest offline (tzn. Nie może rozmawiać z serwerem), jest gwarantowane, że jest unikalne i bezpieczne. Poprzez „bezpieczne” szczególnie martwię się o to, że klienci przesyłają duplikaty identyfikatorów (złośliwie lub w inny sposób) i tym samym sieją spustoszenie w integralności danych.

Robiłem trochę google, mając nadzieję, że to już rozwiązany problem. Nie znalazłem niczego, co byłoby bardzo definitywne, szczególnie jeśli chodzi o podejścia stosowane w systemach produkcyjnych. Znalazłem kilka przykładów systemów, w których użytkownicy będą uzyskiwać dostęp tylko do danych, które utworzyli (np. Lista czynności do wykonania dostępna na wielu urządzeniach, ale tylko przez użytkownika, który je utworzył). Niestety potrzebuję czegoś bardziej wyrafinowanego. Znalazłem tutaj naprawdę dobre pomysły , które są zgodne z tym, jak myślałem, że wszystko może działać.

Poniżej znajduje się moje proponowane rozwiązanie.

Niektóre wymagania

  1. Identyfikatory powinny być globalnie unikalne (lub przynajmniej unikalne w systemie)
  2. Wygenerowane na kliencie (tj. Przez javascript w przeglądarce)
  3. Bezpieczne (jak opisano powyżej i inaczej)
  4. Dane mogą być przeglądane / edytowane przez wielu użytkowników, w tym użytkowników, którzy ich nie napisali
  5. Nie powoduje znaczących problemów z wydajnością baz danych zaplecza (takich jak MongoDB lub CouchDB)

Proponowane rozwiązanie

Gdy użytkownicy utworzą konto, otrzymają identyfikator użytkownika wygenerowany przez serwer, o którym wiadomo, że jest unikalny w systemie. Ten identyfikator NIE może być taki sam jak token uwierzytelnienia użytkownika. Nazwijmy ten identyfikator „tokenem” użytkowników.

Kiedy użytkownik tworzy nowy rekord, generuje nowy identyfikator użytkownika w javascript (generowany przy użyciu window.crypto, jeśli jest dostępny. Zobacz przykłady tutaj ). Ten identyfikator jest łączony z „tokenem id”, który użytkownik otrzymał podczas tworzenia konta. Ten nowy złożony identyfikator (token id po stronie serwera + identyfikator użytkownika po stronie klienta) jest teraz unikalnym identyfikatorem rekordu. Gdy użytkownik jest w trybie online i przesyła ten nowy rekord do serwera zaplecza, serwer:

  1. Zidentyfikuj to jako akcję „wstaw” (tj. Nie aktualizację ani usunięcie)
  2. Sprawdź, czy obie części klucza złożonego są poprawnymi identyfikatorami
  3. Sprawdź, czy podana część „tokenu identyfikatora” złożonego identyfikatora jest poprawna dla bieżącego użytkownika (tzn. Pasuje do tokenu identyfikatora, który serwer przypisał użytkownikowi podczas tworzenia konta)
  4. Jeśli wszystko jest copasetic, wstawić dane do db (uważając, aby zrobić wkładkę, a nie „upsert” tak, że jeśli id nie istnieje już nie zaktualizować istniejący rekord przez pomyłkę)

Zapytania, aktualizacje i usuwanie nie wymagałyby żadnej specjalnej logiki. Po prostu użyliby identyfikatora do zapisu w taki sam sposób, jak tradycyjne aplikacje.

Jakie są zalety tego podejścia?

  1. Kod klienta może tworzyć nowe dane w trybie offline i natychmiast znać identyfikator tego rekordu. Rozważałem alternatywne podejścia, w których tymczasowy identyfikator byłby generowany na kliencie, który później zostałby zamieniony na „ostateczny” identyfikator, gdy system był online. To jednak wydawało się bardzo kruche. Zwłaszcza, gdy zaczniesz myśleć o tworzeniu danych podrzędnych z kluczami obcymi, które również wymagają aktualizacji. Nie wspominając już o obsłudze adresów URL, które zmieniłyby się, gdy zmienił się identyfikator.

  2. Tworząc identyfikatory złożone z wartości generowanej przez klienta ORAZ wartości generowanej przez serwer, każdy użytkownik skutecznie tworzy identyfikatory w piaskownicy. Ma to na celu ograniczenie szkód, które może wyrządzić złośliwy / nieuczciwy klient. Ponadto wszelkie kolizje identyfikatorów dotyczą poszczególnych użytkowników, a nie dotyczą całego systemu.

  3. Ponieważ token identyfikatora użytkownika jest powiązany z jego kontem, identyfikatory mogą być generowane w piaskownicy użytkowników tylko przez klientów, którzy są uwierzytelnieni (tj. W przypadku, gdy użytkownik pomyślnie się zalogował). Ma to na celu powstrzymanie złośliwych klientów przed tworzeniem złych identyfikatorów dla użytkownika. Oczywiście, jeśli token autoryzacyjny użytkownika zostanie skradziony przez złośliwego klienta, mogą zrobić złe rzeczy. Ale po kradzieży tokenu uwierzytelniającego konto i tak zostaje przejęte. W przypadku, gdy tak się stanie, wyrządzone szkody będą ograniczone do przejętego konta (nie do całego systemu).

Obawy

Oto niektóre z moich obaw związanych z tym podejściem

  1. Czy wygeneruje to wystarczająco unikalne identyfikatory dla aplikacji na dużą skalę? Czy istnieje powód, by sądzić, że spowoduje to kolizje identyfikatorów? Czy javascript może wygenerować wystarczająco losowy identyfikator UUID, aby to zadziałało? Wygląda na to, że window.crypto jest dość powszechnie dostępny, a ten projekt wymaga już dość nowoczesnych przeglądarek. ( to pytanie ma teraz osobne pytanie SO )

  2. Czy brakuje mi luk, które mogłyby pozwolić złośliwemu użytkownikowi na złamanie zabezpieczeń systemu?

  3. Czy istnieje powód do niepokoju o wydajność DB podczas wyszukiwania klucza złożonego złożonego z 2 identyfikatorów użytkownika? Jak należy przechowywać ten identyfikator, aby uzyskać najlepszą wydajność? Dwa oddzielne pola czy jedno pole obiektowe? Czy byłoby inne „najlepsze” podejście do gry Mongo vs. Couch? Wiem, że posiadanie niesekwencyjnego klucza podstawowego może powodować znaczące problemy z wydajnością podczas wstawiania. Czy byłoby mądrzej mieć automatycznie wygenerowaną wartość klucza podstawowego i przechowywać ten identyfikator jako osobne pole? ( to pytanie ma teraz osobne pytanie SO )

  4. Dzięki tej strategii łatwo byłoby ustalić, że dany zestaw rekordów został utworzony przez tego samego użytkownika (ponieważ wszyscy mieliby ten sam publicznie widoczny token identyfikatora). Chociaż nie widzę w tym żadnych bezpośrednich problemów, zawsze lepiej nie ujawniać więcej informacji na temat szczegółów wewnętrznych niż jest to potrzebne. Inną możliwością byłoby mieszanie klucza złożonego, ale wydaje się, że może to być bardziej kłopotliwe niż warte.

  5. W przypadku kolizji identyfikatora dla użytkownika nie ma prostego sposobu na odzyskanie. Podejrzewam, że klient mógł wygenerować nowy identyfikator, ale wydaje się, że to dużo pracy dla przypadku krawędzi, który tak naprawdę nigdy nie powinien się wydarzyć. Mam zamiar zostawić to bez adresu.

  6. Tylko uwierzytelnieni użytkownicy mogą przeglądać i / lub edytować dane. Jest to dopuszczalne ograniczenie dla mojego systemu.

Wniosek

Czy powyżej rozsądnego planu? Zdaję sobie sprawę, że niektóre z nich sprowadzają się do wezwania do oceny opartego na pełniejszym zrozumieniu danej aplikacji.

herbrandson
źródło
Myślę, że to pytanie może cię zainteresować stackoverflow.com/questions/105034/... To też czyta mi się jak GUID, ale nie wydają się być rodzime w javascript
Rémi
2
Uderza mnie, że UUID już spełniają 5 wymienionych wymagań. Dlaczego są niewystarczające?
Gabe
@Gabe Zobacz moje komentarze do posta
lie ryans
meta dyskusja na to pytanie: meta.stackoverflow.com/questions/251215/…
gnat
„złośliwy / rouge client” - nieuczciwy.
David Conrad,

Odpowiedzi:

4

Twoje podejście zadziała. Wiele systemów zarządzania dokumentami stosuje takie podejście.

Jedną rzeczą do rozważenia jest to, że nie musisz używać zarówno identyfikatora użytkownika, jak i losowego identyfikatora elementu jako części łańcucha. Zamiast tego możesz zaszyfrować konkatynację obu. To da ci krótszy identyfikator i możliwe inne korzyści, ponieważ wynikowe identyfikatory będą bardziej równomiernie rozłożone (lepiej zrównoważone pod względem indeksowania i przechowywania plików, jeśli przechowujesz pliki w oparciu o ich identyfikator użytkownika).

Inną opcją jest wygenerowanie tylko tymczasowego UUID dla każdego elementu. Następnie po nawiązaniu połączenia i opublikowaniu ich na serwerze serwer generuje (gwarantowane) identyfikator UUID dla każdego elementu i zwraca go do Ciebie. Następnie zaktualizuj kopię lokalną.

Grandmaster B.
źródło
2
Rozważałem użycie skrótu 2 jako identyfikatora. Jednak nie wydawało mi się, że istnieje odpowiedni sposób na wygenerowanie sha256 we wszystkich przeglądarkach, które muszę obsługiwać :(
herbrandson
12

Musisz oddzielić dwie kwestie:

  1. Generowanie identyfikatora: klient musi być w stanie wygenerować unikalny identyfikator w systemie rozproszonym
  2. Kwestia bezpieczeństwa: klient MUSI mieć prawidłowy token uwierzytelnienia użytkownika ORAZ token uwierzytelnienia jest ważny dla tworzonego / modyfikowanego obiektu

Rozwiązania tych dwóch problemów są niestety osobne; ale na szczęście nie są niezgodne.

Obawy związane z generowaniem identyfikatorów można łatwo rozwiązać, generując za pomocą UUID, do czego przeznaczony jest UUID; obawy dotyczące bezpieczeństwa wymagałyby jednak sprawdzenia na serwerze, czy dany token uwierzytelnienia jest autoryzowany do operacji (tj. jeśli token uwierzytelnienia jest dla użytkownika, który nie posiada niezbędnych uprawnień do tego konkretnego obiektu, MUSI to być odrzucone).

Przy prawidłowej obsłudze kolizja tak naprawdę nie stanowiłaby problemu bezpieczeństwa (użytkownik lub klient zostanie po prostu poproszony o ponowienie operacji z innym UUID).

Lie Ryan
źródło
To naprawdę dobry punkt. Być może to wszystko, co jest wymagane, i zastanawiam się nad tym. Mam jednak kilka obaw związanych z tym podejściem. Największe jest to, że uuids generowane przez javascript nie wydają się tak losowe, jak można by się spodziewać (zobacz zatrzymania w komentarzach na stackoverflow.com/a/2117523/13181 ). Wygląda na to, że window.crypto powinien rozwiązać ten problem, ale nie wydaje się to być dostępne we wszystkich wersjach przeglądarki, które muszę obsługiwać.
herbrandson
ciąg dalszy ... Podoba mi się twoja sugestia dodania kodu w kliencie, który zregenerowałby nowy identyfikator użytkownika w przypadku kolizji. Wydaje mi się jednak, że to ponownie przedstawia obawy, które miałem w swoim poście w punkcie 1 sekcji „Jakie są zalety tego podejścia”. Myślę, że gdybym poszedł tą drogą, lepiej byłoby wygenerować tymczasowe identyfikatory po stronie klienta, a następnie zaktualizować je o „końcowy identyfikator” z serwera po podłączeniu
herbrandson
kontynuowano ponownie ... Co więcej, zezwalanie użytkownikom na przesyłanie własnych unikatowych identyfikatorów wydaje się budzić obawy dotyczące bezpieczeństwa. Być może rozmiar UUID i wysoka statystyczna nieprawdopodobieństwo kolizji są wystarczające, aby złagodzić ten problem sam w sobie. Nie jestem pewny. Mam dokuczliwe podejrzenie, że trzymanie każdego użytkownika we własnej „piaskownicy” jest w tym przypadku dobrym pomysłem (tzn. Nie ufaj wkładowi użytkownika).
herbrandson
@herbrandson: Nie ma problemu z bezpieczeństwem, który mógłbym wymyślić, pozwalając użytkownikom na generowanie własnego unikalnego identyfikatora, o ile zawsze sprawdzasz, czy użytkownik ma uprawnienia do operacji. ID to po prostu coś, co można wykorzystać do identyfikacji obiektów, tak naprawdę nie ma znaczenia, jaka jest jego wartość. Jedyną potencjalną szkodą jest to, że użytkownik może zarezerwować zakres identyfikatorów na własny użytek, ale tak naprawdę nie stanowi to żadnego problemu dla systemu jako całości, ponieważ inni użytkownicy równie mało prawdopodobne są do uzyskania tych wartości przez przypadek.
Lie Ryan
Dziękuję za twój komentarz. Naprawdę zmusiło mnie to do wyjaśnienia mojego myślenia! Jest powód, dla którego uważałem na twoje podejście i zapomniałem o tym po drodze :). Mój strach jest związany ze słabym RNG w wielu przeglądarkach. Do generowania UUID wolałby silnie kryptograficznie RNG. Wiele nowszych przeglądarek ma to za pośrednictwem window.crypto, ale starsze przeglądarki nie. Z tego powodu złośliwy użytkownik może dowiedzieć się o ziarnie innego użytkownika RNG i tym samym poznać następny identyfikator użytkownika, który zostanie wygenerowany. Jest to część, która wydaje się być atakowana.
herbrandson