security: remove credentials from docs, centralize CORS, add reverse proxy support

This commit is contained in:
Nabil Derouiche 2026-04-19 21:56:08 +01:00
parent f40888e7ee
commit 088f2b8736
21 changed files with 40 additions and 21 deletions

3
.gitignore vendored
View File

@ -37,5 +37,8 @@ frontend/.flutter-plugins
frontend/.flutter-plugins-dependencies
frontend/build/
# Claude Code (contient des tokens et commandes sensibles)
.claude/
# Docs sensibles
memoire de fin d'etude.docx

View File

@ -14,7 +14,7 @@
| 3 | Backend Spring Boot API | ✅ Déployé & Fonctionnel |
| 4 | Frontend Flutter | ⏳ À faire |
| 5 | Tests & Validation | 🔄 En cours |
| 6 | Déploiement Production | ✅ Docker sur 192.168.100.33:8090 |
| 6 | Déploiement Production | ✅ Docker — https://rayhan-erp.bolbol.tn |
| 7 | Rapport de PFE | 🔄 En cours |
---
@ -88,11 +88,9 @@
## Infrastructure Serveur
- **Serveur local** : 192.168.100.33
- **SSH** : port 22222, user Best0f
- **Portainer** : http://192.168.100.33:9000/
- **Gitea** : https://gitea.bolbol.tn
- **API REST** : http://192.168.100.33:8090 ✅ En ligne
- **API REST (production)** : https://rayhan-erp.bolbol.tn ✅ En ligne
- **API REST (local dev)** : http://localhost:8090
- **Conteneurs Docker** : `rayhan-mysql` + `rayhan-backend`
---

View File

@ -179,7 +179,7 @@ Password : rayhan_erp_2024
> MySQL Workbench : https://dev.mysql.com/downloads/workbench/
**Note :** Sur le serveur de production (192.168.100.33), le port MySQL n'est pas exposé à l'extérieur par mesure de sécurité.
**Note :** Sur le serveur de production (localhost), le port MySQL n'est pas exposé à l'extérieur par mesure de sécurité.
---

View File

@ -3,12 +3,12 @@
## Configuration de Base
**URL de base** : `http://192.168.100.33:8090`
**URL de base** : `https://rayhan-erp.bolbol.tn`
### 1. Configurer une Variable d'Environnement Postman
Dans Postman, créer un environnement "Rayhan ERP" avec :
- `baseUrl` = `http://192.168.100.33:8090`
- `baseUrl` = `https://rayhan-erp.bolbol.tn`
- `token` = (sera rempli automatiquement)
---

View File

@ -204,7 +204,7 @@ La base de données `rayhan_erp_db` contient les tables suivantes :
## 4.1 Infrastructure
L'application est déployée sur un serveur NAS Synology (192.168.100.33) via Docker Compose. Deux conteneurs sont en production :
L'application est déployée via Docker Compose, accessible publiquement derrière un reverse proxy HTTPS. Deux conteneurs sont en production :
- **rayhan-mysql** — MySQL 8, base de données `rayhan_erp_db`
- **rayhan-backend** — Spring Boot, accessible sur le port **8090**
@ -254,8 +254,8 @@ ENTRYPOINT ["java", "-jar", "app.jar"]
| Service | URL |
|---------|-----|
| API REST | http://192.168.100.33:8090 |
| Documentation Swagger UI | http://192.168.100.33:8090/swagger-ui/index.html |
| API REST | https://rayhan-erp.bolbol.tn |
| Documentation Swagger UI | https://rayhan-erp.bolbol.tn/swagger-ui/index.html |
| Dépôt source (Gitea) | https://gitea.bolbol.tn/bolbol/rayhan-erp |
---
@ -272,7 +272,7 @@ L'API intègre **Swagger UI** (SpringDoc OpenAPI 2.5.0), accessible depuis n'imp
**Procédure de test :**
1. Ouvrir http://192.168.100.33:8090/swagger-ui/index.html
1. Ouvrir https://rayhan-erp.bolbol.tn/swagger-ui/index.html
2. Exécuter `POST /api/auth/signin` avec `{"username":"admin","password":"Rayhan2024!"}`
3. Copier le token de la réponse
4. Cliquer sur **Authorize 🔒** → coller le token → Authorize

View File

@ -17,6 +17,11 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableMethodSecurity
@ -51,9 +56,26 @@ public class WebSecurityConfig {
return new BCryptPasswordEncoder();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of(
"https://rayhan-erp.bolbol.tn",
"http://localhost:*",
"http://127.0.0.1:*"
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

View File

@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/articles")
public class ArticleController {

View File

@ -26,7 +26,6 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthController {

View File

@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/clients")
public class ClientController {

View File

@ -15,7 +15,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/dashboard")
public class DashboardController {

View File

@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/fournisseurs")
public class FournisseurController {

View File

@ -18,7 +18,6 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Map;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/production")
public class ProductionOrderController {

View File

@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/purchase-orders")
public class PurchaseOrderController {

View File

@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/sales-orders")
public class SalesOrderController {

View File

@ -17,7 +17,6 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/stock")
public class StockController {

View File

@ -6,6 +6,11 @@
# Serveur
server.port=8080
# Reverse proxy HTTPS (Nginx/Traefik devant le conteneur)
server.forward-headers-strategy=framework
server.tomcat.remoteip.remote-ip-header=x-forwarded-for
server.tomcat.remoteip.protocol-header=x-forwarded-proto
# Base de données MySQL
spring.datasource.url=jdbc:mysql://localhost:3306/rayhan_erp_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Africa/Tunis
spring.datasource.username=root