diff --git a/pom.xml b/pom.xml index 876537c..7abe857 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,12 @@ spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + org.springframework.boot diff --git a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java index 2d61c22..b9047ad 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/SecurityConfig.java @@ -3,6 +3,7 @@ package net.siegeln.cameleer.saas.config; import net.siegeln.cameleer.saas.auth.JwtAuthenticationFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; 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; @@ -17,23 +18,39 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @EnableMethodSecurity public class SecurityConfig { - private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final JwtAuthenticationFilter machineTokenFilter; - public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { - this.jwtAuthenticationFilter = jwtAuthenticationFilter; + public SecurityConfig(JwtAuthenticationFilter machineTokenFilter) { + this.machineTokenFilter = machineTokenFilter; } @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + @Order(1) + public SecurityFilterChain machineAuthFilterChain(HttpSecurity http) throws Exception { + http + .securityMatcher("/api/agent/**", "/api/license/verify/**") + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .addFilterBefore(machineTokenFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + @Order(2) + public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/actuator/health").permitAll() + .requestMatchers("/auth/verify").permitAll() .anyRequest().authenticated() ) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> {})) + .addFilterBefore(machineTokenFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index eed0bb4..308b82c 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -3,3 +3,8 @@ spring: show-sql: false flyway: clean-disabled: false + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://test-issuer.example.com/oidc diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6b2a4d8..8de3917 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,6 +8,12 @@ spring: flyway: enabled: true locations: classpath:db/migration + security: + oauth2: + resourceserver: + jwt: + issuer-uri: ${LOGTO_ISSUER_URI:} + jwk-set-uri: ${LOGTO_JWK_SET_URI:} management: endpoints: @@ -23,3 +29,7 @@ cameleer: expiration: 86400 # 24 hours in seconds private-key-path: ${CAMELEER_JWT_PRIVATE_KEY_PATH:} public-key-path: ${CAMELEER_JWT_PUBLIC_KEY_PATH:} + identity: + logto-endpoint: ${LOGTO_ENDPOINT:} + m2m-client-id: ${LOGTO_M2M_CLIENT_ID:} + m2m-client-secret: ${LOGTO_M2M_CLIENT_SECRET:} diff --git a/src/test/java/net/siegeln/cameleer/saas/CameleerSaasApplicationTest.java b/src/test/java/net/siegeln/cameleer/saas/CameleerSaasApplicationTest.java index 4b31ec8..70bfba7 100644 --- a/src/test/java/net/siegeln/cameleer/saas/CameleerSaasApplicationTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/CameleerSaasApplicationTest.java @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; @SpringBootTest -@Import(TestcontainersConfig.class) +@Import({TestcontainersConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") class CameleerSaasApplicationTest { diff --git a/src/test/java/net/siegeln/cameleer/saas/TestSecurityConfig.java b/src/test/java/net/siegeln/cameleer/saas/TestSecurityConfig.java new file mode 100644 index 0000000..b305ae9 --- /dev/null +++ b/src/test/java/net/siegeln/cameleer/saas/TestSecurityConfig.java @@ -0,0 +1,24 @@ +package net.siegeln.cameleer.saas; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; + +import java.time.Instant; +import java.util.Map; + +@TestConfiguration +public class TestSecurityConfig { + + @Bean + public JwtDecoder jwtDecoder() { + return token -> Jwt.withTokenValue(token) + .header("alg", "RS256") + .claim("sub", "test-user") + .claim("iss", "https://test-issuer.example.com/oidc") + .issuedAt(Instant.now()) + .expiresAt(Instant.now().plusSeconds(3600)) + .build(); + } +} diff --git a/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java index d874094..d672c07 100644 --- a/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/auth/AuthControllerTest.java @@ -2,6 +2,7 @@ package net.siegeln.cameleer.saas.auth; import com.fasterxml.jackson.databind.ObjectMapper; import net.siegeln.cameleer.saas.TestcontainersConfig; +import net.siegeln.cameleer.saas.TestSecurityConfig; import net.siegeln.cameleer.saas.auth.dto.LoginRequest; import net.siegeln.cameleer.saas.auth.dto.RegisterRequest; import org.junit.jupiter.api.Test; @@ -20,7 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc -@Import(TestcontainersConfig.class) +@Import({TestcontainersConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") class AuthControllerTest { diff --git a/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java index 6d89f68..b662626 100644 --- a/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/license/LicenseControllerTest.java @@ -2,6 +2,7 @@ package net.siegeln.cameleer.saas.license; import com.fasterxml.jackson.databind.ObjectMapper; import net.siegeln.cameleer.saas.TestcontainersConfig; +import net.siegeln.cameleer.saas.TestSecurityConfig; import net.siegeln.cameleer.saas.tenant.dto.CreateTenantRequest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,7 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc -@Import(TestcontainersConfig.class) +@Import({TestcontainersConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") class LicenseControllerTest { diff --git a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java index a663583..ad36932 100644 --- a/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java +++ b/src/test/java/net/siegeln/cameleer/saas/tenant/TenantControllerTest.java @@ -2,6 +2,7 @@ package net.siegeln.cameleer.saas.tenant; import com.fasterxml.jackson.databind.ObjectMapper; import net.siegeln.cameleer.saas.TestcontainersConfig; +import net.siegeln.cameleer.saas.TestSecurityConfig; import net.siegeln.cameleer.saas.tenant.dto.CreateTenantRequest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,7 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @AutoConfigureMockMvc -@Import(TestcontainersConfig.class) +@Import({TestcontainersConfig.class, TestSecurityConfig.class}) @ActiveProfiles("test") class TenantControllerTest {