SecurityConfig.java
package com.archiweb.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorityAuthorizationManager;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final JwtFilter jwtFilter;
private final CustomUserDetailsService customUserDetailsService;
public SecurityConfig(JwtFilter jwtFilter, CustomUserDetailsService customUserDetailsService) {
this.jwtFilter = jwtFilter;
this.customUserDetailsService = customUserDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(customUserDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
// --- PUBLIC ---
.requestMatchers(
"/api/auth/**",
"/api/rgpd/**",
"/api/users/me",
"/api/settings/**",
"/api/contact/send",
"/api/newsletter/subscribe",
"/webjars/**",
"/actuator/**"
).permitAll()
// --- SWAGGER ---
.requestMatchers(
"/swagger-ui/**",
"/swagger-ui.html",
"/v3/api-docs/**",
"/v3/api-docs.yaml"
).access((authentication, context) -> {
String activeProfile = System.getProperty("spring.profiles.active", "dev");
if ("prod".equalsIgnoreCase(activeProfile)) {
return AuthorityAuthorizationManager
.hasAuthority("ROLE_ADMIN").check(authentication, context);
}
return new AuthorizationDecision(true);
})
// Exception : le mode Noël est accessible à tous les utilisateurs authentifiés
// (doit être AVANT la règle générale /api/admin/**)
.requestMatchers("/api/admin/settings/christmas-mode")
.authenticated()
// --- ADMIN ---
.requestMatchers("/api/admin/**", "/api/stats/**")
.hasAuthority("ROLE_ADMIN")
// --- EMPLOYEE ---
// TEMPORAIRE : API POS accessible à tous
.requestMatchers("/api/store/**")
.hasAuthority("ROLE_EMPLOYEE")
.requestMatchers("/api/pos/**")
.permitAll()
// --- WINS HOME DELIVERY - ADMIN et EMPLOYEE uniquement ---
.requestMatchers("/api/wins/home-delivery/**")
.hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE")
// --- CONTACT MESSAGES - ADMIN et EMPLOYEE uniquement (sauf /send qui est public) ---
.requestMatchers("/api/contact/messages/**")
.hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE")
// --- NEWSLETTER - ADMIN et EMPLOYEE pour toutes les routes sauf /subscribe (qui est déjà dans permitAll) ---
// IMPORTANT: /api/newsletter/subscribe est déjà dans permitAll() plus haut, donc il sera traité en premier
// Cette règle s'applique à toutes les autres routes newsletter
.requestMatchers("/api/newsletter/**")
.hasAnyAuthority("ROLE_ADMIN", "ROLE_EMPLOYEE")
// --- USERS / ALL ROLES ---
.requestMatchers("/api/wins/**", "/api/participations/**")
.authenticated()
// *** FIX MAJEUR ***
// --- GAME ROUTES ---
.requestMatchers("/api/game/**")
.hasAnyAuthority("ROLE_USER", "ROLE_ADMIN", "ROLE_EMPLOYEE")
// Le reste
.anyRequest().authenticated()
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Ne pas utiliser "*" avec allowCredentials(true) - utiliser des patterns spécifiques
configuration.setAllowedOriginPatterns(Arrays.asList(
"http://localhost:*",
"https://localhost:*",
"http://*.dsp5-archi-o24a-15m-g2.fr",
"https://*.dsp5-archi-o24a-15m-g2.fr",
"http://dsp5-archi-o24a-15m-g2.fr",
"https://dsp5-archi-o24a-15m-g2.fr",
"http://integ.dsp5-archi-o24a-15m-g2.fr",
"https://integ.dsp5-archi-o24a-15m-g2.fr",
"http://34.46.110.24:*",
"https://34.46.110.24:*"
));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}