diff --git a/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java b/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java new file mode 100644 index 0000000..7ab0abf --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/auth/ApiKeyAuthFilter.java @@ -0,0 +1,68 @@ +package com.litoralregas.vpnprovisioner.auth; + +import com.litoralregas.vpnprovisioner.config.AppSecurityProperties; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +public class ApiKeyAuthFilter extends OncePerRequestFilter { + + private static final String API_KEY_HEADER = "X-API-Key"; + + private final AppSecurityProperties securityProperties; + + public ApiKeyAuthFilter(AppSecurityProperties securityProperties) { + this.securityProperties = securityProperties; + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return "/actuator/health".equals(request.getRequestURI()); + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + String providedApiKey = request.getHeader(API_KEY_HEADER); + String expectedApiKey = securityProperties.getApiKey(); + + if (!StringUtils.hasText(expectedApiKey)) { + response.sendError( + HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "API key is not configured" + ); + return; + } + + if (!expectedApiKey.equals(providedApiKey)) { + response.sendError( + HttpServletResponse.SC_UNAUTHORIZED, + "Invalid API key" + ); + return; + } + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + "api-key-client", + null, + List.of() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/config/AppSecurityProperties.java b/src/main/java/com/litoralregas/vpnprovisioner/config/AppSecurityProperties.java new file mode 100644 index 0000000..bd4f694 --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/config/AppSecurityProperties.java @@ -0,0 +1,17 @@ +package com.litoralregas.vpnprovisioner.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "app.security") +public class AppSecurityProperties { + + private String apiKey; + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java b/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java new file mode 100644 index 0000000..3253f9e --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/config/SecurityConfig.java @@ -0,0 +1,34 @@ +package com.litoralregas.vpnprovisioner.config; + +import com.litoralregas.vpnprovisioner.auth.ApiKeyAuthFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableConfigurationProperties(AppSecurityProperties.class) +public class SecurityConfig { + + private final AppSecurityProperties securityProperties; + + public SecurityConfig(AppSecurityProperties securityProperties) { + this.securityProperties = securityProperties; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + ApiKeyAuthFilter apiKeyAuthFilter = new ApiKeyAuthFilter(securityProperties); + + return http + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/actuator/health").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(apiKeyAuthFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } +} \ No newline at end of file