ChatMM to szyfrowany komunikator end-to-end zbudowany na Spring Boot 4 (backend), React + TypeScript (frontend) oraz PostgreSQL. Wiadomości są szyfrowane po stronie klienta przy użyciu RSA i AES - serwer przechowuje wyłącznie zaszyfrowany ciphertext i nigdy nie ma dostępu do treści.
| Warstwa | Technologia |
|---|---|
| Backend | Spring Boot 4, Java 17, Spring Security, STOMP/WebSocket |
| Frontend | React 18, TypeScript, Vite, shadcn/ui |
| Baza danych | PostgreSQL 16 |
| Konteneryzacja | Docker, Docker Compose |
- Docker >= 24
- Docker Compose >= 2.20
Skopiuj przykładowy plik i uzupełnij wartości:
cp .env.example .envMinimalne zmiany, które należy wykonać przed uruchomieniem:
POSTGRES_PASSWORD=twoje_haslo
JWT_SECRET=wygenerowany_klucz_min_256_bitowKlucz JWT można wygenerować poleceniem:
openssl rand -base64 64docker compose up --buildPo pierwszym uruchomieniu Docker pobierze obrazy bazowe, skompiluje backend Mavenem i zbuduje frontend Vitem. Może to potrwać kilka minut.
| Usługa | Adres |
|---|---|
| Frontend | http://localhost:3000 |
| Backend | http://localhost:8080/api/v1 |
| PostgreSQL | localhost:5432 (baza: chatdb) |
docker compose downAby usunąć również wolumen z danymi bazy:
docker compose down -vPełna lista obsługiwanych zmiennych (docker-compose.yml + .env):
| Zmienna | Domyślna wartość | Opis |
|---|---|---|
POSTGRES_PASSWORD |
chatmm_password_123 |
Hasło do bazy PostgreSQL |
JWT_SECRET |
(brak bezpiecznego domyślnego - ustaw własny) | Klucz podpisywania tokenów JWT (min. 256 bit) |
JWT_TOKEN_EXPIRATION |
900000 |
Czas życia access tokena w ms (domyślnie 15 min) |
REFRESH_TOKEN_EXPIRATION |
2592000000 |
Czas życia refresh tokena w ms (domyślnie 30 dni) |
ALLOWED_ORIGINS |
http://localhost:3000,http://localhost:5173 |
Dozwolone originy dla CORS |
DDL_AUTO |
update |
Strategia Hibernate DDL |
VITE_API_URL |
http://localhost:8080/api/v1 |
URL backendu widoczny dla frontendu |
Bazowy prefiks wszystkich endpointów: /api/v1
Endpointy spod /auth/** i /ws/** są publiczne. Wszystkie pozostałe wymagają nagłówka:
Authorization: Bearer <access_token>
Rejestracja nowego użytkownika. Klient generuje parę kluczy RSA po stronie przeglądarki i przesyła klucz publiczny oraz zaszyfrowany klucz prywatny.
Ciało żądania:
{
"email": "user@example.com",
"name": "Jan Kowalski",
"password": "haslo_zahashowane_na_frontendzie",
"frontSalt": "base64_sol_do_hashowania_hasla",
"publicKey": "base64_klucz_publiczny_rsa",
"encryptedPrivateKey": "base64_zaszyfrowany_klucz_prywatny"
}Odpowiedź: 201 Created (brak ciała)
Pobranie solu użytkownika przed zalogowaniem - wymagane do ponownego zahashowania hasła po stronie klienta.
Odpowiedź:
{
"salt": "base64_sol"
}Logowanie. Każde urządzenie rejestruje własny deviceId.
Ciało żądania:
{
"email": "user@example.com",
"password": "zahashowane_haslo",
"deviceId": "uuid-urzadzenia"
}Odpowiedź:
{
"accessToken": "jwt...",
"refreshToken": "uuid...",
"id": 1,
"email": "user@example.com",
"name": "Jan Kowalski",
"encryptedPrivateKey": "base64..."
}Odświeżenie access tokena przy użyciu refresh tokena.
Ciało żądania:
{
"refreshToken": "uuid..."
}Odpowiedź:
{
"accessToken": "nowy_jwt..."
}Unieważnienie sesji dla konkretnego urządzenia.
Ciało żądania:
{
"deviceId": "uuid-urzadzenia"
}Odpowiedź: 204 No Content
Wszystkie endpointy wymagają autoryzacji.
Dane zalogowanego użytkownika.
Odpowiedź:
{
"id": 1,
"email": "user@example.com",
"name": "Jan Kowalski",
"publicKey": "base64..."
}Wyszukanie użytkownika po dokładnym adresie email.
Podpowiedzi użytkowników podczas wpisywania (domyślnie do 5 wyników).
Tworzenie nowej konwersacji. Pole participantKeys to mapa userId -> zaszyfrowany_klucz_sesji_AES. Klucz sesji jest szyfrowany kluczem publicznym RSA każdego uczestnika.
Ciało żądania:
{
"name": "Nazwa grupy (opcjonalnie)",
"participantKeys": {
"2": "base64_zaszyfrowany_klucz_sesji_dla_usera_2",
"3": "base64_zaszyfrowany_klucz_sesji_dla_usera_3"
}
}Odpowiedź: 201 Created
{
"id": 42
}Lista konwersacji zalogowanego użytkownika (stronicowanie).
Parametry query: page (domyślnie 0), size (domyślnie 10)
Odpowiedź: strona Page<ConversationSummaryResponse> ze standardowymi polami Spring Data.
Historia wiadomości dla danej konwersacji (cursor pagination).
Parametry query:
cursor- ID ostatniej wiadomości (opcjonalny, brak = od najnowszej)limit- liczba wiadomości na stronę (domyślnie 50)
Odpowiedź:
{
"messages": [
{
"id": 10,
"senderId": 1,
"senderName": "Jan Kowalski",
"ciphertext": "base64...",
"iv": "base64...",
"timestamp": "2025-06-01T12:00:00"
}
],
"hasMore": true,
"nextCursor": 9
}Klucze publiczne RSA wszystkich uczestników konwersacji - potrzebne do zaszyfrowania klucza sesji przy dodawaniu nowego uczestnika.
Odpowiedź:
[
{
"userId": 2,
"publicKey": "base64..."
}
]Połączenie: ws://localhost:8080/api/v1/ws
Autentykacja odbywa się przez nagłówek STOMP Authorization: Bearer <access_token> podczas handshake CONNECT.
Cel: /app/chat.send
Payload:
{
"conversationId": 42,
"ciphertext": "base64_zaszyfrowana_wiadomosc",
"iv": "base64_wektor_inicjalizacyjny"
}Subskrypcja tematu konwersacji:
/topic/conversation.{conversationId}
Payload odpowiedzi jest identyczny z obiektem wiadomości z endpointu historii.
Subskrypcja powiadomień o nowych wiadomościach (dla aktualnie zalogowanego użytkownika):
/user/queue/notifications
Subskrypcja powiadomień o nowych konwersacjach:
/user/queue/conversations
ChatMM stosuje szyfrowanie end-to-end oparte na modelu hybrydowym:
-
Przy rejestracji klient generuje parę kluczy RSA. Klucz publiczny trafia na serwer, klucz prywatny jest szyfrowany lokalnie (AES-GCM, klucz wyprowadzony z hasła przez PBKDF2) i przechowywany na serwerze w formie zaszyfrowanej.
-
Przy tworzeniu konwersacji klient generuje jednorazowy klucz sesji AES, szyfruje go kluczem publicznym RSA każdego uczestnika i wysyła wyniki do serwera.
-
Każda wiadomość jest szyfrowana kluczem sesji AES-GCM. Serwer przechowuje wyłącznie
ciphertextiiv. -
Hasło użytkownika jest hashowane po stronie klienta z użyciem solu (
frontSalt) przed wysłaniem do serwera. Serwer hashuje otrzymaną wartość BCryptem i nie ma dostępu do oryginalnego hasła.
CHATMM/
├── docker-compose.yml
├── .env.example
├── ChatMM-backend/ # Spring Boot 4
│ ├── Dockerfile
│ ├── pom.xml
│ └── src/
│ ├── main/java/.../
│ │ ├── controller/ # AuthController, ConversationController, UserController, ChatController
│ │ ├── service/ # AuthService, ConversationService, ChatMessageService, NotificationService
│ │ ├── security/ # JwtAuthFilter, JwtService, StompAuthInterceptor
│ │ ├── model/ # User, Conversation, Message, Participant, RefreshToken
│ │ ├── dto/ # request/response DTOs
│ │ └── config/ # SecurityConfig, WebSocketConfig
│ └── test/ # testy jednostkowe i integracyjne
└── frontend/ # React + TypeScript + Vite
├── Dockerfile
└── src/
├── components/
├── hooks/
└── encryption/ # crypto.ts, messageCrypto.ts, deviceKeyStore.ts