Compare commits

...

7 Commits

Author SHA1 Message Date
1e41b75502 commented
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-14 21:18:48 +02:00
acf9ec8a2c Add TestSecurityConfig for improved test setup and update configurations
All checks were successful
continuous-integration/drone/push Build is passing
- Introduce `TestSecurityConfig` to simplify JWT usage in test environments.
- Update integration tests to import `TestSecurityConfig`.
- Split environment-specific configurations into new `application-dev.yml` and `applications-prod.yml` files.
- Adjust `docker-compose.yml` for development-specific settings.
- Clean up redundant JWT properties in `application.yml`.
2025-10-14 16:08:01 +02:00
004ea4eca4 Refine CORS settings and update security configuration
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-13 21:28:25 +02:00
a56d995d0f Add CORS configuration and update Keycloak hostname settings
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-13 21:12:51 +02:00
a3ad34d094 Remove unnecessary JWT logging in MeController
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-12 21:06:33 +02:00
b7a3103837 Remove unnecessary JWT logging in MeController
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-12 12:15:59 +02:00
c54d0214f3 Update issuer-uri in application.yml to new Keycloak endpoint 2025-10-12 12:10:19 +02:00
16 changed files with 121 additions and 10 deletions

View File

@ -21,10 +21,16 @@ services:
KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin KEYCLOAK_ADMIN_PASSWORD: admin
# Make issuer consistent & reachable from other containers # Make issuer consistent & reachable from other containers
KC_HOSTNAME: keycloak # KC_HOSTNAME: keycloak
KC_HTTP_ENABLED: "true" KC_HTTP_ENABLED: "true"
KC_HOSTNAME_STRICT: "false" KC_HOSTNAME_STRICT: "false"
KC_PROXY: edge KC_PROXY: edge
KC_HOSTNAME_URL: "http://localhost:8081/"
KC_HOSTNAME_ADMIN_URL: "http://localhost:8081/"
KC_HOSTNAME_STRICT_HTTPS: "false"
extra_hosts:
- "host.docker.internal:host-gateway"
ports: ports:
- "8081:8081" - "8081:8081"
volumes: volumes:
@ -40,6 +46,9 @@ services:
depends_on: depends_on:
postgres: { condition: service_healthy } postgres: { condition: service_healthy }
environment: environment:
# Aktivera Spring-profilen "dev"
SPRING_PROFILES_ACTIVE: dev
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/hemhub SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/hemhub
SPRING_DATASOURCE_USERNAME: hemhub SPRING_DATASOURCE_USERNAME: hemhub
SPRING_DATASOURCE_PASSWORD: hemhub SPRING_DATASOURCE_PASSWORD: hemhub

View File

@ -2,11 +2,18 @@ package se.urmo.hemhub.config;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; // 👈
import org.springframework.security.config.Customizer; // 👈
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration @Configuration
@EnableMethodSecurity @EnableMethodSecurity
@ -16,18 +23,37 @@ public class SecurityConfig {
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http
.csrf(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers( .requestMatchers(
"/public/**", "/public/**",
"/actuator/health", "/actuator/info", "/actuator/health", "/actuator/info",
"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html" "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html"
).permitAll() ).permitAll()
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter()))); .oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter())));
return http.build(); return http.build();
} }
@Bean
CorsConfigurationSource corsConfigurationSource() {
var config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"http://localhost:5173", // dev-SPA
"https://rubble.se" // prod-origin (SPA ligger under subpath men origin är domen)
));
config.setAllowedMethods(List.of("GET","POST","PATCH","DELETE","OPTIONS"));
config.setAllowedHeaders(List.of("Authorization","Content-Type","Accept"));
config.setAllowCredentials(false); // Bearer, inga cookies
config.setMaxAge(3600L);
var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
@Bean @Bean
JwtAuthenticationConverter jwtConverter() { JwtAuthenticationConverter jwtConverter() {
var converter = new JwtAuthenticationConverter(); var converter = new JwtAuthenticationConverter();
@ -35,4 +61,3 @@ public class SecurityConfig {
return converter; return converter;
} }
} }

View File

@ -19,7 +19,6 @@ public class MeController {
// ---- Läs roller på ett säkert sätt ---- // ---- Läs roller på ett säkert sätt ----
List<String> roles = extractRealmRoles(jwt); List<String> roles = extractRealmRoles(jwt);
log.info("Jwt: {}", jwt);
// ---- Bygg svar ---- // ---- Bygg svar ----
Map<String, Object> response = new LinkedHashMap<>(); Map<String, Object> response = new LinkedHashMap<>();
response.put("sub", jwt.getSubject()); response.put("sub", jwt.getSubject());

View File

@ -0,0 +1,27 @@
server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://postgres:5432/hemhub
username: hemhub
password: hemhub
jpa:
hibernate:
ddl-auto: none
flyway:
enabled: true
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://host.docker.internal:8081/realms/hemhub/protocol/openid-connect/certs
springdoc:
swagger-ui: # (valfritt, behåll om du redan har)
url: /v3/api-docs
logging:
level:
org.springframework.security: DEBUG
org.springframework.security.oauth2: DEBUG

View File

@ -11,12 +11,10 @@ spring:
ddl-auto: none ddl-auto: none
flyway: flyway:
enabled: true enabled: true
security: springdoc:
oauth2: swagger-ui: # (valfritt, behåll om du redan har)
resourceserver: url: /v3/api-docs
jwt:
jwk-set-uri: http://keycloak:8080/realms/hemhub/protocol/openid-connect/certs
issuer-uri: https://rubbel.se/hemhub/auth/realms/hemhub
hemhub: hemhub:
schedule: schedule:
reminders: reminders:

View File

@ -0,0 +1,7 @@
spring:
security:
oauth2:
resourceserver:
jwt:
#jwk-set-uri: http://keycloak:8080/realms/hemhub/protocol/openid-connect/certs
issuer-uri: https://rubble.se/hemhub/auth/realms/hemhub

View File

@ -4,8 +4,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -19,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class HouseholdControllerIT { class HouseholdControllerIT {
@Autowired @Autowired

View File

@ -4,8 +4,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import java.util.Map; import java.util.Map;
@ -17,6 +19,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class MeControllerBranchesIT { class MeControllerBranchesIT {
@Autowired @Autowired

View File

@ -4,8 +4,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -18,6 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class MeControllerIT { class MeControllerIT {
@Autowired @Autowired

View File

@ -5,9 +5,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import java.time.LocalDate; import java.time.LocalDate;
@ -19,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class PagingAndFilteringIT { class PagingAndFilteringIT {
@Autowired MockMvc mvc; @Autowired MockMvc mvc;

View File

@ -5,8 +5,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
@ -15,6 +17,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class ProjectTaskControllerIT { class ProjectTaskControllerIT {
@Autowired MockMvc mvc; @Autowired MockMvc mvc;

View File

@ -4,8 +4,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@ -14,6 +16,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class PublicControllerIT { class PublicControllerIT {
@Autowired @Autowired

View File

@ -5,8 +5,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import java.time.LocalDate; import java.time.LocalDate;
@ -19,6 +21,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class TaskDueControllerIT { class TaskDueControllerIT {
@Autowired @Autowired

View File

@ -5,9 +5,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import se.urmo.hemhub.support.TestSecurityConfig;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
@ -16,6 +18,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc
@ActiveProfiles("test") @ActiveProfiles("test")
@Import(TestSecurityConfig.class)
class ValidationAndErrorHandlingIT { class ValidationAndErrorHandlingIT {
@Autowired MockMvc mvc; @Autowired MockMvc mvc;

View File

@ -0,0 +1,22 @@
package se.urmo.hemhub.support;
import java.time.Instant;
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;
@TestConfiguration
public class TestSecurityConfig {
@Bean
JwtDecoder jwtDecoder() {
return token -> Jwt.withTokenValue(token)
.header("alg", "none")
.claim("sub", "test-user")
.claim("preferred_username", "test")
.issuedAt(Instant.now())
.expiresAt(Instant.now().plusSeconds(300))
.build();
}
}

View File

@ -6,7 +6,7 @@ spring:
password: password:
jpa: jpa:
hibernate: hibernate:
ddl-auto: none # 👈 turn off Hibernate's validate/ddl in tests ddl-auto: none
properties: properties:
hibernate.hbm2ddl.auto: none hibernate.hbm2ddl.auto: none
flyway: flyway: