SecurityConfig.java
package com.ctrlbuy.webshop.config;
import com.ctrlbuy.webshop.service.CustomOAuth2UserService;
import com.ctrlbuy.webshop.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 🛡️ Aktiverar @PreAuthorize annotationer
public class SecurityConfig {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private CustomOAuth2UserService customOAuth2UserService;
@Value("${app.security.remember-me.token-validity-seconds:2592000}")
private int rememberMeTokenValiditySeconds;
@Value("${app.security.remember-me.key:uniqueAndSecretKey2025CtrlBuy}")
private String rememberMeKey;
@Value("${app.security.remember-me.parameter:remember-me}")
private String rememberMeParameter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
// 🔥 H2-KONSOLL - MÅSTE VARA FÖRST!
.requestMatchers("/h2-console/**").permitAll()
// 📁 Statiska resurser - tillgängliga för alla
.requestMatchers("/css/**", "/js/**", "/images/**", "/webjars/**", "/favicon.ico").permitAll()
// 🏠 Publika sidor - ingen inloggning krävs
.requestMatchers("/", "/home", "/home/**", "/about", "/om-oss", "/produkter", "/produkter/**", "/kontakt", "/support", "/debug-products").permitAll()
// 🛍️ Produktsidor - publikt tillgängliga
.requestMatchers("/products", "/products/**").permitAll()
// 🔐 Autentisering och registrering - publikt tillgängliga
.requestMatchers("/login", "/login/**", "/register", "/register/**").permitAll()
// 🌐 OAuth2 login flow
.requestMatchers("/oauth2/**", "/login/oauth2/**").permitAll()
// 📧 E-postverifiering och relaterade endpoints
.requestMatchers("/verify-email", "/verify-email/**").permitAll()
.requestMatchers("/resend-verification", "/resend-verification/**").permitAll()
// 🔑 Lösenordsåterställning - publikt tillgängliga
.requestMatchers("/forgot-password", "/forgot-password/**").permitAll()
.requestMatchers("/reset-password", "/reset-password/**").permitAll()
// 🧪 Test endpoints - utveckling
.requestMatchers("/test-email", "/test-email/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
// ❌ Error och access denied sidor - publikt tillgängliga
.requestMatchers("/error", "/access-denied").permitAll()
// 🛡️ ADMIN ENDPOINTS - KRÄVER ROLE_ADMIN (dubbel säkerhet)
.requestMatchers("/admin/**").hasRole("ADMIN")
// 🛒 Varukorg - tillgänglig för alla (session-baserad)
.requestMatchers("/cart/**", "/varukorg/**").permitAll()
// 💳 Checkout och betalningar - kräver inloggning
.requestMatchers("/checkout/**", "/betalningsmetoder/**").authenticated()
// 📦 Orderhantering - kräver inloggning
.requestMatchers("/orders/**", "/order/**").authenticated()
// 📄 Coming Soon sidor - publikt tillgängliga
.requestMatchers("/returer", "/spara-bestallning", "/garantivillkor", "/coming-soon").permitAll()
// 👤 Profil-sidor - kräver inloggning (vilken roll som helst)
.requestMatchers("/min-profil", "/min-profil/**", "/profile/**").authenticated()
// 🌐 Allt annat är publikt (fallback)
.anyRequest().permitAll()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.failureUrl("/login?error=true")
.permitAll()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.defaultSuccessUrl("/home", true)
.failureUrl("/login?error=true")
)
.rememberMe(remember -> remember
.key(rememberMeKey)
.tokenValiditySeconds(rememberMeTokenValiditySeconds)
.userDetailsService(customUserDetailsService)
.rememberMeParameter(rememberMeParameter)
.rememberMeCookieName("ctrlbuy-remember-me")
.alwaysRemember(false)
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout=true")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "ctrlbuy-remember-me")
.clearAuthentication(true)
.permitAll()
)
// 🚫 EXCEPTION HANDLING - SNYGG ACCESS DENIED HANTERING
.exceptionHandling(exceptions -> exceptions
.accessDeniedPage("/access-denied") // Snygg access denied sida
.authenticationEntryPoint((request, response, authException) -> {
// Om inte inloggad och försöker nå admin, omdirigera till login
String requestURI = request.getRequestURI();
if (requestURI.startsWith("/admin")) {
response.sendRedirect("/login?message=admin_required");
} else if (requestURI.startsWith("/checkout") || requestURI.startsWith("/orders")) {
response.sendRedirect("/login?message=login_required");
} else {
response.sendRedirect("/login");
}
})
)
// 🛡️ CSRF-SKYDD MED TOKENS FÖR ADMIN-PANEL
.csrf(csrf -> csrf
.ignoringRequestMatchers("/h2-console/**", "/test-email/**", "/api/test/**")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler())
)
// 🖼️ Headers för H2-konsoll
.headers(headers -> headers
.frameOptions(frameOptions -> frameOptions.sameOrigin())
)
.userDetailsService(customUserDetailsService);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}