SignalR w ASP.NET Core i Angular – przykładowa implementacja

Niedawno opublikowałem wpis na temat instalacji biblioteki SignalR w projekcie ASP.NET Core z wykorzystaniem Angulara. Dzisiaj chciałem kontynuować ten temat na przykładzie z mojego projektu. Zademonstruję działającą implementację po stronie serwera.

Link do poprzedniego wpisu: SignalR w ASP.NET Core – instalacja i uruchomienie

Konfiguracji ciąg dalszy

Chociaż konfiguracja jaką podałem w ostatnim wpisie wystarczała do działania, w moim rozwiązaniu musiałem dodać jeszcze kilka ustawień. Na pierwszy ogień poszedł CamelCasePropertyNamesContractResolver, dobrze znany wszystkim, którzy tworzyli kiedyś API w Asp.Net. Dzięki niemu, obiekty zwracane są z API w notacji CamelCase. Jest to domyślne ustawienie w .NET Core, ale jako że my mamy podpiętą bibliotekę ze starszej wersji frameworka, musimy ustawić to ręcznie. Przykładowa implementacja poniżej.

Następnie rejestrujemy nasz serializator w pliku Startup.cs.

Rozsyłanie eventów z poziomu backendu aplikacji

Wysłanie zdarzenia z serwera może odbyć w dwóch przypadkach:

  • Bezpośrednio z naszej klasy, dziedziczącej po Hub, w momencie gdy klient wywołuje zdalną metodę na serwerze
  • Po wywołaniu zwykłej metody HTTP w API, pośrednio poprzez Hub

O ile w pierwszym przypadku jest to bardzo proste, to ten drugi wymaga dodatkowego nakładu pracy. Załóżmy że nasz Hub ma tylko jedną metodę.

Klient, w momencie gdy wywoła metodę Subscribe, zapisze się do grupy odpowiadającej identyfikatorowi gry. Zwrócone mu zostanie id jego połączenia. Wewnątrz klasy Broadcaster możemy wysłać zdarzenie o nowej subskrypcji do wszystkich klientów. Taki przykład podałem w poprzednim poście, dlatego tutaj to pomijam (nie jest to potrzebne w moim przypadku). Efekt który chcemy tutaj osiągnąć to wysłanie do każdego klienta informacje o tym, że gracz wykonał ruch. Jako przykład posłuży tutaj zakończenie tury przez gracza.

Podczas wywołania tej akcji kontrolera, wywołujemy metodę SkipRound. Wewnątrz niej, po tym jak nastąpi zmiana kolejki, chcielibyśmy poinformować wszystkich graczy o tym fakcie. Nasz Hub to nie jest zwykła klasa, którą można po prostu wstrzyknąć do naszych obiektów. W tym celu musimy odwołać się do klasy ConnectionManager dostarczonej przez SignalR.

Dodałem klasę EventBroadcaster, która będzie odpowiedzialna za przekazywanie informacji do klientów bezpośrednio z serwera. Posiada ona obiekt HubContext, który umożliwia takie działania. Dostępny jest po wywołaniu metody GlobalHost.ConnectionManager.GetHubContext. Ponieważ jej wywołanie jest dosyć czasochłonne, klasę EventBroadcaster rejestrujemy w systemie jako singleton.

Dzięki temu, w dowolne miejsce aplikacji możemy dodać obiekt implementujący IEventBroadcaster i wysyłać do klientów interesujące nas zdarzenia. Przykład poniżej.

Podsumowanie

Dzisiaj zademonstrowałem przykładową implementację rozsyłania zdarzeń w aplikacji po stronie serwera aplikacji. W kolejnym wpisie zajmiemy się implementacją po stronie klienta w Angularze. Dzięki za uwagę i zapraszam ponownie!

Zabezpieczenie WebAPI w ASP.NET Core – Wprowadzenie

Dzisiaj chciałbym w skrócie omówić jak zabezpieczyć nasze API, które napisane jest w .NET Core. Jeżeli chcemy mówić o bezpieczeństwie aplikacji, musimy poznać i zrozumieć dwa bardzo ważne pojęcia, które często są ze sobą mylone:

  • Authentication, czyli uwierzytelnianie, to proces podczas którego sprawdzamy, czy dana osoba jest rzeczywiście tym za kogo się podaje.
  • Authorization, czyli autoryzacja, polega na ustaleniu, czy podmiot ma dostęp do zasobu po który wysłał żądanie.

Bardziej na ten temat nie będę się rozpisywał, zachęcam do bardziej dogłębnej lektury, po przeczytaniu mojego wpisu oczywiście.

Zło konieczne

W wielkich systemach zabezpieczenia to sprawa oczywista: finanse, adresy etc. Nikomu nie trzeba tłumaczyć, że te dane pod żadnym pozorem nie mogą wpaść w niepowołane ręce. Jednak rozwijając małą aplikację można sobie pomyśleć: „Po co mi zabezpieczenia, to tylko mały projekt po godzinach, kto chciałby mi się tutaj włamywać?”. Takie myślenie może być zgubne. W sieci grasuje wiele niebezpieczeństw i nawet, jeżeli nie mamy żadnych danych poufnych, to po prostu możemy paść ofiarą zwykłej złośliwości i utracić cenne dla nas dane. Tak mi się skojarzyło z obrazkiem poniżej, za każdym razem jak będziesz odczuwał pokusę udostępniania API bez zabezpieczeń, przypomnij sobie słowa Melisandre.

Satyryczny obrazek postaci z serialu Gra o Tron, ostrzegający o tym że "Internet jest ciemny i pełny strachu"

„Internet jest ciemny i pełny strachu”

Zdając sobie sprawę z zagrożeń o których wspomniałem, przystąpiłem do implementacji mojego API. Osobiście uważam, że jest to bardzo nudna część projektu, zło konieczne z którym trzeba się pogodzić. Cytując słowa Scotta Hanselmana: Shave the Yak, co oznacza mniej więcej tyle, że musimy się uporać z mniejszym problem, żebyśmy mogli przejść do konkretów.

Implementacja JWT Tokens

Na szczęście, do dyspozycji mamy wiele narzędzi które ułatwiają nam życie. Ja moje API zabezpieczam przy użyciu Tokenów JWT. Na razie przedstawię podejście minimalistyczne, w którym będziemy korzystali z indywidualnych kont użytkowników. W przyszłości chcę wykorzystać Social Login, czyli po prostu stare dobre Zaloguj się poprzez fejsbuka, gdzie oczywiście zamiast tego można wykorzystywać także Google, Twittera czy innego GitHub’a. No ale przejdźmy już do konkretów.

Na wstępie musimy dodać bazę danych, gdzie przechowywać będziemy użytkowników. Ja wykorzystam do tego Entity Framework. Dane konfiguracyjne przechowuje w plikach, które nie są wrzucone do repozytorium na GitHubie, a do ich odczytu dodałem własną klasę.

Teraz definiujemy z jakich narzędzi będziemy korzystać. Dzieje się to w pliku Startup.cs, w metodzie ConfigureServices. Najpierw konfigurujemy wykorzystanie systemu identyfikacji Identity dostępnego w paczce Microsoft.AspNetCore.Identity. W tym samym miejscu definiujemy, że do tego cely wykorzystamy właśnie Entity Framework.

Następnie konfigurujemy to, w jaki sposób aplikacja będzie uwierzytelniała użytkowników. Oto fragment tej metody:

UseJwtBearerTokenAuthentication to extension method, który dodałem w celu odchudzenia pliku Startup.cs. To dopiero początek pracy nad projektem, a ten już rośnie w niebezpiecznym tempie. Dlatego staram się wydzielać część konfiguracji do innych plików. Implementacja wspomnianej metody:

Tutaj wprowadzamy nasz tajny klucz, który służy do szyfrowania naszego tokena. Jak poprzednio, przechowuje go w pliku konfiguracyjnym. Definiujemy także to kto wystawia, oraz kto będzie konsumentem naszego tokena. Wszystko to w celu jak najlepszego zabezpieczenia naszej aplikacji.

Ostatnim etapem zabezpieczenia naszej aplikacji jest dodanie klasy, która będzie generowała nam nasze tokeny. Nie będę w całości kopiował tego kodu (kod dostępny w serwisie GitHub tutaj), a tylko kilka kluczowych elementów.

W sytuacji, gdy potwierdzimy tożsamość użytkownika, generujemy klucz, tak samo jak robiliśmy to podczas konfiguracji. Następnie przy pomocy algorytmu HmacSha256 będziemy szyfrować dane. Token zawiera podstawowe informacje o użytkowniku, a także zbiór jego uprawnień, Claims. Dokładniej o tym co zawiera taki token napiszę w innym poście, gdzie opowiem więcej o wspomnianych Claims.

Teraz wywołujemy tą klasę w kontrolerze i nasi użytkownicy mogą wysłać request o token.

Podsumowanie

Gdy przejdziemy przez kroki o których wspomniałem, otrzymamy gotowe rozwiązanie które pozwoli nam w pełni zabezpieczyć naszą aplikację. Nie wspomniałem tutaj o takich pojęciach jak np. CORS oraz SSL, ale myślę że będzie jeszcze ku temu okazja w kolejnych postach. Tymczasem zapraszam do komentowania oraz odwiedzenia mojego Twittera.

Przygotowanie API – REST, czy nie do końca?

Pracę nad moim projektem zacząłem od zadania sobie pytania: jak powinna wyglądać architektura mojego programu? I chociaż na temat samej architektury dodam oddzielny post, potrzebne będzie krótkie wprowadzenie. Głównym założeniem jest powstanie aplikacji dostępnej z poziomu przeglądarki, dzięki czemu będzie można uruchomić ją na komputerach PC oraz Mac, tabletach, a nawet smartfonach. Wiadomo jednak, że na urządzeniach przenośnych dużo wygodniej korzysta się z aplikacji natywnych. Tak powstał kolejny cel, czyli apka na urządzenia zaopatrzone w system Android. Nie postanowiłem jeszcze jak będzie ona wyglądać, ale skłaniam się ku rozwiązaniu w którym będzie ona miała ten sam kod (w miarę możliwości) co strona www, dzięki zastosowaniu rozwiązań takich jak np. Adobe PhoneGap. Jeżeli tak się sprawy mają, to naturalnym wnioskiem dla mnie jest to, że muszę przygotować jakieś API, które mój klient będzie mógł wykorzystać. Tutaj pojawia się kolejne pytanie, na które chcę dzisiaj odpowiedzieć: czy moje API powinno być RESTful, czy też nie? Może wystarczy podejście RPC.

REST czy RPC? I co to takiego?

W celu podjęcia decyzji, postaram się w skrócie opisać te dwa podejścia i wskazać czym się różnią. Oba z tych rozwiązań w tym kontekście operować będą na protokole HTTP. Zapytanie przy użyciu tego protokołu składa się z między innymi:

  • Metody (Mehtod) np. GET, HEAD, POST, która decyduje o tym jaką operację wykonamy na zasobie
  • Nagłówków (Header), w których dostępne są dodatkowe informacje o żądaniu np. dane pozwalające na uwierzytelnianie
  • Ciała zapytania (Content body), które przechowuje informacje np. o zasobie który chcemy dodać/edytować

Niektóre z metod są idempotentne (Idempotent) (z założenia, tak powinny być zaimplementowane), to znaczy że nie ważne ile razy je wykonamy w danych warunkach, zawsze zwrócą tej sam wynik (np. GET i PUT). Inną cechą niektórych metod jest to że są bezpieczne (Safe), czyli nie modyfikują one danych i nie posiadają skutków ubocznych np. GET i HEAD (tak jak poprzednio, w ten sposób powinny być implementowane).

REST, czyli Representational State Transfer to architektura pozwalająca na pisanie skalowalnych usług internetowych. Nie będę rozpisywał się na temat definicji pojęcia REST. Opisując w skrócie: udostępniamy zasoby na których możemy wykonywać różne operacje za pomocą protokołu HTTP. Rodzaj operacji zależy od tego, jakiej metody użyjemy podczas zapytania. Przykładowo, poniższe trzy zapytania będą miały różne wyniki:

  • GET www.example.com/api/objects/2
  • DELETE www.example.com/api/objects/2
  • PUT www.example.com/api/objects/2

W przypadku, gdy mówimy o REST, mamy na myśli podejście zorientowane na zasoby. Definiując adres zasobu używamy rzeczowników, a to jaką akcję wykonujemy nie jest wskazywane przez dodanie czasownika, lecz przez wybór metody HTTP.

RPC (Remote procedure call) ma wiele definicji, ale w tym znaczeniu chodzi o podejście, w którym zamiast operować na zasobach, mamy do czynienia z operacjami. Operacje te w swojej nazwie sugerują co będzie efektem ich wykonania. W przypadku implementacji tego podejścia za pomocą protokołu HTTP wykorzystuje się głównie dwie metody: GET oraz POST. Nie ma tutaj jasno sformułowanych zasad, ale zazwyczaj GET służy do pobierania danych lub akcji, które nie zmieniają żadnych danych, natomiast POST służy do pozostałych operacji. Przykładowe wywołanie takich zapytań:

  • GET www.example.com/api/GetAllObjects
  • POST www.example.com/api/CreateObject

Jeżeli wiemy już jak takie zapytania wyglądają i czym się charakteryzują, możemy przystąpić do porównania. Wezmę pod uwagę kilka czynników, które moim zdaniem są ważne podczas wyboru metody implementacji naszego API.

Projektowanie

Na początek weźmy pod uwagę to, w jak łatwy sposób można projektować nasze API. Jeżeli weźmiemy pod uwagę systemy, które już istnieją np. mamy napisaną już bibliotekę i chcemy w jakiś sposób wyprowadzić ją na świat, łatwiej będzie nam użyć podejścia RPC. Dzieje się tak z reguły dla tego, że większość kodu jaki piszemy jest zorientowany na operacje i łatwiej go w ten sposób odwzorować. Łatwiej też będzie rozwijać system, który głównie opiera się o różnego rodzaju procesy, gdzie jeden obiekt ciągle zmienia się wg jakiejś logiki. Wadą tego podejścia może być to, że ponieważ nie nakłada nam ona żadnych ograniczeń, projektant musi sam pilnować tego, żeby API było spójne i składało się w logiczną całość. Z drugiej strony, REST ułatwia projektowanie prostszych systemów gdzie głównym zadaniem są operacje typu CRUD. Nawet, jeżeli projektowanie API RESTowego może być trudniejsze, dzięki założeniom jakie ze sobą niesie łatwiej otrzymać spójne i logiczne zaplecze dla naszej aplikacji. Tutaj pytanie pozostaje bez odpowiedzi moim zdaniem, ponieważ wszystko zależy od kontekstu w jakim będziemy pracować.

Dokumentacja i jak łatwo ją przygotować

W obu przypadkach możemy wykorzystać narzędzia typu Swagger do udokumentowania naszego API, więc żadne podejście nie jest tutaj lepsze. Sama nazwa zasobu/operacji także może nam sporo powiedzieć o tym, co będzie efektem wywołania danego zapytania. GET www.example.com/api/GetAllObjects czy GET www.example.com/api/objects w jasny sposób mówi nam, co dostaniemy w odpowiedzi, więc można tutaj powiedzieć w pewnym stopniu o tym jak API dokumentuje się samo. Z doświadczenia wiadomo jednak, że takie rzeczy nie zdarzają się zbyt często i trzeba posiłkować się dodatkowymi źródłami wiedzy o naszym requescie.

Semantyka i używalność

W RPC nie jesteśmy w stanie przewidzieć tego, jak dana operacja będzie się nazywać. Ta sama operacja może przybierać taki wygląd:

  • GET/POST www.example.com/api/RemoveObject/1
  • GET/POST www.example.com/api/DeleteObject/1

Zdajemy się tutaj na to, jaki styl wybiera osoba odpowiedzialna za przygotowanie API. Można założyć, że będzie się ona trzymała swojego standardu i podczas usuwania innego rodzaju obiektu użyje tej samej nazwy zapytania. Jednak może się zdarzyć, że za każdym razem trzeba będzie zagłębiać się w dokumentację. REST jest bardziej przewidywalny, dzięki wykorzystywaniu metod HTTP. Nawet, jeżeli nie wiemy dokładnie co stanie się po wykonaniu danej metody, mamy ogólne pojęcie co może być efektem końcowym. W tym przypadku, mój głos dostaje podejście REST, chociaż nie musi zawsze tak być.

Obsługa Cache

Obsługa Cache to jedna z bardzo ważnych rzeczy, które powinna zapewniać nasza RESTowa architektura. Patrząc na specyfikację metod HTTP w łatwy sposób dodać taką obsługę do naszego API. W podejściu RPC jest to dużo trudniejsze, zwłaszcza, gdy zbyt często będziemy korzystać z metody POST. Nie jest to jednak niemożliwe. W tym punkcie można skłaniać się minimalnie w stronę architektury REST.

Podsumowanie

Niektórzy stwierdzą, że wybór naszego podejścia można sprowadzić tylko do osobistych odczuć na temat tego, czy wolimy taki czy inny styl zapytań. Jednak od naszego podejścia w dużej mierze zależy to, w jaki sposób będziemy później pisać nasze aplikacje. Moim zdaniem, głównym czynnikiem jakim powinniśmy się kierować jest to, jaki jest nasz projekt i nie ma sensu od razu zakładać że zrobimy to tak, czy inaczej. Pomimo kilku punktów, w których REST jest lepszym podejściem, np. to że jest bardzo przejrzysty i przewidywalny, ma on też kilka sporych wad i nieudogodnień, które wynikają z jego ograniczeń. Dlatego ja zwykle stawiam na pragmatyzm w moich rozwiązaniach i czerpię to, co najlepsze z danych rozwiązań. Nawet, jeżeli moje API będzie bardziej skłaniało się ku architekturze REST, to nie będę bał się użyć podejścia bardziej skierowanego na operacje. Moim zdaniem POST www.example.com/api/game/1/endturn będzie lepiej opisywało sytuację, niż zmiana właściwości obiektu i wykonywanie konkretnej operacji później.

A jakie jest Twoje zdanie na ten temat? Wszelkie uwagi mile widziane! Zapraszam do komentowania i kolejny raz polecam się na Twitterze!