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ę.

NoteZakres demonstratora i zakres projektu

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:

  1. Zweryfikować uwierzytelnienie.
  2. Ustalić rolę i jednostkę z zaufanych danych konta.
  3. Odczytać wskazany obiekt i sprawdzić uprawnienie do żądanej operacji.
  4. 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

  1. 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.
  2. Alfa zna identyfikator rezerwacji Bravo. Należy wskazać miejsce kontroli uprawnień oraz dane, którym serwer może zaufać.
  3. Odpowiedź na POST nie 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.

Źródła