package at.eisibaer.jbear2.security import at.eisibaer.jbear2.config.ApplicationProperties import jakarta.servlet.FilterChain import jakarta.servlet.ServletException import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletResponse import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.security.authentication.AuthenticationManager 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.core.userdetails.UserDetailsService import org.springframework.security.crypto.argon2.Argon2PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.web.SecurityFilterChain import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter import org.springframework.security.web.authentication.www.BasicAuthenticationFilter import org.springframework.security.web.csrf.* import org.springframework.util.StringUtils import org.springframework.web.filter.OncePerRequestFilter import java.io.IOException import java.util.function.Supplier @Configuration @EnableMethodSecurity class SecurityConfiguration( private val userDetailService: UserDetailsService, private val unauthorizedHandler: AuthFilter, private val applicationProperties: ApplicationProperties ) { final val log: Logger = LoggerFactory.getLogger(SecurityConfiguration::class.java); @Bean fun securityFilterChain(httpSecurity: HttpSecurity): SecurityFilterChain { return addCsrfConfig(httpSecurity) .authorizeHttpRequests { config -> config .requestMatchers("/api/auth/*").permitAll() .requestMatchers("/api/user/**").authenticated() .requestMatchers("/**").permitAll() } .authenticationProvider(authenticationProvider()) .addFilterBefore(unauthorizedHandler, UsernamePasswordAuthenticationFilter::class.java) .build() } private fun addCsrfConfig(httpSecurity: HttpSecurity): HttpSecurity{ if( applicationProperties.test ){ httpSecurity.csrf{ config -> config.disable()}; } else { httpSecurity.csrf { config -> config .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(SpaCsrfTokenRequestHandler()) } .addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) } return httpSecurity; } class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() { private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler() override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier) { /* * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of * the CsrfToken when it is rendered in the response body. */ delegate.handle(request, response, csrfToken) } override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? { /* * If the request contains a request header, use CsrfTokenRequestAttributeHandler * to resolve the CsrfToken. This applies when a single-page application includes * the header value automatically, which was obtained via a cookie containing the * raw CsrfToken. */ return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) { super.resolveCsrfTokenValue(request, csrfToken) } else { /* * In all other cases (e.g. if the request contains a request parameter), use * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies * when a server-side rendered form includes the _csrf request parameter as a * hidden input. */ delegate.resolveCsrfTokenValue(request, csrfToken) } } } class CsrfCookieFilter : OncePerRequestFilter() { @Throws(ServletException::class, IOException::class) override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { val csrfToken = request.getAttribute("_csrf") as CsrfToken // Render the token value to a cookie by causing the deferred token to be loaded csrfToken.token filterChain.doFilter(request, response) } } @Bean fun passwordEncoder() : PasswordEncoder { return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); } @Bean fun authenticationProvider(): DaoAuthenticationProvider{ val authProvider: DaoAuthenticationProvider = DaoAuthenticationProvider() authProvider.setUserDetailsService(userDetailService) authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean fun authenticationManager(authConfig: AuthenticationConfiguration): AuthenticationManager{ return authConfig.authenticationManager; } }