W przypadku operacji aktualizacji w REST API najczęściej używamy metody PATCH. Jest to bardzo popularny sposób wykonania częściowej aktualizacji zasobów, gdy chcemy zmienić tylko wybrane pola, takie jak na przykład opis kraju. W standardowej implementacji metody PATCH, która jest zgodna z dokumentami RFC, przepływ działa w sposób dość prosty: walidujemy dane wejściowe, a jeśli spełniają one wymagania, wykonujemy aktualizację, zwracając odpowiedź 204 No Content, co oznacza, że operacja się powiodła, ale nie zwrócono żadnej treści.

Walidacja jest kluczowa, aby zapewnić, że tylko prawidłowe dane będą przekazane do bazy. W przypadku, gdy parametry wejściowe są błędne lub niekompletne, zwracamy błąd 400 Bad Request. Jeśli zasób, który próbujemy zaktualizować, nie istnieje, zwracamy 404 Not Found. Dobrą praktyką jest, aby metoda PATCH nie tworzyła nowych zasobów, jeśli nie istnieje identyfikator w żądaniu. Dlatego identyfikator zasobu w ścieżce URL jest niezbędny do jednoznacznego określenia, który zasób chcemy zaktualizować.

Zaimplementowanie takiej metody w aplikacji webowej może wymagać zastosowania dodatkowych narzędzi, takich jak walidatory, które pomagają upewnić się, że dane są zgodne z wymaganiami. W przykładzie, który przedstawiam, stworzyliśmy klasę CountryPatchValidator, która odpowiada za walidację opisu kraju. Walidator ten używa wyrażeń regularnych do wykrywania potencjalnych tagów HTML, które mogą znajdować się w opisie. Dzięki temu zapewniamy, że dane wejściowe są pozbawione niepożądanych elementów.

Warto zwrócić uwagę, że implementacja PATCH zgodna z RFC 5789 i 6902 oferuje bardziej zaawansowane podejście do częściowej aktualizacji, które jest bardziej skomplikowane, ale daje większą elastyczność. Można sięgnąć po szczegółową dokumentację Microsoftu, aby dowiedzieć się, jak prawidłowo wykonać takie zapytania PATCH, jednak w codziennej praktyce prostsza metoda, jak ta opisana, jest najczęściej wystarczająca.

Inną kluczową częścią zarządzania danymi w REST API jest obsługa plików, zarówno w zakresie ich pobierania, jak i przesyłania. Praca z plikami to nieodłączny element wielu aplikacji. Zwykle w tym celu tworzymy dedykowane endpointy do przesyłania i pobierania plików. Aby prawidłowo pobrać plik, należy posiadać trzy podstawowe informacje: typ MIME pliku, zawartość pliku w postaci tablicy bajtów oraz nazwę pliku. W tym przypadku również, korzystając z metody File w klasie Results, możemy efektywnie przesyłać pliki do klienta.

W przypadku pobierania pliku, jak np. pliku CSV z danymi o krajach, implementacja jest dość prosta. Endpoint GET /countries/download odpowiada za zwrócenie pliku, który jest najpierw pobierany z serwera, a następnie zwracany jako odpowiedź HTTP. Klient otrzymuje w odpowiedzi plik w odpowiednim formacie (np. CSV), który może być zapisany na jego urządzeniu lub odczytany w przypadku, gdy typ MIME pozwala na jego wyświetlenie bezpośrednio w przeglądarce.

Choć obsługa plików w REST API jest relatywnie prosta, warto pamiętać o kilku rzeczach. Przede wszystkim należy odpowiednio ustawić typ MIME, aby plik został poprawnie rozpoznany przez przeglądarkę lub aplikację kliencką. Ponadto, ważnym aspektem jest zapewnienie odpowiedniej walidacji i obsługi błędów, aby nie doprowadzić do sytuacji, w których użytkownicy otrzymują błędne lub uszkodzone pliki.

Podczas implementacji pobierania plików, warto również zwrócić uwagę na optymalizację wydajności, szczególnie jeśli pliki mają duże rozmiary lub jeśli aplikacja obsługuje wielu użytkowników jednocześnie. Należy zadbać o to, aby serwer nie przeciążał się zbyt dużymi obciążeniami podczas przesyłania plików, a odpowiedzi były wystarczająco szybkie.

W kontekście wysyłania plików na serwer, proces jest nieco bardziej złożony. Wymaga on obsługi zarówno metody POST, jak i odpowiednich nagłówków HTTP, które określają sposób przesyłania danych. Ważne jest także, aby serwer potrafił zweryfikować poprawność przesyłanych plików, np. sprawdzając ich rozszerzenia czy rozmiary, aby zapobiec przesyłaniu złośliwego oprogramowania lub zbyt dużych plików, które mogłyby zablokować aplikację.

Zarządzanie plikami i częściowa aktualizacja zasobów za pomocą PATCH to fundamenty, które stanowią podstawę dla nowoczesnych aplikacji webowych, umożliwiając łatwą integrację z systemami zewnętrznymi i efektywne zarządzanie danymi.

Jakie modele limitowania żądań należy zastosować w REST API?

Jednym z kluczowych aspektów przy projektowaniu nowoczesnych API jest zarządzanie dostępem do zasobów, w tym kontrolowanie liczby żądań wysyłanych przez użytkowników lub aplikacje. Mechanizm limitowania żądań (Rate Limiting) jest niezbędnym narzędziem w zapewnianiu ochrony systemu oraz utrzymaniu wysokiej jakości usług. Dzięki niemu można zapobiec atakom typu Denial of Service (DoS), zarządzać przepustowością i zagwarantować, że zasoby są równomiernie przydzielane wszystkim użytkownikom.

Rate Limiting polega na nałożeniu ograniczeń na liczbę żądań, które użytkownik lub aplikacja mogą wysłać w określonym czasie. W zależności od implementacji, może to oznaczać różne mechanizmy i podejścia. ASP.NET Core 8 oferuje cztery główne modele limitowania:

  1. Fixed Window (Stałe Okno): Model oparty na dwóch parametrach – liczbie dozwolonych żądań oraz oknie czasowym, w którym te żądania mogą zostać wysłane. Każde wysłane żądanie zmniejsza licznik dozwolonych żądań. Po upływie okna czasowego licznik zostaje zresetowany. Żądania, które przekraczają dozwoloną liczbę w danym oknie czasowym, są odrzucane. Ten model pozwala na kolejkę żądań, które mogą zostać przyjęte, zanim zostaną odrzucone.

  2. Sliding Window (Przesuwane Okno): Model przypomina Fixed Window, ale działa na zasadzie podziału okna czasowego na segmenty. W każdym segmencie określa się liczbę dozwolonych żądań, a po zakończeniu segmentu pozostała liczba dozwolonych żądań jest przenoszona do następnego segmentu. W ten sposób liczba dostępnych żądań jest rozdzielana w czasie, a każde okno czasowe jest "przesuwane" o kolejny okres.

  3. Token Bucket (Worek Tokenów): W tym modelu zamiast prostego licznika żądań, wykorzystuje się pojęcie tokena i worka. Każde żądanie wymaga "wydania" tokena z worka. Tokeny są dodawane do worka w określonych odstępach czasu, ale nie mogą przekroczyć ustalonego limitu. Jeśli w danym momencie nie ma dostępnych tokenów, żądanie jest automatycznie odrzucane. Ten model pozwala na bardziej elastyczne zarządzanie dostępem, ale nie umożliwia kolejkowania żądań.

  4. Concurrency (Współbieżność): Najprostszy model, który ogranicza liczbę jednoczesnych żądań. Gdy liczba aktywnych żądań przekroczy ustalony limit, pozostałe żądania zostają odrzucone.

Każdy z tych modeli może być dostosowany do konkretnych potrzeb aplikacji. Dodatkowo, ASP.NET Core 8 pozwala na stosowanie kluczy partycji, które umożliwiają definiowanie różnych zasad limitowania w zależności od użytkownika, jego adresu IP czy innych kryteriów. Jeśli nie zdefiniujemy klucza partycji, limitowanie będzie miało charakter globalny i dotyczyć wszystkich użytkowników.

Kiedy żądanie jest odrzucane, API zwraca kod błędu, który może być dostosowany do specyfiki aplikacji. Chociaż domyślnie zwracany jest błąd 503 (Usługa niedostępna), warto ustawić odpowiedni kod statusu HTTP, jak np. 429 (Zbyt wiele żądań), który jest bardziej zgodny z wymaganiami protokołu HTTP.

W praktyce, aby zaimplementować rate limiting w aplikacji ASP.NET Core 8, należy użyć metody rozszerzenia AddRateLimiter oraz middleware UseRateLimiter. Dzięki temu można łatwo zintegrować różne modele limitowania żądań w swojej aplikacji. Przykładowa konfiguracja dla Fixed Window wygląda następująco:

csharp
builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = (int)HttpStatusCode.TooManyRequests; options.OnRejected = async (context, token) => { await context.HttpContext.Response.WriteAsync("Zbyt wiele żądań. Proszę spróbować ponownie później."); }; options.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => RateLimitPartition.GetFixedWindowLimiter( partitionKey: httpContext.Connection.RemoteIpAddress.ToString(), factory: _ => new FixedWindowRateLimiterOptions { QueueLimit = 10, PermitLimit = 50, Window = TimeSpan.FromSeconds(15) } )); });

Taki kod konfiguruje limitowanie w oparciu o adres IP klienta, z maksymalną liczbą 50 żądań na 15 sekund oraz możliwością kolejkowania 10 żądań. Jeśli żądania przekroczą limit, odpowiedź zwróci błąd 429.

Istnieje również możliwość wyłączenia limitowania dla niektórych końcówek za pomocą metody DisableRateLimiting. Na przykład, jeżeli chcemy, aby końcówka API była dostępna bez żadnych ograniczeń, możemy dodać następującą konfigurację:

csharp
app.MapGet("/notlimited", () => { return Results.Ok(); }).DisableRateLimiting();

Warto również zauważyć, że możliwe jest tworzenie różnych zasad limitowania, które mogą być przypisane do konkretnych końcówek API. Każdą zasadę można zaaplikować za pomocą metody RequireRateLimiting, co pozwala na bardziej granularne kontrolowanie dostępności API w zależności od potrzeb.

Zastosowanie rate limiting w API nie tylko chroni system przed nadmiernym obciążeniem, ale także zapewnia lepszą jakość usług. Dzięki odpowiedniemu limitowaniu możemy zapewnić, że każdy użytkownik będzie miał dostęp do zasobów w sposób sprawiedliwy, bez ryzyka przeciążenia systemu. Ograniczenie liczby żądań jest zatem nie tylko kwestią bezpieczeństwa, ale również integralną częścią projektowania skalowalnych i wydajnych aplikacji.

Jak skonfigurować limitowanie współbieżności i obsługę błędów w ASP.NET Core 8

W modelu limitowania współbieżności najprostszą konfiguracją jest ustawienie opcji QueueLimit oraz PermitLimit, jak pokazano w przykładzie 5-31. Działa to w sposób bardzo przejrzysty, a kluczowe dla tego rozwiązania jest odpowiednie ustawienie tych dwóch parametrów, które regulują, jak duża liczba żądań może być równocześnie obsługiwana oraz jak wiele żądań może oczekiwać w kolejce.

Model limitowania współbieżności w ASP.NET Core 8 pozwala na bardziej zaawansowaną kontrolę nad przepustowością i szybkością obsługi zapytań, co jest szczególnie istotne w przypadku aplikacji, które przyjmują duż

Jak zabezpieczyć aplikację za pomocą OpenID Connect i JWT w ASP.NET Core?

Współczesne aplikacje wymagają zastosowania silnych mechanizmów zabezpieczeń, aby chronić dostęp do danych i funkcji. Jednym z najczęściej używanych podejść w tym kontekście jest OpenID Connect (OIDC) w połączeniu z JSON Web Token (JWT). Te technologie umożliwiają efektywne zarządzanie uwierzytelnianiem i autoryzacją, szczególnie w rozbudowanych aplikacjach webowych.

Pierwszym krokiem w implementacji zabezpieczeń jest konfiguracja JWT w aplikacji ASP.NET Core. Zaczynamy od ustawienia odpowiednich parametrów, takich jak Authority i Audience, które są niezbędne do prawidłowego walidowania tokenów. Należy zwrócić szczególną uwagę na ustawienia TokenValidationParameters, które kontrolują takie aspekty, jak walidacja żywotności tokenu i poprawność wydawcy. Aby tokeny działały poprawnie, warto ustawić także ClockSkew, który pozwala na tolerowanie drobnych rozbieżności czasowych. Poniższy fragment kodu prezentuje podstawową konfigurację JWT w ASP.NET Core:

csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://login.microsoftonline.com/{tenantId}/v2.0"; options.Audience = "{clientId}"; options.TokenValidationParameters.ValidateLifetime = true; options.TokenValidationParameters.ValidateIssuer = true; options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5); });

Kiedy już odpowiednio skonfigurujemy uwierzytelnianie, musimy pamiętać, by odpowiednio ustawić middleware. Kolejność middleware w ASP.NET Core ma kluczowe znaczenie dla poprawności działania aplikacji. Middleware do obsługi CORS (Cross-Origin Resource Sharing) musi być umieszczone przed middleware do uwierzytelniania i autoryzacji, ponieważ CORS nie powinien być zależny od uwierzytelniania.

Aby chronić konkretne endpointy, wystarczy dodać metodę RequireAuthorization, która wymusi konieczność uwierzytelnienia, zanim użytkownik uzyska dostęp do zasobów. Tylko wtedy, gdy użytkownik posiada ważny token JWT, zostanie autoryzowany do korzystania z chronionych zasobów. Przykład:

csharp
app.MapGet("/authenticated", () => { return Results.Ok("Authenticated!"); }).RequireAuthorization();

Często aplikacje wymagają większych uprawnień dla niektórych użytkowników. Role mogą być przypisane do użytkownika podczas generowania tokenu JWT, a następnie zweryfikowane przez system autoryzacji. W przypadku, gdy użytkownik nie spełnia wymagań autoryzacji, system zwróci błąd HTTP Forbidden (403). Przykład implementacji z wykorzystaniem ról i niestandardowych polityk:

csharp
builder.Services.AddAuthorization(options => { options.AddPolicy("SurveyCreator", policy => policy.RequireRole("SurveyCreator")); }); app.MapGet("/authorized", () => { return Results.Ok("Authorized!"); }).RequireAuthorization("SurveyCreator");

Jeżeli użytkownik nie jest uwierzytelniony lub nie posiada odpowiednich uprawnień, aplikacja zwróci odpowiedni błąd, np. HTTP Unauthenticated (401) lub HTTP Forbidden (403). Takie zabezpieczenia są niezbędne w każdej nowoczesnej aplikacji, aby zapewnić odpowiedni poziom ochrony przed nieautoryzowanym dostępem.

Kolejnym krokiem jest zrozumienie, jak działa token JWT i jak go poprawnie przekazywać do aplikacji. Token JWT składa się z trzech części: nagłówka, ładunku i podpisu. Warto wiedzieć, jak analizować taki token, by zrozumieć, jakie informacje zawiera. Można to zrobić na przykład za pomocą narzędzi takich jak jwt.io, które pozwalają na dekodowanie tokenu i weryfikowanie jego zawartości. Przykład tokenu JWT pokazuje, jakie dane są zawarte w poszczególnych częściach tokenu:

  1. Nagłówek – zawiera informacje o algorytmie używanym do podpisania tokenu.

  2. Ładunek – zawiera dane użytkownika (np. ID, rola).

  3. Podpis – zapewnia integralność tokenu.

Ważnym elementem debugowania aplikacji może być analiza wygasających tokenów, błędnie ustawionych parametrów Audience lub niepoprawnie skonfigurowanych ról. Każda z tych sytuacji może prowadzić do problemów z autoryzacją.

Aby korzystać z JWT w praktyce, należy umieścić token w nagłówkach zapytań HTTP, szczególnie w nagłówku Authorization, z prefiksem Bearer:

css
Authorization: Bearer {YourJWT}

Swagger, narzędzie do dokumentacji API, może zostać skonfigurowane w taki sposób, aby umożliwić testowanie endpointów chronionych przez JWT. Można to zrobić poprzez dodanie odpowiedniej definicji bezpieczeństwa w konfiguracji Swaggera. W ten sposób, użytkownicy będą mogli wygodnie wprowadzać token JWT i testować zabezpieczone zasoby bezpośrednio z poziomu dokumentacji API.

csharp
builder.Services.AddSwaggerGen(c => { c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", Scheme = "Bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "JWT Authorization header required" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] {} } }); });

Po dodaniu tej konfiguracji, przy korzystaniu ze Swaggera, użytkownicy będą mogli wprowadzać swoje tokeny JWT, co pozwala na pełne testowanie aplikacji.

Warto również pamiętać, że poza Swaggerem, inne narzędzia, takie jak Postman, umożliwiają testowanie zabezpieczonych endpointów. W przypadku Postmana należy jedynie umieścić token w sekcji Authorization i wybrać odpowiednią metodę (np. Bearer).

Prawidłowa obsługa tożsamości użytkownika w aplikacji opartych na JWT jest kluczowa. Korzystając z klasy ClaimsPrincipal, która jest automatycznie rejestrowana w DI (Dependency Injection) w ASP.NET Core, możemy uzyskać dostęp do informacji o użytkowniku, takich jak jego rola czy identyfikator.

Wszystkie powyższe kroki pokazują, jak skutecznie zabezpieczyć aplikację przy użyciu OpenID Connect i JWT, a także jak poprawnie obsługiwać proces uwierzytelniania i autoryzacji. Dzięki tym technologiom możliwe jest stworzenie aplikacji, która nie tylko zapewnia bezpieczeństwo, ale i elastyczność w zarządzaniu dostępem do zasobów.