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
- Identyfikatory powinny być globalnie unikalne (lub przynajmniej unikalne w systemie)
- Wygenerowane na kliencie (tj. Przez javascript w przeglądarce)
- Bezpieczne (jak opisano powyżej i inaczej)
- Dane mogą być przeglądane / edytowane przez wielu użytkowników, w tym użytkowników, którzy ich nie napisali
- 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:
- Zidentyfikuj to jako akcję „wstaw” (tj. Nie aktualizację ani usunięcie)
- Sprawdź, czy obie części klucza złożonego są poprawnymi identyfikatorami
- 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)
- 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?
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.
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.
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
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 )
Czy brakuje mi luk, które mogłyby pozwolić złośliwemu użytkownikowi na złamanie zabezpieczeń systemu?
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 )
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.
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.
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.
źródło
Odpowiedzi:
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ą.
źródło
Musisz oddzielić dwie kwestie:
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).
źródło