Wykład 2: Projektowanie API i bezpieczeństwo
Termin: sobota, 24 października 2026 r. — pierwsza część wspólnego bloku 4 × 45 minut.
Wykłady 2 i 3 odbywają się podczas jednego spotkania. Każdy moduł obejmuje 2 × 45 minut. Materiał na stronie rozwija przykłady omawiane na wykładzie; samodzielna praca nad implementacją będzie realizowana na laboratoriach.
Od działającego żądania do kontraktu API
Pierwszy wykład przedstawiał drogę żądania od klienta przez serwer do bazy danych. Działająca operacja zapisu nie odpowiada jednak na wszystkie pytania projektowe: jak klient rozpozna konflikt, kto może zmienić cudzą rezerwację i co oznacza ponowienie żądania po utracie odpowiedzi?
API — Application Programming Interface, interfejs programowania aplikacji, określa sposób korzystania z funkcji systemu przez inne programy. Jego kontrakt obejmuje adresy, metody, formaty danych, znaczenie odpowiedzi i reguły dostępu. Nie jest tylko listą funkcji dostępnych pod /docs.
Przykładem pozostaje fikcyjny ośrodek „Sosna”: plac treningowy P1, sala symulatorów P2 i jednostki Alfa oraz Bravo. W demonstratorze odpowiadają im identyfikatory liczbowe 1 i 2. Rezerwacja R1 dotyczy P1 w przedziale 08:00–10:00. Data T jest ustalana względem przygotowanego stanu bazy, a czasy mają jawną strefę.
Aplikacja z pierwszego wykładu obsługuje obiekty, jednostki, rezerwacje, walidację, konflikty i trwały zapis SQLite. Nie zawiera kont, logowania ani kontroli właściciela rezerwacji. Część dotycząca uprawnień opisuje projektowane rozszerzenie, a nie istniejące zabezpieczenia tego programu.
Laboratoria odbędą się 8 listopada i 13 grudnia. Przykłady wykładowe nie zakładają, że implementacja laboratoryjna została już wykonana.
Zasoby, adresy i metody
REST — Representational State Transfer jest stylem architektonicznym. W stosowanej na kursie konwencji zasoby mają adresy, a metody HTTP — Hypertext Transfer Protocol wyrażają semantykę operacji. Sama liczba mnoga w adresie nie dowodzi zgodności systemu z całym stylem REST, ale pomaga tworzyć przewidywalny interfejs.
| Operacja | Przykład | Znaczenie |
|---|---|---|
| Lista obiektów | GET /poligony |
Pobranie kolekcji |
| Wybrany obiekt | GET /poligony/1 |
Pobranie jednej reprezentacji |
| Nowy obiekt | POST /poligony |
Utworzenie zasobu |
| Zastąpienie danych | PUT /poligony/1 |
Przesłanie pełnego zestawu edytowalnych pól |
| Częściowa zmiana | PATCH /poligony/1 |
Zmiana wskazanych pól |
| Usunięcie obiektu | DELETE /poligony/1 |
Próba usunięcia zgodna z regułami bazy |
Adres, czyli URL — Uniform Resource Locator, powinien być stabilny względem kolejności wyników. Identyfikator 1 nie oznacza „pierwszego elementu obecnej listy”. Usunięcie obiektu używanego przez rezerwacje może zostać odrzucone — dostępność metody nie oznacza, że każda operacja jest dopuszczalna w każdym stanie systemu.
Filtrowanie i stronicowanie
GET /poligony?typ=sala
GET /rezerwacje?poligon_id=1&status=zatwierdzona
Oba filtry są obsługiwane przez demonstrator. Parametry po ? zawężają kolekcję; pusta lista spełniających warunki obiektów jest poprawnym wynikiem 200 OK, a nie błędem 404 Not Found.
Dla większych zbiorów kontrakt powinien również określać stronicowanie, stabilną kolejność i limit rozmiaru odpowiedzi. Przykładowe GET /rezerwacje?limit=20&offset=40 jest propozycją rozszerzenia; obecny demonstrator nie implementuje tych parametrów. Stronicowanie bez jednoznacznego sortowania może powodować powtarzanie lub pomijanie elementów.
Wersjonowanie
Zmiana nazwy pola lub znaczenia kodu odpowiedzi może uszkodzić istniejącego klienta. Wersjonowanie, np. /api/v1/rezerwacje, pozwala rozdzielić niezgodne kontrakty, lecz nie zastępuje planu migracji. Dodanie nowego opcjonalnego pola nie zawsze wymaga nowej wersji. Decyduje zgodność z rzeczywistymi klientami, a nie sam fakt zmiany kodu.
Walidacja: poprawny JSON nie wystarcza
JSON — JavaScript Object Notation określa sposób zapisu danych, ale nie ich poprawność biznesową. Rezerwacja może być poprawnym dokumentem JSON, a mimo to wskazywać nieistniejący obiekt albo kończyć się przed rozpoczęciem.
Walidacja występuje na kilku poziomach:
| Poziom | Przykład kontroli | Miejsce odpowiedzialności |
|---|---|---|
| Struktura danych | Identyfikator jest dodatnią liczbą, czas zawiera strefę | Model wejściowy API |
| Znaczenie danych | Początek poprzedza koniec i jest w przyszłości | Logika aplikacji |
| Stan systemu | Obiekt jest dostępny, termin nie koliduje | Logika i transakcja bazodanowa |
| Uprawnienia | Konto może rezerwować dla wskazanej jednostki | Autoryzacja po stronie serwera |
W demonstratorze nieznane pola są odrzucane. Przesłanie rola=planista nie stworzy zatem uprawnień, ale samo odrzucanie dodatkowych pól nie zastępuje implementacji kont i autoryzacji.
Nakładanie się przedziałów
Dwa przedziały tego samego obiektu kolidują, gdy:
new_start < old_end AND new_end > old_start
Przedziały są półotwarte: początek należy do rezerwacji, a koniec już nie. Dlatego 10:00–11:00 nie koliduje z R1 kończącą się o 10:00.
| Próba przy istniejącej R1: P1, 08:00–10:00 | Oczekiwana odpowiedź demonstratora |
|---|---|
| P1, 10:00–11:00 | 201 — utworzenie rezerwacji |
| P1, 09:00–11:00 | 409 — konflikt terminów |
| P2, 10:00–11:00 | 409 — obiekt niedostępny z powodu przeglądu |
| Początek równy końcowi | 422 — niepoprawny przedział |
| Nieistniejąca jednostka | 404 — brak wskazanego zasobu |
Próby wymagają znanego stanu bazy. Po udanej rezerwacji 10:00–11:00 ponowienie tego samego żądania napotka już konflikt. Dwa wyniki 409 w tabeli mają różne przyczyny; treść odpowiedzi musi je wyjaśniać.
Kody odpowiedzi jako część kontraktu
| Kod | Znaczenie w projekcie |
|---|---|
200 OK |
Poprawny odczyt lub zmiana zwracająca reprezentację |
201 Created |
Utworzenie nowego zasobu |
204 No Content |
Sukces bez treści odpowiedzi |
401 Unauthorized |
Brak poprawnego uwierzytelnienia dla chronionej operacji |
403 Forbidden |
Odmowa wykonania operacji; w naszej polityce konto nie ma wymaganych uprawnień |
404 Not Found |
Wskazany pojedynczy zasób nie istnieje |
409 Conflict |
Konflikt z obecnym stanem, np. zajęty termin |
422 Unprocessable Content |
Dane nie spełniają przyjętych wymagań walidacji |
503 Service Unavailable |
Usługa chwilowo nie może wykonać operacji, np. wskutek zajęcia bazy |
Wariant chroniony powinien przy 401 przesyłać odpowiednie wyzwanie WWW-Authenticate, np. Bearer. Odpowiedź 403 jest przyjętą tutaj polityką odmowy dostępu; inne systemy mogą używać 404, aby nie ujawniać istnienia zasobu.
{"detail": "Konflikt terminów."}To przykładowy błąd biznesowy demonstratora. Błędy walidacji FastAPI mogą mieć w detail listę obiektów opisujących poszczególne pola, a nie pojedynczy tekst. Klient musi obsługiwać udokumentowane warianty zamiast zakładać jeden kształt każdej odpowiedzi błędnej.
OpenAPI: dokumentacja i rzeczywiste zachowanie
OpenAPI jest specyfikacją opisu interfejsów HTTP. FastAPI tworzy dokument /openapi.json na podstawie modeli i deklaracji operacji. Swagger UI pod /docs jest interaktywną prezentacją tego opisu. UI — User Interface oznacza interfejs użytkownika.
Generowanie dokumentacji ogranicza powielanie definicji pól, ale nie analizuje całej logiki funkcji. Wyjątek HTTPException(404, ...) w ciele funkcji nie jest sam w sobie deklaracją odpowiedzi w OpenAPI. Własne odpowiedzi należy opisać jawnie, np. przez responses.
Poniższy kompletny, mały przykład pokazuje deklarację; jest osobną ilustracją, a nie zmianą zainstalowanej aplikacji „Sosna”:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Resource(BaseModel):
id: int
name: str
@app.get(
"/poligony/{resource_id}",
response_model=Resource,
responses={404: {"description": "Resource not found"}},
)
def get_resource(resource_id: int) -> Resource:
if resource_id != 1:
raise HTTPException(status_code=404, detail="Resource not found")
return Resource(id=1, name="Training ground")Deklaracja responses dokumentuje możliwość błędu. Dopiero warunek i wyjątek realizują ją podczas wykonania. Dokumentację należy sprawdzać przez rzeczywiste wywołania, także dla przypadków niepoprawnych.
Uwierzytelnianie i autoryzacja
Authentication — uwierzytelnianie ustala tożsamość klienta. Authorization — autoryzacja ustala, czy ten klient może wykonać konkretną operację na konkretnym zasobie. Poprawne logowanie nie daje prawa do wszystkich danych.
Proponowana polityka rozbudowanej aplikacji:
| Operacja | Gość | Konto jednostki Alfa | Planista |
|---|---|---|---|
| Odczyt ogólnej dostępności | Tak | Tak | Tak |
| Odczyt szczegółów rezerwacji jednostki Alfa | Nie | Tak | Tak |
| Odczyt szczegółów rezerwacji Bravo | Nie | Nie | Tak |
| Utworzenie rezerwacji dla Alfy | Nie | Tak | Tak |
| Utworzenie rezerwacji dla Bravo | Nie | Nie | Tak |
| Zmiana dostępności obiektu | Nie | Nie | Tak |
Szczegóły polityki są decyzją projektową. Publiczna dostępność obiektu nie musi oznaczać publicznej listy jednostek, które go rezerwują.
Przypadek: zmiana identyfikatora
Konto Alfy wysyła żądanie dotyczące rezerwacji Bravo. Ukrycie przycisku w przeglądarce nie zapobiega ręcznemu wysłaniu tego żądania. Serwer powinien:
- Zweryfikować uwierzytelnienie.
- Ustalić rolę i jednostkę z zaufanych danych konta.
- Odczytać wskazany obiekt i sprawdzić uprawnienie do żądanej operacji.
- Dopiero po spełnieniu warunków wykonać zmianę.
Identyfikator jednostki w JSON nie stanowi dowodu przynależności użytkownika. Rola przekazana przez klienta przy rejestracji nie może samodzielnie nadawać uprawnień planisty. Zasady powinny obowiązywać również przy listowaniu, a nie wyłącznie przy pobraniu pojedynczej rezerwacji.
Hasła, sesje i tokeny
Hasła są przechowywane jako wyniki funkcji przeznaczonych do hashowania haseł, z indywidualną losową solą i odpowiednim kosztem obliczeń. Przykładem jest Argon2id; bcrypt pozostaje spotykany w istniejących systemach. Zwykły szybki skrót, np. SHA-256 bez mechanizmu przeznaczonego do haseł, nie spełnia tego celu. Hashowanie nie uniemożliwia zgadywania słabego hasła — podnosi koszt prób.
Po zalogowaniu klient może używać identyfikatora sesji lub tokenu:
| Rozwiązanie | Gdzie ustalany jest stan | Istotny koszt |
|---|---|---|
| Sesja po stronie serwera | Magazyn sesji powiązany z identyfikatorem klienta | Dostęp do magazynu i utrzymanie sesji |
| Samodzielnie weryfikowany token | Podpisane informacje i polityka weryfikacji | Czas ważności, rotacja kluczy i unieważnianie |
Cookie i token nie są przeciwstawnymi kategoriami: cookie jest sposobem przechowania i przesyłania wartości przez przeglądarkę, a JWT formatem tokenu. JWT również może być przesyłany w cookie.
JWT i JWS
JWT — JSON Web Token jest formatem przenoszenia deklaracji o podmiocie. W omawianym wariancie JWS — JSON Web Signature zapewnia ochronę integralności:
header.payload.signature
Nagłówek i dane są kodowane Base64url, a nie szyfrowane. Podpis cyfrowy lub MAC — Message Authentication Code, kod uwierzytelniający wiadomość, pozwala wykryć modyfikację. W wariancie symetrycznym HS256 strony weryfikujące znają wspólny sekret; przy podpisie asymetrycznym weryfikacja używa klucza publicznego.
Serwer sprawdza dozwolony algorytm, integralność i wymagane deklaracje, np. exp — czas wygaśnięcia, iss — wystawcę, aud — odbiorcę. Samo odkodowanie payloadu nie jest weryfikacją tokenu. JWE — JSON Web Encryption jest odrębnym mechanizmem szyfrowania; nie należy przypisywać jego właściwości JWS.
Token typu Bearer może wykorzystać jego posiadacz. Transport wymaga HTTPS — Hypertext Transfer Protocol Secure, czyli HTTP chronionego przez TLS — Transport Layer Security. Tokeny i hasła nie powinny trafiać do logów.
Wybrane zagrożenia aplikacji sieciowej
OWASP — Open Worldwide Application Security Project publikuje materiały poświęcone bezpieczeństwu aplikacji. Dla projektu szczególnie istotne są:
| Zagrożenie | Przykład | Podstawowa ochrona |
|---|---|---|
| Brak kontroli dostępu do obiektu | Alfa zmienia rezerwację Bravo | Sprawdzenie roli i właściciela na serwerze |
| SQL injection | Dane wejściowe zmieniają strukturę zapytania | Parametryzacja wartości, kontrolowana lista nazw kolumn |
| XSS — Cross-Site Scripting | Opis obiektu zostaje wykonany jako skrypt | Kodowanie wyjścia odpowiednie do kontekstu; unikanie wstawiania niezaufanego HTML |
| CSRF — Cross-Site Request Forgery | Przeglądarka wysyła niezamierzone żądanie z automatycznie dołączonym cookie | Ochrona CSRF, kontrola pochodzenia i odpowiednie ustawienia cookies |
# Values are parameters, not fragments of SQL syntax.
row = connection.execute(
"SELECT * FROM poligony WHERE id = ?", (resource_id,)
).fetchone()Nagłówek Authorization dodawany jawnie przez aplikację zmienia model ryzyka klasycznego CSRF, lecz nie chroni przed XSS ani kradzieżą tokenu. CORS — Cross-Origin Resource Sharing określa zasady dostępu skryptów przeglądarki między originami. Nie zastępuje uwierzytelniania ani autoryzacji; klient spoza przeglądarki nie staje się przez CORS niezdolny do wysłania żądania.
Przypadki do przeanalizowania
- R1 kończy się o 10:00. Porównanie prób 09:00–11:00 i 10:00–11:00 powinno odwoływać się do konkretnego warunku kolizji, nie tylko do kodu odpowiedzi.
- Alfa zna identyfikator rezerwacji Bravo. Należy wskazać miejsce kontroli uprawnień oraz dane, którym serwer może zaufać.
- Odpowiedź na
POSTnie dotarła. Klient nie wie jeszcze, czy zapis nastąpił. Poprawny format API nie zapewnia automatycznie bezpiecznego ponowienia.
Trzeci przypadek prowadzi do wykładu 3: współbieżność, trwałość danych i odpowiedzialność infrastruktury.
Test po wykładach
Otwórz wspólny test po wykładach — jedna karta z 10 pytaniami i jednym wynikiem.