2015-05-16 17 views
6

Wyobraźmy sobie problem: Mam usługę REST, która jest implementowana za pomocą technologii Java/MySQL/Spring i HTTP/JSON. Klientami usługi REST są aplikacje mobilne. Jest więc możliwe, że ktoś zdekompiluje kod i otrzyma interfejs API usługi REST. (Tak, kod jest zaciemniony itp., Ale tak).Warunki usługi i wyścigu REST

Problem: istnieje metoda POST wysyłania pieniędzy do innego użytkownika aplikacji. Martwię się, że ktoś może uzyskać API, napisać bota i wysłać żądanie POST 500 lub 5000 lub nawet 50 000 razy na sekundę. W rezultacie może wysłać więcej pieniędzy niż ma, ponieważ jeśli 1000 zgłoszeń jest przetwarzanych jednocześnie, sprawdzanie salda może być udane dla wszystkich 1000 zgłoszeń, jednak prawdziwa kwota na koncie może być wystarczająca tylko dla, powiedzmy, 50 wniosków.

Zasadniczo bardziej przypomina standardową "rasę" z wieloma wątkami. Problem polega na tym, że mam wiele serwerów i nie są one ze sobą powiązane. Tak więc, 300 żądań może przyjść do serwera A, 300 żądań może przyjść do serwera B, a żądania reszty mogą przyjść na serwer C.

Najlepszym pomysłem jaki mam, jest użycie czegoś takiego jak "WYBIERZ ... DO AKTUALIZACJI" i synchronizuj na poziomie bazy danych. Chciałbym jednak rozważyć inne rozwiązania.

Wszelkie pomysły lub sugestie?

+1

Nie masz loginy, sesje i anty-CSRF tokeny w celu zapewnienia, że ​​żądania transferu może pochodzić tylko z zalogowany, Autoryzowany użytkownik? Czy nie masz kontroli autoryzacyjnych, aby upewnić się, że tylko żądania przekazania własnych pieniędzy są przestrzegane? Czy nie masz aplikacji trójwarstwowej, więc interfejs obsługuje tylko warstwę prezentacji, a logika biznesowa jest obsługiwana za kulisami? Czy nie masz możliwości segmentowania przetwarzania dla podobnych żądań (ten sam donator, ten sam cel itp.) Na jednym serwerze logiki biznesowej? – atk

+0

W jaki sposób logowanie/sesje mogą temu zapobiec? Jeśli ktoś włamie się do API, może zhackować logowanie/sesję i wysłać to żądanie przy użyciu prawidłowego mechanizmu uwierzytelniania. Nawiasem mówiąc, uwierzytelnianie jest oparte na tokenie, tj. OAuth. Jest to usługa REST i nie używam tokenów csrf. – user3489820

+0

Jeśli ludzie logują się i ograniczają rzeczy, aby ludzie mogli wydawać tylko własne pieniądze, zapobiegają atakom, w których osoba atakująca wyda pieniądze innej osoby. Nie przeszkadza im to w nadmiernym robieniu własnego konta, ale uniemożliwia to nadpisanie * konta innej osoby *. – atk

Odpowiedz

2

Masz kilka opcji:

  1. polegać na realizacji ACID bazy danych (MySQL w Twoim przypadku). Zakładając, że korzystasz z silnika InnoDB, musisz wybrać właściwy poziom izolacji transakcji (SET TRANSACTION syntax) w połączeniu z odpowiednim mechanizmem blokowania odczytów (SELECT ... FOR UPDATE and SELECT ... LOCK IN SHARE MODE Locking Reads). Musisz dobrze zrozumieć te koncepcje, aby dokonać właściwego wyboru. Może się zdarzyć, że samo użycie właściwego poziomu izolacji zapobiegnie już wyścigowi nawet bez odczytu blokującego. Wady są niezgodne z skalowalnością i wiązaniem aplikacji z bazą danych RDBMS, więc na przykład trudniej będzie przenieść się do NoSQL.

  2. Rozwiń swój koniec z powrotem do warstwy sieci i warstwy usług (opcja sugerowana przez atk w komentarzach). Pozwoli to na skalowanie instancji warstwy sieci niezależnie, zachowując jednocześnie jedną instancję warstwy usług. Posiadanie pojedynczej instancji warstwy usług umożliwia korzystanie z mechanizmów synchronizacji Java, takich jak bloki synchronised lub ReadWriteLock. Chociaż to rozwiązanie zadziała, nie polecam go, ponieważ zmniejsza skalowalność warstwy usług.

  3. To jest ulepszenie poprzedniej opcji. Zamiast wbudowanych mechanizmów synchronizacji Java można użyć Distributed lock manager. Pozwoli to na skalowanie warstwy sieci i poziomu usług niezależnie.

+0

Dziękuję za odpowiedź :) Menedżer zamka rozproszonego może być złym pomysłem na skalowalność. Postaram się grać z RDBMS. – user3489820

0

W przypadku aplikacji o znaczeniu krytycznym najlepiej jest mieć wiele poziomów mechanizmów blokujących.

"SELECT ... FOR UPDATE" to dobry sposób na zrobienie tego, ale są one dość drogie, a kiedy spróbujesz zbombardować to z Charlesem, zobaczysz, że twój górny stos API będzie cierpieć, i ten prosty mechanizm dość łatwo okaleczy twoją infrastrukturę, podobnie jak wydarzenie DDoS.

Najpierw zaimplementuj go w warstwie Load Balancer/Proxy, aby zmniejszyć liczbę żądań na określone interwały z jednego adresu IP.

Następnie zastosuj blokadę współdzielonej warstwy pamięci podręcznej, w której wszystkie skrzynki są synchronizowane na niektórych klawiszach w zależności od tego, która krytyczna transakcja ma zostać zablokowana. Na przykład możesz użyć funkcji Redis GETSET lub INCR do atomistycznego ustawienia flagi przed wejściem do krytycznej ścieżki kodu. Odrzuć cokolwiek innego, aby uniknąć sytuacji, w której ci źli aktorzy będą trzymać się procesora/pamięci.

Możesz także zaimplementować takie rzeczy, jak pamięć podręczna APC (przed naciśnięciem klastra Redis/Memcache), aby wykonać podobne blokowanie w zależności od skrzynki. Jest to szybsze, ponieważ nie ma opóźnień w sieci.

Powyższy są niezbędne na górze za pomocą „SELECT ... FOR UPDATE”

Powiązane problemy