diff --git a/pom.xml b/pom.xml index 4a303074..f271e0ba 100644 --- a/pom.xml +++ b/pom.xml @@ -24,12 +24,15 @@ jacoco java UTF-8 + + src/main/java/fr/insee/genesis/configuration/**/*.java + true 1.17.2 1.2.1 2.18.2 - 1.0.3 + 1.0.4 @@ -44,6 +47,11 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + org.springframework.boot spring-boot-starter-test @@ -163,6 +171,11 @@ org.jacoco jacoco-maven-plugin ${jacoco.version} + + + src/main/java/fr/insee/genesis/configuration/**/* + + default-prepare-agent diff --git a/src/main/java/fr/insee/genesis/configuration/Config.java b/src/main/java/fr/insee/genesis/configuration/Config.java index 14650205..ed5812e4 100644 --- a/src/main/java/fr/insee/genesis/configuration/Config.java +++ b/src/main/java/fr/insee/genesis/configuration/Config.java @@ -20,6 +20,21 @@ public class Config { @Value("${fr.insee.genesis.sourcefolder.specifications}") private String specFolderSource; + @Value("${fr.insee.genesis.oidc.auth-server-url}") + private String authServerUrl; + + @Value("${fr.insee.genesis.oidc.realm}") + private String realm; + + @Value("${fr.insee.genesis.security.token.oidc-claim-role}") + private String oidcClaimRole; + + @Value("${fr.insee.genesis.security.token.oidc-claim-username}") + private String oidcClaimUsername; + + @Value("#{'${fr.insee.genesis.security.whitelist-matchers}'.split(',')}") + private String[] whiteList; + private final String logFolder; //Extract log folder from log filename property diff --git a/src/main/java/fr/insee/genesis/configuration/SpringDocConfiguration.java b/src/main/java/fr/insee/genesis/configuration/SpringDocConfiguration.java index b0b3de9b..d5e27cd5 100644 --- a/src/main/java/fr/insee/genesis/configuration/SpringDocConfiguration.java +++ b/src/main/java/fr/insee/genesis/configuration/SpringDocConfiguration.java @@ -1,28 +1,76 @@ package fr.insee.genesis.configuration; +import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.servers.Server; + +import io.swagger.v3.oas.models.security.OAuthFlow; +import io.swagger.v3.oas.models.security.OAuthFlows; +import io.swagger.v3.oas.models.security.Scopes; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SpringDocConfiguration { - @Value("${fr.insee.genesis.version}") - private String projectVersion; + @Value("${fr.insee.genesis.version}") + private String projectVersion; + public static final String BEARERSCHEME = "bearerAuth"; + public static final String OAUTH2SCHEME = "oauth2"; + + @Bean + @ConditionalOnProperty(name = "fr.insee.genesis.authentication", havingValue = "NONE") + public OpenAPI noAuthOpenAPI() { + return generateOpenAPI(); + } + + @Bean + @ConditionalOnProperty(name = "fr.insee.genesis.authentication", havingValue = "OIDC") + public OpenAPI oidcOpenAPI(Config config) { + String authUrl = config.getAuthServerUrl() + "/realms/" + config.getRealm() + "/protocol/openid-connect"; + return generateOpenAPI() + .addSecurityItem(new SecurityRequirement().addList(OAUTH2SCHEME)) + .addSecurityItem(new SecurityRequirement().addList(BEARERSCHEME)) + .components( + new Components() + .addSecuritySchemes(OAUTH2SCHEME, + new SecurityScheme() + .name(OAUTH2SCHEME) + .type(SecurityScheme.Type.OAUTH2) + .flows(getFlows(authUrl)) + ) + .addSecuritySchemes(BEARERSCHEME, + new SecurityScheme() + .name(BEARERSCHEME) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ) + ); + } - @Bean - public OpenAPI customOpenAPI() { - return new OpenAPI() - .addServersItem(new Server().url("/")) - .info(new Info() - .title("Genesis API") - .description("Rest Endpoints and services to communicate with Genesis database") - .version(projectVersion) - ); - } + private OpenAPI generateOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Genesis API") + .description("Rest Endpoints and services to communicate with Genesis database") + .version(projectVersion) + ); + } + private OAuthFlows getFlows(String authUrl) { + OAuthFlows flows = new OAuthFlows(); + OAuthFlow flow = new OAuthFlow(); + Scopes scopes = new Scopes(); + flow.setAuthorizationUrl(authUrl + "/auth"); + flow.setTokenUrl(authUrl + "/token"); + flow.setRefreshUrl(authUrl + "/token"); + flow.setScopes(scopes); + return flows.authorizationCode(flow); + } } diff --git a/src/main/java/fr/insee/genesis/configuration/auth/security/DefaultSecurityConfig.java b/src/main/java/fr/insee/genesis/configuration/auth/security/DefaultSecurityConfig.java index 25c7487b..af4b5648 100644 --- a/src/main/java/fr/insee/genesis/configuration/auth/security/DefaultSecurityConfig.java +++ b/src/main/java/fr/insee/genesis/configuration/auth/security/DefaultSecurityConfig.java @@ -1,6 +1,6 @@ package fr.insee.genesis.configuration.auth.security; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -10,7 +10,7 @@ @Configuration @EnableWebSecurity -@ConditionalOnMissingBean(OIDCSecurityConfig.class) +@ConditionalOnProperty(name = "fr.insee.genesis.authentication", havingValue = "NONE") public class DefaultSecurityConfig { @Bean diff --git a/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java b/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java index b0f625a0..ac524ce0 100644 --- a/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java +++ b/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java @@ -1,22 +1,48 @@ package fr.insee.genesis.configuration.auth.security; +import fr.insee.genesis.configuration.Config; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity +@Slf4j @ConditionalOnProperty(name = "fr.insee.genesis.authentication", havingValue = "OIDC") public class OIDCSecurityConfig { - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(auth -> auth.anyRequest().permitAll()); - return http.build(); + Config config; + @Autowired + public OIDCSecurityConfig(Config config) { + this.config = config; } + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + for (var pattern : config.getWhiteList()) { + http.authorizeHttpRequests(authorize -> + authorize + .requestMatchers(AntPathRequestMatcher.antMatcher(pattern)).permitAll() + ); + } + http + .authorizeHttpRequests(configurer -> configurer + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); + return http.build(); + } + } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index f29b9618..6588b136 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -5,4 +5,11 @@ fr.insee.genesis.persistence.database.mongodb.port=27017 fr.insee.genesis.persistence.database.mongodb.database=CollectedDataRepository fr.insee.genesis.persistence.database.mongodb.username=user -#fr.insee.genesis.persistence.database.mongodb.password in Vault \ No newline at end of file +#fr.insee.genesis.persistence.database.mongodb.password in Vault + +#-------------------------------------------------------------------------- +# Keycloak configuration +#-------------------------------------------------------------------------- +fr.insee.genesis.oidc.auth-server-url=*** +fr.insee.genesis.oidc.realm=*** +springdoc.swagger-ui.oauth.client-id=*** \ No newline at end of file diff --git a/src/main/resources/application-preprod.properties b/src/main/resources/application-preprod.properties index be37b15a..c0192fce 100644 --- a/src/main/resources/application-preprod.properties +++ b/src/main/resources/application-preprod.properties @@ -5,4 +5,11 @@ fr.insee.genesis.persistence.database.mongodb.port=27017 fr.insee.genesis.persistence.database.mongodb.database=CollectedDataRepository fr.insee.genesis.persistence.database.mongodb.username=user -#fr.insee.genesis.persistence.database.mongodb.password in Vault \ No newline at end of file +#fr.insee.genesis.persistence.database.mongodb.password in Vault + +#-------------------------------------------------------------------------- +# Keycloak configuration +#-------------------------------------------------------------------------- +fr.insee.genesis.oidc.auth-server-url=*** +fr.insee.genesis.oidc.realm=*** +springdoc.swagger-ui.oauth.client-id=*** \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 8193e963..a28c41aa 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -5,4 +5,11 @@ fr.insee.genesis.persistence.database.mongodb.port=27017 fr.insee.genesis.persistence.database.mongodb.database=CollectedDataRepository fr.insee.genesis.persistence.database.mongodb.username=user -#fr.insee.genesis.persistence.database.mongodb.password in Vault \ No newline at end of file +#fr.insee.genesis.persistence.database.mongodb.password in Vault + +#-------------------------------------------------------------------------- +# Keycloak configuration +#-------------------------------------------------------------------------- +fr.insee.genesis.oidc.auth-server-url=*** +fr.insee.genesis.oidc.realm=*** +springdoc.swagger-ui.oauth.client-id=*** \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e9e5c4dc..8d75fa68 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,7 +6,7 @@ spring.profiles.active=local #-------------------------------------------------------------------------- # Global configuration #-------------------------------------------------------------------------- -fr.insee.genesis.authentication = NONE +fr.insee.genesis.authentication = OIDC #-------------------------------------------------------------------------- # Configuration for springdoc / swagger @@ -14,6 +14,17 @@ fr.insee.genesis.authentication = NONE fr.insee.genesis.version=@project.version@ #To make swagger-ui display the actuator endpoints springdoc.show-actuator=true +springdoc.swagger-ui.oauth2RedirectUrl=${fr.insee.genesis.application.host.url}/swagger-ui/oauth2-redirect.html +# To deal with http/https issues in swagger +server.forward-headers-strategy=framework + +#-------------------------------------------------------------------------- +# Security +#-------------------------------------------------------------------------- +fr.insee.genesis.security.token.oidc-claim-role=realm_access.roles +fr.insee.genesis.security.token.oidc-claim-username=name +spring.security.oauth2.resourceserver.jwt.issuer-uri=${fr.insee.genesis.oidc.auth-server-url}/realms/${fr.insee.genesis.oidc.realm} +fr.insee.genesis.security.whitelist-matchers=/v3/api-docs/**,/swagger-ui/**,/swagger-ui.html,/actuator/**,/error,/,/health-check/** #-------------------------------------------------------------------------- # Actuator