API REST per la gestione di una lista di attività (todo) sviluppata con Spring Boot 4.0.0.
Applicazione backend che fornisce un sistema completo di gestione di todo list con autenticazione JWT e autorizzazione basata su ruoli. Gli utenti possono registrarsi, autenticarsi e gestire le proprie attività personali. Il sistema supporta due ruoli: USER (utente standard) e ADMIN (amministratore con privilegi aggiuntivi).
- Spring Boot 4.0.1
- Java 17
- Spring Security - Autenticazione e autorizzazione
- Spring Security Crypto - Crittografia dei refresh token salvati nel database
- Spring Data JPA - Persistenza dati
- PostgreSQL - Database relazionale
- JWT (JSON Web Token) - Autenticazione stateless
- AWS SDK v2 - Integrazione con Amazon S3 per storage file
- Apache Tika - Rilevamento MIME type basato sul contenuto dei file
- Lombok - Riduzione boilerplate code
- MapStruct 1.5.5 - Mapping automatico Entity ↔ DTO (compile-time)
- Jakarta Validation - Validazione dei dati
- Jackson - Serializzazione/deserializzazione JSON
- Bucket4j - Rate limiting distribuito con algoritmo token bucket
- Lettuce - Client Java Redis (driver ufficiale supportato da Spring)
- RedisService - Servizio custom per operazioni generiche su Redis (caching, storage chiave-valore)
spring-boot-starter-webmvc- Framework webspring-boot-starter-data-jpa- Persistenza con JPA/Hibernatespring-boot-starter-security- Sicurezza e autenticazionespring-boot-starter-validation- Validazione dei datipostgresql- Driver database PostgreSQLjjwt(0.11.5) - Gestione JWTaws-sdk-s3(2.26.0) - SDK AWS per integrazione S3aws-sdk-auth(2.26.0) - SDK AWS per autenticazionetika-core(2.9.0) - Rilevamento MIME type basato sul contenutolombok- Generazione codice automaticamapstruct(1.5.5.Final) - Mapping automatico Entity ↔ DTOmapstruct-processor- Annotation processor per MapStructlombok-mapstruct-binding(0.2.0) - Integrazione Lombok-MapStructspring-boot-devtools- Strumenti di sviluppobucket4j-core- Libreria rate limiting con algoritmo token bucketlettuce-core- Client Redis reattivobucket4j_jdk17-lettuce- Integrazione Bucket4j con Redis tramite Lettuce
src/main/java/com/example/dataware/todolist/
├── config/
│ ├── SecurityConfig.java # Configurazione Spring Security
│ ├── S3Config.java # Configurazione client AWS S3
│ └── RedisConfig.java # Configurazione Redis e Bucket4j per rate limiting
├── controller/
│ ├── AuthController.java # Endpoint autenticazione
│ ├── TodoController.java # Endpoint gestione todo
│ └── UserController.java # Endpoint gestione utente
├── dto/
│ ├── validator/
│ │ ├── LoginDto.java # DTO per login
│ │ ├── UserDto.java # DTO validazione registrazione utente
│ │ ├── TodoDto.java # DTO validazione creazione todo
│ │ └── TodoUpdateDto.java # DTO validazione aggiornamento todo
│ └── response/
│ ├── UserResponse.java # Response utente completo
│ ├── TodoResponse.java # Response todo completo
│ ├── TokenResponse.java # Response con accessToken e refreshToken
│ ├── PageResponse.java # DTO per risposte paginate
│ └── builder/
│ ├── SuccessResponse.java # Modello risposta successo
│ └── SuccessResponseBuilder.java # Builder per risposte di successo
├── entity/
│ ├── BaseEntity.java # Entità base con id, createdAt, updatedAt
│ ├── enums/
│ │ └── Role.java # Enum ruoli utente (USER, ADMIN)
│ ├── User.java # Entità utente
│ └── Todo.java # Entità todo
├── exception/
│ ├── ErrorResponse.java # Modello risposta errore con metodo buildResponse()
│ ├── CustomExceptionHandler.java # Gestore eccezioni custom (priorità alta)
│ ├── GlobalExceptionHandler.java # Gestore eccezioni native/Spring (priorità bassa)
│ └── custom/
│ ├── BaseCustomException.java # Interfaccia comune per eccezioni custom
│ ├── EmailConflictException.java # Eccezione conflitto email
│ ├── EmptyFileException.java # Eccezione file vuoto
│ ├── InvalidCredentialsException.java # Eccezione credenziali non valide
│ ├── InvalidFileTypeException.java # Eccezione tipo file non valido
│ ├── InvalidSortablePropertyException.java # Eccezione proprietà ordinabile non valida
│ ├── S3UploadException.java # Eccezione errore upload S3
│ ├── TodoNotFoundException.java # Eccezione todo non trovato
│ └── UserNotFoundException.java # Eccezione utente non trovato
├── filter/
│ ├── jwt/
│ │ ├── enums/
│ │ │ └── TokenType.java # Enum per distinguere ACCESS e REFRESH token
│ │ ├── payload/
│ │ │ └── JwtPayload.java # Payload JWT (userId, email)
│ │ ├── service/
│ │ │ └── JwtService.java # Servizio gestione JWT (generazione e validazione)
│ │ ├── JwtAccessFilter.java # Filtro per validazione access token
│ │ └── JwtRefreshFilter.java # Filtro per validazione refresh token
│ └── rateLimiter/
│ ├── enums/
│ │ └── RateLimitEndpoint.java # Enum endpoint con limiti configurabili
│ ├── service/
│ │ └── RateLimiteService.java # Servizio rate limiting con Bucket4j e Redis
│ └── RateLimitFilter.java # Filtro per applicare rate limiting
├── mapper/
│ ├── TodoMapper.java # Interfaccia MapStruct per mapping Todo ↔ DTO
│ └── UserMapper.java # Interfaccia MapStruct per mapping User ↔ DTO
│ # ⚠️ Le implementazioni sono auto-generate da MapStruct durante la compilazione
│ # 📁 Posizione: target/generated-sources/annotations/com/example/dataware/todolist/mapper/
├── repository/
│ ├── TodoRepository.java # Repository JPA per Todo
│ └── UserRepository.java # Repository JPA per User
├── service/
│ ├── interfaces/
│ │ ├── AuthService.java # Interfaccia servizio autenticazione
│ │ ├── TodoService.java # Interfaccia servizio todo
│ │ └── UserService.java # Interfaccia servizio utente
│ ├── implementation/
│ │ ├── AuthServiceImpl.java # Implementazione servizio autenticazione
│ │ ├── TodoServiceImpl.java # Implementazione servizio todo
│ │ └── UserServiceImpl.java # Implementazione servizio utente
│ ├── EncryptionService.java # Servizio crittografia/decrittografia refresh token
│ └── RedisService.java # Servizio wrapper per operazioni Redis (set, get, delete)
├── s3/
│ ├── S3Properties.java # Proprietà configurazione S3
│ └── S3Service.java # Servizio gestione upload/delete file su S3
└── util/
├── fileValidation/
│ ├── ImageValidation.java # Validazione file immagine con Apache Tika
│ └── enums/
│ └── ImageMimeType.java # Enum tipi MIME supportati (JPEG, PNG, WEBP, GIF, HEIC, HEIF)
└── sort/
└── TodoSortableProperty.java # Enum proprietà ordinabili per i todo
L'applicazione utilizza JWT (JSON Web Token) con sistema di Access Token e Refresh Token per l'autenticazione stateless e autorizzazione basata su ruoli:
- Gli endpoint
/auth/registere/auth/loginsono pubblici e non richiedono autenticazione - L'endpoint
/auth/refresh-tokenrichiede un refresh token valido nell'headerAuthorization: Bearer <refreshToken> - L'endpoint
/auth/logoutrichiede un access token valido nell'headerAuthorization: Bearer <accessToken> - Tutti gli altri endpoint richiedono un access token valido nell'header
Authorization: Bearer <accessToken> - Il sistema utilizza due tipi di token:
- Access Token: Token a breve durata per autenticare le richieste API
- Refresh Token: Token a lunga durata per ottenere nuovi access token senza ri-autenticarsi, salvato nel database (crittografato)
- Entrambi i token contengono
email,userIderoledell'utente - I token utilizzano chiavi segrete separate per maggiore sicurezza
- I refresh token vengono crittografati prima di essere salvati nel database
- La sessione è configurata come
STATELESS - Ordine dei filtri: I filtri sono configurati nell'ordine seguente nella catena di Spring Security:
RateLimitFilter- Applica il rate limiting (prima di tutto)JwtAccessFilter- Valida gli access tokenJwtRefreshFilter- Valida i refresh token (solo per/auth/refresh-token)
- Due filtri separati gestiscono la validazione:
JwtAccessFilterper gli access token eJwtRefreshFilterper i refresh token - Autorizzazione basata su ruoli: Il sistema supporta due ruoli (
USEReADMIN) e utilizza@PreAuthorizeper controllare l'accesso agli endpoint in base al ruolo dell'utente - Il ruolo viene estratto dal token JWT e aggiunto alle authorities di Spring Security con il prefisso
ROLE_
id(Long) - Identificatore univoconome(String) - Nome utente (min 4 caratteri)email(String) - Email univocapassword(String) - Password hashata con BCryptprofileImageUrl(String) - URL dell'immagine profilo su S3 o URL defaultrole(Role) - Ruolo dell'utente (USERoADMIN). Default:USER(impostato automaticamente tramite@PrePersist)refreshToken(String) - Refresh token crittografato salvato nel database (nullable)todos(List) - Lista di todo associaticreatedAt(Instant) - Data di creazioneupdatedAt(Instant) - Data ultimo aggiornamento
id(Long) - Identificatore univocotitle(String) - Titolo del todo (min 4 caratteri)completed(boolean) - Stato di completamento (default: false)user(User) - Utente proprietariocreatedAt(Instant) - Data di creazioneupdatedAt(Instant) - Data ultimo aggiornamento
Registrazione nuovo utente.
Body:
{
"nome": "string (min 4 caratteri)",
"email": "string (formato email valido)",
"password": "string (min 6 caratteri)"
}Response: 201 Created
{
"statusCode": 201,
"message": "Success",
"data": {
"id": 1,
"nome": "Mario Rossi",
"email": "mario@example.com",
"role": "USER",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- Alla registrazione, il ruolo viene impostato automaticamente a
USERse non specificato - Il ruolo viene incluso nei token JWT generati durante il login
Login utente esistente.
Body:
{
"email": "string",
"password": "string (min 6 caratteri)"
}Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"timestamp": "2024-01-01T10:00:00Z"
}Ottiene nuovi access token e refresh token utilizzando un refresh token valido.
Headers: Authorization: Bearer <refreshToken>
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- Richiede un refresh token valido nell'header
Authorization - Il refresh token viene validato contro quello salvato nel database (crittografato)
- Restituisce una nuova coppia di access token e refresh token
- Il nuovo refresh token viene salvato nel database (sostituendo quello precedente)
- Utile quando l'access token è scaduto senza dover effettuare nuovamente il login
Effettua il logout dell'utente autenticato, rimuovendo il refresh token dal database.
Headers: Authorization: Bearer <accessToken>
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": "Logged out successfully",
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- Richiede un access token valido nell'header
Authorization - Rimuove il refresh token salvato nel database per l'utente
- Dopo il logout, il refresh token non può più essere utilizzato per ottenere nuovi token
- L'utente dovrà effettuare nuovamente il login per ottenere nuovi token
Nota: Tutti gli endpoint richiedono autenticazione JWT.
Ottiene tutti i todo dell'utente autenticato con paginazione e filtro opzionale.
Headers: Authorization: Bearer <token>
Query Parameters:
page(opzionale, default:1) - Numero di pagina (min: 1)limit(opzionale, default:10) - Numero di elementi per pagina (min: 1, max: 100)completed(opzionale) - Filtra per stato di completamento (trueofalse)sort(opzionale) - Parametro di ordinamento nel formato Spring Data (es:sort=title,ascosort=updatedAt,desc)
Ordinamento:
- Se non specificato, i todo sono ordinati per
updatedAtin ordine decrescente (più recenti prima) per default - Le proprietà ordinabili sono:
id,title,completed,createdAt,updatedAt - Formato:
sort=<proprietà>,<direzione>dove direzione può essereasc(crescente) odesc(decrescente) - Esempi:
sort=title,asc,sort=createdAt,desc,sort=completed,asc&sort=title,desc(ordinamento multiplo)
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"content": [
{
"id": 1,
"title": "Fare la spesa",
"completed": false,
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
}
],
"currentPage": 1,
"limit": 10,
"totalElements": 25,
"totalPages": 3,
"first": true,
"last": false,
"hasNext": true,
"hasPrevious": false
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- La paginazione è 1-based (la prima pagina è
page=1) - Il parametro
completedè opzionale: se non specificato, vengono restituiti tutti i todo - Il parametro
sortè opzionale: se non specificato, viene utilizzato l'ordinamento di default (updatedAtdecrescente) - Esempi:
GET /todos?page=2&limit=5&completed=false- Seconda pagina di 5 todo non completatiGET /todos?sort=title,asc- Todo ordinati per titolo crescenteGET /todos?sort=createdAt,desc&completed=true- Todo completati ordinati per data di creazione decrescenteGET /todos?sort=completed,asc&sort=title,desc- Ordinamento multiplo: prima per completamento, poi per titolo decrescente
Ottiene un singolo todo per ID.
Headers: Authorization: Bearer <token>
Response: 200 OK
Crea un nuovo todo.
Headers: Authorization: Bearer <token>
Body:
{
"title": "string (min 4 caratteri)"
}Response: 201 Created
Aggiorna un todo esistente.
Headers: Authorization: Bearer <token>
Body:
{
"title": "string (opzionale, min 4 caratteri)",
"completed": true/false (opzionale)
}Response: 200 OK
Elimina un todo.
Headers: Authorization: Bearer <token>
Response: 200 OK
Nota: Tutti gli endpoint richiedono autenticazione JWT e ruoli specifici.
Autorizzazione:
- Tutti gli endpoint richiedono almeno il ruolo
USERoADMIN - L'endpoint
GET /usersrichiede il ruoloADMIN
Ottiene tutti gli utenti registrati nel sistema con paginazione.
Ruolo richiesto: ADMIN
Headers: Authorization: Bearer <token>
Query Parameters:
page(opzionale, default:1) - Numero di pagina (min: 1)limit(opzionale, default:10) - Numero di elementi per pagina (min: 1, max: 100)
Ordinamento: Gli utenti sono ordinati per id in ordine crescente.
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"content": [
{
"id": 1,
"nome": "Mario Rossi",
"email": "mario@example.com",
"role": "USER",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
},
{
"id": 2,
"nome": "Admin User",
"email": "admin@example.com",
"role": "ADMIN",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
}
],
"currentPage": 1,
"limit": 10,
"totalElements": 15,
"totalPages": 2,
"first": true,
"last": false,
"hasNext": true,
"hasPrevious": false
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- La paginazione è 1-based (la prima pagina è
page=1) - Esempio:
GET /users?page=2&limit=5per ottenere la seconda pagina di 5 utenti
Ottiene il profilo dell'utente autenticato.
Ruolo richiesto: USER o ADMIN
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"id": 1,
"nome": "Mario Rossi",
"email": "mario@example.com",
"profileImageUrl": "https://bucket.s3.region.amazonaws.com/users/1/profile.jpg",
"role": "USER",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
},
"timestamp": "2024-01-01T10:00:00Z"
}Carica un'immagine profilo per l'utente autenticato su Amazon S3.
Ruolo richiesto: USER o ADMIN
Headers: Authorization: Bearer <token>
Content-Type: multipart/form-data
Body:
file(MultipartFile) - File immagine da caricare
Validazione:
- Il file deve essere un'immagine valida (JPEG, PNG, WEBP, GIF, HEIC, HEIF)
- Dimensione massima: 20MB
- Il tipo MIME viene rilevato dal contenuto del file (Apache Tika), non dall'estensione
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"id": 1,
"nome": "Mario Rossi",
"email": "mario@example.com",
"profileImageUrl": "https://bucket.s3.region.amazonaws.com/users/1/profile.jpg",
"role": "USER",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- L'immagine viene caricata su S3 con il path:
users/{userId}/profile.{ext} - Se esiste già un'immagine profilo, viene eliminata automaticamente dopo il successo dell'upload
- Il vecchio file viene eliminato solo dopo che il database è stato aggiornato con successo
- L'immagine di default non viene mai eliminata
Elimina l'immagine profilo dell'utente autenticato e ripristina l'avatar di default.
Ruolo richiesto: USER o ADMIN
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"statusCode": 200,
"message": "Success",
"data": {
"id": 1,
"nome": "Mario Rossi",
"email": "mario@example.com",
"profileImageUrl": "https://default-avatar-url.com/avatar.png",
"role": "USER",
"createdAt": "2024-01-01T10:00:00Z",
"updatedAt": "2024-01-01T10:00:00Z"
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- Se l'immagine è già quella di default, non viene eseguita alcuna operazione
- Il file viene eliminato da S3 e l'URL viene impostato all'avatar di default configurato
Elimina l'account dell'utente autenticato (cascade delete dei todo).
Ruolo richiesto: USER o ADMIN
Headers: Authorization: Bearer <token>
Response: 200 OK
Creare un file .env nella root del progetto con le seguenti variabili:
DB_URL=jdbc:postgresql://localhost:5432/todolist
DB_USERNAME=your_username
DB_PASSWORD=your_password
JWT_ACCESS_SECRET=your_access_secret_key_min_32_characters
JWT_ACCESS_EXPIRATION=2h
JWT_REFRESH_SECRET=your_refresh_secret_key_min_32_characters
JWT_REFRESH_EXPIRATION=7d
ENCRYPTION_KEY=your_encryption_key_min_16_characters
ENCRYPTION_SALT=your_encryption_salt_min_8_characters
AWS_ACCESS_KEY_ID=your_aws_access_key_id
AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key
AWS_REGION=us-east-1
AWS_S3_BUCKET=your-bucket-name
DEFAULT_AVATAR_URL=https://default-avatar-url.com/avatar.png
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=Descrizione variabili:
DB_URL- URL di connessione al database PostgreSQLDB_USERNAME- Username per il databaseDB_PASSWORD- Password per il databaseJWT_ACCESS_SECRET- Chiave segreta per la firma degli access token (minimo 32 caratteri)JWT_ACCESS_EXPIRATION- Durata dell'access token (es:2h= 2 ore)JWT_REFRESH_SECRET- Chiave segreta per la firma dei refresh token (minimo 32 caratteri, diversa dall'access secret)JWT_REFRESH_EXPIRATION- Durata del refresh token (es:7d= 7 giorni)ENCRYPTION_KEY- Chiave per la crittografia dei refresh token salvati nel database (minimo 16 caratteri)ENCRYPTION_SALT- Salt per la crittografia dei refresh token (minimo 8 caratteri)AWS_ACCESS_KEY_ID- Access Key ID per AWS S3AWS_SECRET_ACCESS_KEY- Secret Access Key per AWS S3AWS_REGION- Regione AWS dove si trova il bucket S3 (es:us-east-1,eu-west-1)AWS_S3_BUCKET- Nome del bucket S3 per lo storage delle immagini profiloDEFAULT_AVATAR_URL- URL dell'avatar di default da utilizzare quando l'utente non ha un'immagine profiloREDIS_HOST- Host di Redis (default:localhost)REDIS_PORT- Porta di Redis (default:6379)REDIS_PASSWORD- Password di Redis (opzionale, lasciare vuoto se non configurata)
Il file application.properties è configurato con:
- Porta server: 3001
- Hibernate DDL:
update(aggiorna automaticamente lo schema del database) - Show SQL:
true(mostra le query SQL nei log) - Error handling: Stack trace disabilitato, messaggi abilitati
- Logging: DEBUG per il package dell'applicazione, INFO per root
- Circular references: Abilitati per i mapper MapStruct (
spring.main.allow-circular-references=true) - Caricamento .env: Il file
.envviene caricato automaticamente tramitespring.config.import - JWT separati: Configurazione separata per access token e refresh token con chiavi e scadenze indipendenti
- Crittografia refresh token: Configurazione per crittografare i refresh token salvati nel database usando Spring Security Crypto
- Method Security: Abilitato tramite
@EnableMethodSecurityinSecurityConfigper supportare l'autorizzazione basata su ruoli con@PreAuthorize - Multipart file upload: Dimensione massima file 20MB (
spring.servlet.multipart.max-file-size=20MB) - AWS S3: Configurazione per l'integrazione con Amazon S3 per lo storage delle immagini profilo
- Redis: Configurazione per il rate limiting distribuito (host, port, password configurabili)
- Java 17 o superiore
- Maven 3.6+
- PostgreSQL in esecuzione
- Redis in esecuzione (per il rate limiting distribuito)
- Clonare il repository
git clone <repository-url>
cd todolist- Configurare il database PostgreSQL
createdb todolist- Avviare Redis (se non già in esecuzione)
# Con Docker
docker run -d -p 6379:6379 redis:latest
# Oppure con redis-server (se installato localmente)
redis-server-
Configurare le variabili d'ambiente Creare il file
.envnella root del progetto con le credenziali del database, la chiave JWT e le configurazioni Redis. -
Compilare e avviare l'applicazione
./mvnw spring-boot:runOppure tramite Maven:
mvn clean install
java -jar target/todolist-0.0.1-SNAPSHOT.jarNota sulla compilazione:
- Durante la compilazione, MapStruct genera automaticamente le implementazioni dei mapper in
target/generated-sources/annotations/ - Le classi generate sono annotate con
@Componente possono essere iniettate come bean Spring - Se modifichi le interfacce mapper, ricompila per rigenerare le implementazioni
L'applicazione sarà disponibile su http://localhost:3001
L'applicazione utilizza Jakarta Validation per validare i dati in ingresso:
-
UserDto:
nome: NotBlank, Size(min=4)email: NotBlank, Emailpassword: NotBlank, Size(min=6)
-
LoginDto:
email: NotBlank, Emailpassword: NotBlank, Size(min=6)
-
TodoDto:
title: NotBlank, Size(min=4)
-
TodoUpdateDto:
title: Pattern (opzionale), Size(min=4)completed: Boolean (opzionale)
-
Upload Immagini Profilo:
- Il file deve essere un'immagine valida (rilevamento MIME type dal contenuto con Apache Tika)
- Formati supportati: JPEG, PNG, WEBP, GIF, HEIC, HEIF
- Dimensione massima: 20MB (configurato in
application.properties) - Validazione basata sul contenuto del file, non sull'estensione
L'applicazione utilizza Spring Data Pagination per gestire grandi quantità di dati in modo efficiente. Gli endpoint che restituiscono liste di elementi supportano la paginazione tramite query parameters.
- GET
/todos- Lista todo dell'utente autenticato - GET
/users- Lista tutti gli utenti (solo ADMIN)
page(opzionale, default:1) - Numero di pagina (1-based, minimo: 1)limit(opzionale, default:10) - Numero di elementi per pagina (minimo: 1, massimo: 100)
- GET
/todossupporta anche il parametrocompleted(Boolean) per filtrare i todo per stato di completamento
Tutte le risposte paginate utilizzano il DTO PageResponse<T> che contiene:
{
"content": [...], // Array di elementi della pagina corrente
"currentPage": 1, // Numero della pagina corrente (1-based)
"limit": 10, // Numero di elementi per pagina
"totalElements": 25, // Totale elementi in tutte le pagine
"totalPages": 3, // Totale pagine disponibili
"first": true, // Se è la prima pagina
"last": false, // Se è l'ultima pagina
"hasNext": true, // Se esiste una pagina successiva
"hasPrevious": false // Se esiste una pagina precedente
}- GET
/todos:- Default: Ordinati per
updatedAtin ordine decrescente (più recenti prima) se non specificato - Configurabile tramite parametro
sortcon proprietà:id,title,completed,createdAt,updatedAt - Formato:
sort=<proprietà>,<direzione>(es:sort=title,asc,sort=createdAt,desc) - Supporta ordinamento multiplo:
sort=completed,asc&sort=title,desc
- Default: Ordinati per
- GET
/users: Ordinati peridin ordine crescente
# Prima pagina con 10 elementi (default)
GET /todos
# Seconda pagina con 5 elementi
GET /todos?page=2&limit=5
# Prima pagina di todo non completati
GET /todos?page=1&limit=10&completed=false
# Todo ordinati per titolo crescente
GET /todos?sort=title,asc
# Todo completati ordinati per data di creazione decrescente
GET /todos?sort=createdAt,desc&completed=true
# Ordinamento multiplo: prima per completamento, poi per titolo decrescente
GET /todos?sort=completed,asc&sort=title,desc
# Lista utenti, terza pagina con 20 elementi
GET /users?page=3&limit=20- ✅ Performance: Carica solo i dati necessari per la pagina richiesta
- ✅ Scalabilità: Gestisce grandi quantità di dati senza problemi di memoria
- ✅ Flessibilità: Parametri configurabili per adattarsi a diverse esigenze
- ✅ Navigazione: Metadati completi per implementare la navigazione tra pagine
- ✅ Filtri: Supporto per filtri opzionali (es:
completedper i todo)
L'applicazione utilizza un sistema di gestione errori rifattorizzato e centralizzato con due handler separati per una migliore organizzazione e manutenibilità.
Il sistema di gestione errori è diviso in due handler con priorità diversa:
-
CustomExceptionHandler(@Order(HIGHEST_PRECEDENCE))- Gestisce tutte le eccezioni custom dell'applicazione
- Ha priorità massima per essere valutato per primo
- Tutte le eccezioni custom implementano
BaseCustomException
-
GlobalExceptionHandler(@Order(LOWEST_PRECEDENCE))- Gestisce tutte le eccezioni native di Java/Spring
- Ha priorità minima come fallback per eccezioni non gestite altrove
Tutte le eccezioni custom dell'applicazione implementano l'interfaccia BaseCustomException, che definisce i metodi comuni (getStatusCode(), getErrorReasonPhrase(), getMessage()) per una gestione uniforme:
EmailConflictException- Conflitto email durante la registrazione (409 Conflict)EmptyFileException- File vuoto o nullo durante l'upload (400 Bad Request)InvalidCredentialsException- Credenziali non valide durante il login (400 Bad Request)InvalidFileTypeException- Tipo file non valido durante l'upload (400 Bad Request)InvalidSortablePropertyException- Proprietà di ordinamento non valida (400 Bad Request)S3UploadException- Errore durante l'upload su S3 (502 Bad Gateway)TodoNotFoundException- Todo non trovato (404 Not Found)UserNotFoundException- Utente non trovato (404 Not Found)
Il GlobalExceptionHandler gestisce tutte le eccezioni standard di Java/Spring:
ResponseStatusException- Eccezioni personalizzate con codice HTTPMethodArgumentNotValidException- Errori di validazione dei DTO (restituisceMap<String, String>con errori di validazione)HttpMessageNotReadableException- Body mancante o non validoMethodArgumentTypeMismatchException- Tipo parametro non valido (PathVariable/RequestParam)AuthorizationDeniedException- Accesso negato per autorizzazione (403 Forbidden)MultipartException- Errori durante l'upload di file multipartMissingServletRequestPartException- Parte della richiesta multipart mancanteMaxUploadSizeExceededException- File supera la dimensione massima consentitaException- Errori generici non gestiti (500 Internal Server Error)
Il sistema è stato rifattorizzato per eliminare la duplicazione del codice e migliorare la manutenibilità:
Il metodo statico ErrorResponse.buildResponse() centralizza la costruzione di ResponseEntity<ErrorResponse>:
public static ResponseEntity<ErrorResponse> buildResponse(
int statusCode, String reasonPhrase, Object message
)Vantaggi:
- Elimina la duplicazione tra i due handler
- Logica di costruzione centralizzata in un unico punto
- Manutenibilità migliorata (modifiche in un solo posto)
private ResponseEntity<ErrorResponse> handleException(BaseCustomException ex)Caratteristiche:
- Accetta direttamente l'istanza dell'eccezione
- Estrae automaticamente tutti i dati dall'interfaccia
BaseCustomException - Estrae automaticamente il nome della classe per il logging
private ResponseEntity<ErrorResponse> handleException(
Exception ex, int statusCode, String reasonPhrase, Object message
)Caratteristiche:
- Accetta l'istanza dell'eccezione e i parametri già estratti
- Necessario perché le eccezioni Spring native non hanno un'interfaccia comune
- Ogni handler method estrae manualmente i dati specifici, poi delega al metodo helper per logging e costruzione della risposta
Tutte le risposte di errore seguono il formato standardizzato:
{
"statusCode": 400,
"reason": "Bad Request",
"message": "Messaggio di errore",
"timestamp": "2024-01-01T10:00:00Z"
}Per errori di validazione multipli, il campo message può essere un oggetto:
{
"statusCode": 400,
"reason": "Bad Request",
"message": {
"email": "Email non valida",
"password": "Password troppo corta"
},
"timestamp": "2024-01-01T10:00:00Z"
}Note:
- Il campo
messagepuò essere unaStringo unObject(es:Map<String, String>per errori di validazione multipli) - Il campo
reasoncontiene la frase di errore HTTP standardizzata (es: "Bad Request", "Not Found") - Il campo
timestampviene aggiunto automaticamente in formato UTC - Nessuno stack trace viene esposto nelle risposte per motivi di sicurezza
- Il logging è consistente: tutti i log utilizzano il formato
{ExceptionName}: {statusCode} - {message}
Il progetto utilizza MapStruct per la conversione automatica tra entità JPA e DTO di risposta, eliminando completamente il boilerplate code manuale.
- Zero boilerplate: Le interfacce mapper contengono solo le definizioni dei metodi
- Type-safe: Tutti gli errori di mapping vengono rilevati a compile-time
- Performance: Il codice viene generato a compile-time (no reflection a runtime)
- Auto-mapping: MapStruct mappa automaticamente i campi con lo stesso nome
- Conversione automatica: Gestisce automaticamente conversioni di tipi annidati e liste
-
Interfacce Mapper: Definite in
src/main/java/.../mapper/@Mapper(componentModel = "spring") public interface TodoMapper { TodoResponse toDTO(Todo todo); }
-
Implementazioni Generate: MapStruct genera automaticamente le implementazioni in
target/generated-sources/annotations/durante la compilazione -
Componenti Spring: Le implementazioni generate sono automaticamente annotate con
@Component, quindi possono essere iniettate nei controller
MapStruct è configurato nel pom.xml:
- Dipendenza:
mapstruct(1.5.5.Final) - Annotation Processor:
mapstruct-processorconfigurato inmaven-compiler-plugin - Integrazione Lombok:
lombok-mapstruct-bindingper compatibilità con Lombok
- ✅ Nessun codice manuale: Tutto generato automaticamente
- ✅ Type-safe: Errori rilevati in compilazione
- ✅ Manutenibilità: Cambi alle entità/DTO si riflettono automaticamente
- ✅ Performance: Codice generato ottimizzato, no reflection
- ✅ Auto-mapping intelligente: Gestisce automaticamente conversioni complesse
MapStruct gestisce automaticamente:
- Campi semplici:
id,title,completed→ mappati per nome - Tipi annidati:
User→UserResponse(genera metodo helper automaticamente) - Liste:
List<Todo>→List<TodoResponse>(conversione automatica)
Le implementazioni generate sono visibili in target/generated-sources/annotations/com/example/dataware/todolist/mapper/ dopo la compilazione.
Il progetto implementa un sistema completo di Access Token e Refresh Token con persistenza e crittografia per migliorare la sicurezza e l'esperienza utente.
-
Login: Quando un utente effettua il login:
- Vengono generati due token: Access Token (breve durata) e Refresh Token (lunga durata)
- Il refresh token viene crittografato e salvato nel database nella colonna
refresh_tokendella tabellausers - Entrambi i token vengono restituiti al client
-
Utilizzo Access Token: L'access token viene inviato nell'header
Authorization: Bearer <accessToken>per tutte le richieste API protette. -
Refresh Token: Quando l'access token scade:
- Il client invia il refresh token all'endpoint
/auth/refresh-token - Il sistema valida il refresh token contro quello salvato nel database (dopo decrittografia)
- Se valido, genera una nuova coppia di token e aggiorna il refresh token nel database
- Il client invia il refresh token all'endpoint
-
Logout: Quando l'utente effettua il logout:
- Il refresh token viene rimosso dal database
- Il refresh token non può più essere utilizzato per ottenere nuovi token
- L'utente dovrà effettuare nuovamente il login
- Persistenza: I refresh token vengono salvati nel database nella colonna
refresh_tokendella tabellausers - Crittografia: I refresh token vengono crittografati prima di essere salvati usando Spring Security Crypto (
EncryptionService) - Validazione: Durante il refresh, il token fornito viene confrontato con quello salvato nel database (dopo decrittografia)
- Sicurezza: Anche se il database viene compromesso, i refresh token sono crittografati e non utilizzabili direttamente
Il sistema utilizza due filtri separati per gestire i diversi tipi di token:
-
JwtAccessFilter:
- Valida gli access token per tutti gli endpoint tranne
/auth/** - Estrae
userId,emaileroledal token e li inserisce nelSecurityContext - Aggiunge il ruolo alle authorities di Spring Security con il prefisso
ROLE_(es:ROLE_USER,ROLE_ADMIN) - Gestisce errori di token scaduto, malformato o mancante
- Valida gli access token per tutti gli endpoint tranne
-
JwtRefreshFilter:
- Valida i refresh token solo per l'endpoint
/auth/refresh-token - Estrae
userIdeemaildal refresh token - Recupera l'utente dal database e valida che il refresh token corrisponda a quello salvato
- Decrittografa il token salvato e lo confronta con quello fornito
- Permette l'accesso all'endpoint di refresh solo se la validazione ha successo
- Valida i refresh token solo per l'endpoint
- JwtService: Genera e valida i token JWT (access e refresh)
- EncryptionService: Crittografa e decrittografa i refresh token usando Spring Security Crypto
- AuthService: Gestisce login, logout e refresh token, coordinando JwtService e EncryptionService
- Chiavi Separate: Access token e refresh token utilizzano chiavi segrete diverse (
JWT_ACCESS_SECRETeJWT_REFRESH_SECRET) - Scadenze Diverse: Access token con durata breve, refresh token con durata più lunga
- Validazione Separata: Ogni tipo di token viene validato con la propria chiave segreta
- Enum TokenType: Utilizzato per distinguere tra ACCESS e REFRESH token durante la validazione
- Crittografia Database: I refresh token salvati sono crittografati con chiave e salt configurabili
- Validazione Database: Il refresh token viene validato contro quello salvato nel database
- Revoca Esplicita: Il logout rimuove il refresh token dal database, invalidandolo immediatamente
- ✅ Sicurezza Migliorata: Access token a breve durata limitano i danni in caso di compromissione
- ✅ Esperienza Utente: Nessun bisogno di ri-autenticarsi frequentemente grazie al refresh token
- ✅ Revoca Implicita: Quando un refresh token scade, l'utente deve ri-autenticarsi
- ✅ Revoca Esplicita: Il logout invalida immediatamente il refresh token
- ✅ Separazione delle Responsabilità: Chiavi e filtri separati per maggiore sicurezza
- ✅ Protezione Database: I refresh token salvati sono crittografati
- ✅ Validazione Robusta: Il refresh token deve corrispondere a quello salvato nel database
Il progetto implementa un sistema completo di autorizzazione basata su ruoli (RBAC) utilizzando Spring Security e le annotation @PreAuthorize.
Il sistema supporta due ruoli definiti nell'enum Role:
- USER: Ruolo standard per gli utenti registrati. Permette di gestire i propri todo e il proprio profilo.
- ADMIN: Ruolo amministratore con privilegi aggiuntivi. Oltre alle funzionalità di
USER, può visualizzare tutti gli utenti registrati nel sistema.
-
Registrazione: Quando un utente si registra, il ruolo viene impostato automaticamente a
USERtramite il metodo@PrePersistnell'entitàUser. -
Token JWT: Il ruolo dell'utente viene incluso nei token JWT (sia access che refresh) come claim
role. -
Validazione Token: Il
JwtAccessFilterestrae il ruolo dal token e lo aggiunge alle authorities di Spring Security con il prefissoROLE_(es:ROLE_USER,ROLE_ADMIN). -
Autorizzazione Endpoint: I controller utilizzano l'annotation
@PreAuthorizeper specificare quali ruoli possono accedere a ciascun endpoint:@PreAuthorize("hasAnyRole('USER', 'ADMIN')")- Richiede uno dei ruoli specificati@PreAuthorize("hasRole('ADMIN')")- Richiede esclusivamente il ruolo ADMIN
- SecurityConfig: Abilita
@EnableMethodSecurityper supportare le annotation@PreAuthorize - JwtAccessFilter: Estrae il ruolo dal token e lo aggiunge alle authorities del
SecurityContext - User Entity: Campo
roledi tipoRolecon defaultUSER
| Endpoint | Metodo | Ruolo Richiesto | Descrizione |
|---|---|---|---|
/auth/register |
POST | Nessuno | Registrazione pubblica |
/auth/login |
POST | Nessuno | Login pubblico |
/auth/refresh-token |
POST | Nessuno (richiede refresh token) | Refresh token |
/auth/logout |
DELETE | USER o ADMIN | Logout |
/todos |
GET, POST | USER o ADMIN | Gestione todo |
/todos/{id} |
GET, PATCH, DELETE | USER o ADMIN | Operazioni su singolo todo |
/users |
GET | ADMIN | Lista tutti gli utenti |
/users/profile |
GET | USER o ADMIN | Profilo utente autenticato |
/users/profile/image |
POST | USER o ADMIN | Carica immagine profilo |
/users/profile/image |
DELETE | USER o ADMIN | Elimina immagine profilo |
/users |
DELETE | USER o ADMIN | Elimina account utente |
@RestController
@RequestMapping("/users")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')") // Autorizzazione a livello di classe
public class UserController {
@GetMapping()
@PreAuthorize("hasRole('ADMIN')") // Autorizzazione a livello di metodo
public ResponseEntity<...> findAll() {
// Solo ADMIN può accedere
}
@GetMapping("/profile")
public ResponseEntity<...> getProfile() {
// USER o ADMIN possono accedere (eredita dalla classe)
}
}- ✅ Ruolo nel Token: Il ruolo è incluso nel JWT e viene validato ad ogni richiesta
- ✅ Method Security: Utilizzo di
@PreAuthorizeper controllo granulare degli accessi - ✅ Default Sicuro: Nuovi utenti ricevono automaticamente il ruolo
USER(privilegi minimi) - ✅ Separazione Ruoli: Chiaro distinguo tra privilegi USER e ADMIN
- ✅ Type-Safe: Utilizzo di enum per i ruoli (previene errori di digitazione)
Il progetto implementa un sistema completo di gestione immagini profilo utilizzando Amazon S3 per lo storage e Apache Tika per la validazione sicura dei file.
- Storage su S3: Le immagini profilo vengono caricate su Amazon S3 invece che sul filesystem locale
- Validazione Robusta: Utilizzo di Apache Tika per rilevare il MIME type dal contenuto del file (magic numbers), non dall'estensione
- Formati Supportati: JPEG, PNG, WEBP, GIF, HEIC, HEIF
- Gestione Rollback: Il vecchio file viene eliminato solo dopo il successo dell'upload e del salvataggio nel database
- Avatar Default: Sistema di fallback con avatar di default configurabile
- Dimensione Massima: 20MB per file (configurabile in
application.properties)
-
S3Service: Gestisce upload e delete dei file su S3
uploadUserProfileImage(): Carica un'immagine profilo su S3deleteFileByUrl(): Elimina un file da S3 usando il suo URL pubblicoextractKeyFromUrl(): Estrae la key S3 dall'URL (supporta virtual-hosted style)
-
ImageValidation: Valida i file immagine
validateAndGetImageMimeType(): Rileva il MIME type dal contenuto usando Apache Tika- Supporta solo formati immagine validi e sicuri
-
UserService: Coordina le operazioni
updateProfileImage(): Gestisce l'upload con rollback in caso di erroredeleteProfileImage(): Elimina l'immagine e ripristina il defaultdeleteImageFromS3(): Metodo privato per eliminazione sicura
- Validazione: Il file viene validato con Apache Tika per verificare che sia un'immagine valida
- Upload S3: L'immagine viene caricata su S3 con path:
users/{userId}/profile.{ext} - Salvataggio DB: L'URL dell'immagine viene salvato nel database
- Eliminazione Vecchio File: Solo dopo il successo del salvataggio, il vecchio file viene eliminato da S3
- ✅ Validazione Contenuto: Il MIME type viene rilevato dal contenuto, non dall'estensione (previene attacchi di tipo spoofing)
- ✅ Formati Limitati: Solo formati immagine sicuri sono supportati
- ✅ Dimensione Massima: Limite di 20MB per prevenire DoS
- ✅ Rollback Sicuro: Il vecchio file non viene eliminato se l'upload o il salvataggio falliscono
- ✅ Protezione Default: L'avatar di default non viene mai eliminato
Il client S3 viene configurato in S3Config utilizzando:
- Credentials: Access Key ID e Secret Access Key da variabili d'ambiente
- Region: Regione AWS configurabile
- Bucket: Nome del bucket S3 per lo storage
EmptyFileException: File nullo o vuoto (400 Bad Request)InvalidFileTypeException: Tipo file non supportato (400 Bad Request)S3UploadException: Errore durante l'upload su S3 (502 Bad Gateway)
# Carica immagine profilo
curl -X POST http://localhost:3001/users/profile/image \
-H "Authorization: Bearer <token>" \
-F "file=@profile.jpg"
# Elimina immagine profilo (ripristina default)
curl -X DELETE http://localhost:3001/users/profile/image \
-H "Authorization: Bearer <token>"- Le immagini vengono caricate con il formato originale mantenuto
- Il path su S3 è strutturato:
users/{userId}/profile.{ext} - Se l'utente carica una nuova immagine, quella precedente viene eliminata automaticamente
- L'eliminazione del vecchio file avviene solo dopo il successo dell'operazione per garantire consistenza
Il progetto implementa un sistema completo di rate limiting distribuito utilizzando Bucket4j con Redis per proteggere gli endpoint critici da abusi e attacchi DDoS.
- Rate Limiting Distribuito: Utilizza Redis per sincronizzare i limiti tra più istanze dell'applicazione
- Algoritmo Token Bucket: Implementato tramite Bucket4j con refill intervallato
- Configurazione per Endpoint: Ogni endpoint può avere limiti personalizzati
- Rilevamento IP Intelligente: Gestisce correttamente gli IP dei client dietro proxy e load balancer
- Header Informativi: Restituisce header HTTP standard (
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset) - Single Request Check: Ottimizzato per verificare il rate limit con una singola chiamata a Redis
-
RateLimitFilter: Filtro Spring che applica il rate limiting
- Estende
OncePerRequestFilterper essere eseguito una sola volta per richiesta - Posizionato prima di
JwtAccessFilternella catena dei filtri - Estrae l'IP reale del client considerando proxy/load balancer
- Imposta gli header di rate limit in tutte le risposte
- Estende
-
RateLimiteService: Servizio che gestisce la logica di rate limiting
- Utilizza Bucket4j con Redis (tramite Lettuce)
- Restituisce tutte le informazioni necessarie in una singola chiamata (
checkRateLimit) - Calcola il reset time solo quando necessario (quando il limite è stato superato)
-
RateLimitEndpoint: Enum che configura gli endpoint protetti
- Ogni endpoint ha: metodo HTTP, path, numero massimo richieste, finestra temporale
- Matching preciso per metodo HTTP e path
- Facilmente estendibile per aggiungere nuovi endpoint
-
RedisConfig: Configurazione Redis e Bucket4j
- Configura Lettuce client per connessione a Redis
- Configura ProxyManager per Bucket4j con supporto distribuito
- Supporta autenticazione Redis (password opzionale)
Il sistema protegge i seguenti endpoint con rate limiting:
| Endpoint | Metodo | Limite | Finestra |
|---|---|---|---|
/auth/register |
POST | 4 richieste | 60 secondi |
/auth/login |
POST | 4 richieste | 60 secondi |
/auth/logout |
DELETE | 4 richieste | 60 secondi |
/auth/refresh-token |
POST | 4 richieste | 60 secondi |
/users/profile/image |
POST | 2 richieste | 60 secondi |
/users/profile/image |
DELETE | 2 richieste | 60 secondi |
- Richiesta Incoming: Il
RateLimitFilterintercetta tutte le richieste - Verifica Endpoint: Controlla se l'endpoint richiede rate limiting tramite l'enum
RateLimitEndpoint - Estrae IP Client: Estrae l'IP reale del client considerando header
X-Forwarded-ForeX-Real-IP - Genera Key: Crea una chiave univoca nel formato:
{ENDPOINT_NAME}:{METHOD}:{CLIENT_IP} - Verifica Limite: Chiama
RateLimiteService.checkRateLimit()che:- Ottiene o crea un bucket per la chiave
- Tenta di consumare un token dal bucket
- Restituisce tutte le informazioni necessarie (allowed, remaining, resetTime)
- Imposta Header: Imposta gli header HTTP di rate limit nella risposta
- Gestisce Esito: Se il limite è stato superato, restituisce
429 Too Many Requests, altrimenti prosegue con la richiesta
Il sistema gestisce correttamente gli IP dei client in diversi scenari:
- Proxy/Load Balancer: Controlla l'header
X-Forwarded-Fore prende il primo IP della lista - X-Real-IP: Fallback all'header
X-Real-IPse presente - Connessione Diretta: Fallback a
request.getRemoteAddr()per connessioni dirette
private String getClientIpAddress(HttpServletRequest request) {
// 1. Controlla X-Forwarded-For (può contenere lista di IP)
// 2. Controlla X-Real-IP
// 3. Fallback a getRemoteAddr()
}Tutte le risposte includono gli header di rate limiting:
- X-RateLimit-Limit: Numero massimo di richieste consentite nella finestra temporale
- X-RateLimit-Remaining: Numero di richieste rimanenti nella finestra corrente
- X-RateLimit-Reset: Timestamp Unix (in secondi) quando il bucket si resetta (solo quando limitato)
Esempio di risposta:
X-RateLimit-Limit: 4
X-RateLimit-Remaining: 2
X-RateLimit-Reset: 1704110400
Quando il rate limit viene superato, il sistema restituisce:
Status Code: 429 Too Many Requests
Response Body:
{
"statusCode": 429,
"reason": "Too Many Requests",
"message": "Too many requests. Maximum 4 requests per 1 minute(s) allowed.",
"timestamp": "2024-01-01T10:00:00Z"
}Bucket4j utilizza l'algoritmo Token Bucket con refill intervallato:
- Capacity: Numero massimo di token nel bucket (es: 4)
- Refill: Il bucket si riempie completamente ogni
windowSeconds(es: 60 secondi) - Consumption: Ogni richiesta consuma 1 token
- Distribuito: I bucket sono sincronizzati tra tutte le istanze tramite Redis
Il sistema utilizza Redis per il rate limiting distribuito:
- Host: Configurabile tramite
REDIS_HOST(default:localhost) - Port: Configurabile tramite
REDIS_PORT(default:6379) - Password: Configurabile tramite
REDIS_PASSWORD(opzionale) - Client: Lettuce per operazioni distribuite
- Proxy Manager: Bucket4j Lettuce-based ProxyManager per sincronizzazione
- ✅ Protezione DDoS: Previene attacchi di tipo Denial of Service limitando le richieste
- ✅ Protezione Brute Force: Limita i tentativi di login/registrazione
- ✅ Distribuito: Funziona correttamente con più istanze dell'applicazione
- ✅ Efficiente: Singola chiamata a Redis per verificare il limite
- ✅ Trasparente: Header informativi per il client
- ✅ Configurabile: Facile aggiungere/modificare limiti per endpoint
- ✅ IP-Aware: Gestisce correttamente proxy e load balancer
- ✅ Produzione-Ready: Ottimizzato per ambienti di produzione
Per aggiungere un nuovo endpoint al rate limiting:
- Aggiungi una nuova entry all'enum
RateLimitEndpoint:
NEW_ENDPOINT(HttpMethod.POST, "/api/new-endpoint", 10, 60);- Il filtro applicherà automaticamente il rate limiting all'endpoint specificato.
- ✅ IP-Based: Il rate limiting è basato sull'IP del client
- ✅ Distribuito: I limiti sono sincronizzati tra tutte le istanze
- ✅ Non By-Passabile: Il filtro è posizionato all'inizio della catena
- ✅ Header Standard: Utilizza header HTTP standard per trasparenza
- ✅ Logging: Registra tutti i tentativi di superamento del limite
Progetto sviluppato per la gestione di una lista di attività (todo).