diff --git a/docker-compose.yml b/docker-compose.yml index b7e9f988d..9d55a2e13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,8 +63,8 @@ services: - search_providerName=${SEARCH_PROVIDER_NAME-dev.sunbirdrc.registry.service.NativeSearchService} - sunbird_sso_realm=${KEYCLOAK_REALM-sunbird-rc} - sunbird_sso_url=${sunbird_sso_url-http://keycloak:8080/auth} - - oauth2_resource_uri=${oauth2_resource_uri-http://keycloak:8080/auth/realms/sunbird-rc} - - oauth2_resource_roles_path=${oauth2_resource_roles_path-realm_access.roles} + - OAUTH2_RESOURCES_0_URI=${oauth2_resource_uri-http://keycloak:8080/auth/realms/sunbird-rc} + - OAUTH2_RESOURCES_0_PROPERTIES_ROLES_PATH=${oauth2_resource_roles_path-realm_access.roles} - identity_provider=${identity_provider-dev.sunbirdrc.auth.keycloak.KeycloakProviderImpl} - idgen_enabled=${IDGEN_ENABLED-false} - idgen_health_check_url=http://id-gen-service:8088/egov-idgen/health diff --git a/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/CustomJwtDecoderProviderConfigurationUtils.java b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/CustomJwtDecoderProviderConfigurationUtils.java new file mode 100644 index 000000000..ff9d2e65f --- /dev/null +++ b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/CustomJwtDecoderProviderConfigurationUtils.java @@ -0,0 +1,65 @@ +package dev.sunbirdrc.registry.authorization; + +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.Collections; +import java.util.Map; + +final class CustomJwtDecoderProviderConfigurationUtils { + private static final String OIDC_METADATA_PATH = "/.well-known/openid-configuration"; + private static final RestTemplate rest = new RestTemplate(); + private static final ParameterizedTypeReference> typeReference = + new ParameterizedTypeReference>() {}; + + static Map getConfigurationForOidcIssuerLocation(String oidcIssuerLocation) { + return getConfiguration(oidcIssuerLocation, oidc(URI.create(oidcIssuerLocation))); + } + + static String getIssuer(Map configuration) { + String metadataIssuer = "(unavailable)"; + if (configuration.containsKey("issuer")) { + metadataIssuer = configuration.get("issuer").toString(); + } + return metadataIssuer; + } + + private static Map getConfiguration(String issuer, URI... uris) { + String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " + + "\"" + issuer + "\""; + for (URI uri : uris) { + try { + RequestEntity request = RequestEntity.get(uri).build(); + ResponseEntity> response = rest.exchange(request, typeReference); + Map configuration = response.getBody(); + + if (configuration.get("jwks_uri") == null) { + throw new IllegalArgumentException("The public JWK set URI must not be null"); + } + + return configuration; + } catch (IllegalArgumentException e) { + throw e; + } catch (RuntimeException e) { + if (!(e instanceof HttpClientErrorException && + ((HttpClientErrorException) e).getStatusCode().is4xxClientError())) { + throw new IllegalArgumentException(errorMessage, e); + } + // else try another endpoint + } + } + throw new IllegalArgumentException(errorMessage); + } + + private static URI oidc(URI issuer) { + return UriComponentsBuilder.fromUri(issuer) + .replacePath(issuer.getPath() + OIDC_METADATA_PATH) + .build(Collections.emptyMap()); + } +} + diff --git a/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/CustomJwtDecoders.java b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/CustomJwtDecoders.java new file mode 100644 index 000000000..18d95875e --- /dev/null +++ b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/CustomJwtDecoders.java @@ -0,0 +1,29 @@ +package dev.sunbirdrc.registry.authorization; + +import java.util.Map; + +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.jwt.*; +import org.springframework.util.Assert; + +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; + +final class CustomJwtDecoders { + + public static TenantJwtDecoder fromOidcIssuerLocation(String oidcIssuerLocation) { + Assert.hasText(oidcIssuerLocation, "oidcIssuerLocation cannot be empty"); + Map configuration = CustomJwtDecoderProviderConfigurationUtils.getConfigurationForOidcIssuerLocation(oidcIssuerLocation); + return withProviderConfiguration(configuration); + } + + private static TenantJwtDecoder withProviderConfiguration(Map configuration) { + String metadataIssuer = CustomJwtDecoderProviderConfigurationUtils.getIssuer(configuration); + OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(metadataIssuer); + NimbusJwtDecoder jwtDecoder = withJwkSetUri(configuration.get("jwks_uri").toString()).build(); + jwtDecoder.setJwtValidator(jwtValidator); + return TenantJwtDecoder.from(jwtDecoder, metadataIssuer); + } + + private CustomJwtDecoders() {} +} + diff --git a/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/SecurityConfig.java b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/SecurityConfig.java index 0af37a2b3..f456072fb 100644 --- a/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/SecurityConfig.java +++ b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/SecurityConfig.java @@ -3,14 +3,12 @@ import dev.sunbirdrc.registry.authorization.pojos.OAuth2Configuration; import dev.sunbirdrc.registry.authorization.pojos.OAuth2Resources; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.oauth2.jwt.JwtDecoders; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; @@ -51,10 +49,11 @@ protected void configure(HttpSecurity http) throws Exception { } - private void addManager(Map authenticationManagers, OAuth2Resources issuer) { - JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(JwtDecoders.fromOidcIssuerLocation(issuer.getUri())); - authenticationProvider.setJwtAuthenticationConverter(new CustomJwtAuthenticationConverter(issuer.getProperties())); - authenticationManagers.put(issuer.getUri(), authenticationProvider::authenticate); + private void addManager(Map authenticationManagers, OAuth2Resources auth2Resources) { + TenantJwtDecoder tenantJwtDecoder = CustomJwtDecoders.fromOidcIssuerLocation(auth2Resources.getUri()); + JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(tenantJwtDecoder); + authenticationProvider.setJwtAuthenticationConverter(new CustomJwtAuthenticationConverter(auth2Resources.getProperties())); + authenticationManagers.put(tenantJwtDecoder.getIssuer(), authenticationProvider::authenticate); } } diff --git a/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/TenantJwtDecoder.java b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/TenantJwtDecoder.java new file mode 100644 index 000000000..193f9a3d3 --- /dev/null +++ b/java/middleware/registry-middleware/authorization/src/main/java/dev/sunbirdrc/registry/authorization/TenantJwtDecoder.java @@ -0,0 +1,25 @@ +package dev.sunbirdrc.registry.authorization; + +import lombok.Getter; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtException; + +public final class TenantJwtDecoder implements JwtDecoder { + JwtDecoder jwtDecoder; + @Getter + String issuer; + private TenantJwtDecoder(JwtDecoder jwtDecoder, String issuer) { + this.jwtDecoder = jwtDecoder; + this.issuer = issuer; + } + + @Override + public Jwt decode(String token) throws JwtException { + return this.jwtDecoder.decode(token); + } + + public static TenantJwtDecoder from(JwtDecoder jwtDecoder, String issuer) { + return new TenantJwtDecoder(jwtDecoder, issuer); + } +}