package es.tatvil.taiageweb.config; import es.tatvil.taiageweb.servicio.UsuarioService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; /** * Configuración de seguridad de la aplicación. * *

Define las reglas de acceso HTTP, el proveedor de autenticación basado en * base de datos y la configuración del formulario de login/logout.

* * */ @Configuration @EnableWebSecurity public class SecurityConfig { /** * Crea el {@link PasswordEncoder} BCrypt usado en toda la aplicación. * * @return encoder BCrypt con factor de coste por defecto (10) */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * Crea el proveedor de autenticación DAO que consulta {@link UsuarioService}. * * @param service servicio que implementa {@link org.springframework.security.core.userdetails.UserDetailsService} * @param encoder encoder de contraseñas * @return proveedor configurado */ @Bean public DaoAuthenticationProvider authProvider(UsuarioService service, PasswordEncoder encoder) { var provider = new DaoAuthenticationProvider(service); provider.setPasswordEncoder(encoder); return provider; } /** * Define la cadena de filtros de seguridad HTTP. * * @param http objeto de configuración de Spring Security * @return cadena de filtros construida * @throws Exception si la configuración es inválida */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.ignoringRequestMatchers("/webhook/stripe")) .headers(headers -> headers .frameOptions(frame -> frame.sameOrigin()) ) .authorizeHttpRequests(auth -> auth // Recursos públicos .requestMatchers( "/", "/inicio", "/login", "/registro", "/leyes", "/noticias", "/acceso-denegado", "/error", "/webhook/stripe", "/css/**", "/js/**", "/images/**", "/leyes/**", "/audios/**", "/favicon.ico" ).permitAll() // Panel de administración .requestMatchers("/admin/**").hasRole("ADMIN") // Contenido de pago .requestMatchers("/curso", "/curso/**", "/planning", "/esquema.html", "/flashcards.html", "/flashcards/**", "/api/**").hasAnyRole("PAGADO", "ADMIN") // Cualquier otra ruta requiere autenticación .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .usernameParameter("email") // el campo del form se llama "email" .defaultSuccessUrl("/", false) .failureUrl("/login?error") .permitAll() ) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .permitAll() ) .exceptionHandling(ex -> ex .accessDeniedPage("/acceso-denegado") ); return http.build(); } }