Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AOT Optimizations #868

Merged
merged 58 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
bf90177
refactor: extract OpenIdProviderMetadataFetcher
sdelamo Dec 16, 2021
71b10c7
aot wip
sdelamo Dec 21, 2021
c243d57
Use `StaticOptimizations` instead of `@Replaces`
melix Dec 21, 2021
21e3d52
Upgrade to AOT 1.0.0-M5
melix Dec 21, 2021
b912d84
setters WIP
sdelamo Dec 21, 2021
f1380eb
fix test
sdelamo Dec 22, 2021
8b8888c
move nullability check to method
sdelamo Dec 22, 2021
1c82448
simplify aot build
sdelamo Dec 22, 2021
3890f6b
define AOT version in gradle.properties
sdelamo Dec 22, 2021
2eb4dde
build: bump up micronaut to 3.2.3
sdelamo Dec 22, 2021
2dfae4e
build: runtime selenium chrome driver for tests
sdelamo Dec 22, 2021
3eb9b32
wip JwkSetFetcher
sdelamo Dec 22, 2021
b732e1c
Fix incorrect service declaration
melix Dec 22, 2021
92f7c2c
Fix typo in static initializer
melix Dec 22, 2021
cb50471
draft JwksFetcherCodeGenerator
sdelamo Dec 22, 2021
e8e0dd7
fix test
sdelamo Dec 22, 2021
b2eb5bc
Fix how static optimizations are injected
melix Dec 22, 2021
4a7ea92
Introduce AOT test suite
melix Dec 22, 2021
8f8fca0
use JsonMapper instead ObjectMapper
sdelamo Jan 3, 2022
b939f7d
Merge branch 'master' into extract-fetcher
sdelamo Jan 3, 2022
2ba8259
Upgrade to Micronaut AOT 1.0.0-M6
melix Jan 21, 2022
49ffe06
bump up to 1.0.0-M7
sdelamo Jan 31, 2022
eac2dd6
Merge branch 'master' into extract-fetcher
melix Mar 29, 2022
3c1f1b1
Merge branch 'master' into extract-fetcher
melix Mar 29, 2022
ba0798b
Merge branch 'main' into extract-fetcher
sdelamo Jul 22, 2022
dda72da
extract utils
sdelamo Jul 22, 2022
e0cd245
add JwksFetcherCodeGenerator
sdelamo Jul 22, 2022
d359adc
set version to 3.7.0
sdelamo Jul 22, 2022
273a344
Use it as a parameter of the factory method
sdelamo Jul 22, 2022
475ff5f
fix generated code
sdelamo Jul 22, 2022
5b9dc97
Merge branch '3.9.x' into extract-fetcher
sdelamo Nov 14, 2022
fbbe1c0
change since
sdelamo Nov 14, 2022
c099dc2
add author
sdelamo Nov 14, 2022
c37307c
annotate wtih @Internal
sdelamo Nov 14, 2022
8203256
build: enable TYPESAFE_PROJECT_ACCSSORS
sdelamo Nov 14, 2022
9cfe1c7
remove settings for test project
sdelamo Nov 14, 2022
e385626
build: use kotlin DSL
sdelamo Nov 14, 2022
2c89906
wip
sdelamo Nov 14, 2022
ab6fd76
add test scritp [skip ci]
sdelamo Nov 14, 2022
3e852a0
Add AOT test
sdelamo Nov 15, 2022
897240e
checkstyle: fix checkstyle issues
sdelamo Nov 15, 2022
b8e391f
add test for jwksfetcher
sdelamo Nov 15, 2022
0c55252
add docs
sdelamo Nov 15, 2022
e488d5e
fix since
sdelamo Nov 15, 2022
fd9de39
extract method
sdelamo Nov 15, 2022
0732006
remove -a
sdelamo Nov 15, 2022
c22834c
more verbose test
sdelamo Nov 15, 2022
eec7c43
added as implementation
sdelamo Nov 15, 2022
fbbbbd4
sonar: Format specifiers should be used instead of string concatenation
sdelamo Nov 15, 2022
a9746a2
sonar extract constant
sdelamo Nov 15, 2022
b71d6bc
sonar: remove extra line
sdelamo Nov 15, 2022
96de149
sonar: remove unused logger
sdelamo Nov 15, 2022
be50d93
checkstyle: remove unused import
sdelamo Nov 15, 2022
cfc2048
sonar: remove of generic wildcard type
sdelamo Nov 15, 2022
8a0ba17
Update test-suite-aot.sh
sdelamo Nov 15, 2022
e420598
add warnings
sdelamo Nov 16, 2022
7bcfa42
add version
sdelamo Nov 16, 2022
7e25b80
checkstyle: remove unused imports
sdelamo Nov 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ projectGroup=io.micronaut.security
micronautTestVersion=3.0.2
groovyVersion=3.0.9
micronautDocsVersion=2.0.0
micronautVersion=3.1.0
micronautVersion=3.2.0
spockVersion=2.0-groovy-3.0

gebVersion=4.1
Expand Down
28 changes: 28 additions & 0 deletions security-aot/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id("io.micronaut.build.internal.aot-module")
}

micronautBuild {
aot {
version = '1.0.0-M5'
}
}

dependencies {
compileOnly platform("io.micronaut:micronaut-bom:$micronautVersion")

testCompileOnly("io.micronaut:micronaut-inject-groovy:$micronautVersion")
testImplementation(platform("io.micronaut:micronaut-bom:$micronautVersion"))
testImplementation("org.spockframework:spock-core:${spockVersion}") {
exclude module:'groovy-all'
}
runtimeOnly("ch.qos.logback:logback-classic:1.2.8")
testImplementation "io.micronaut.test:micronaut-test-spock:$micronautTestVersion"
testImplementation "io.micronaut:micronaut-management"
testImplementation "io.micronaut:micronaut-http-server-netty"
testImplementation "io.micronaut:micronaut-http-client"

compileOnly(project(":security-oauth2"))
testImplementation(project(":security-oauth2"))

}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//file:noinspection HardCodedStringLiteral
package io.micronaut.security.aot

import io.micronaut.aot.core.AOTCodeGenerator
import io.micronaut.aot.core.codegen.AbstractSourceGeneratorSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.context.ApplicationContextBuilder
import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.runtime.server.EmbeddedServer
import io.micronaut.security.annotation.Secured
import io.micronaut.security.rules.SecurityRule
import spock.lang.AutoCleanup
import spock.lang.Shared

class OpenIdProviderMetadataFetcherCodeGeneratorSpec extends AbstractSourceGeneratorSpec {

@AutoCleanup
@Shared
EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer,
['spec.name': 'EmbeddedServerOpenIdProviderMetadataFetcherCodeGeneratorSpec'],
Environment.TEST)

@Override
protected void customizeContext(ApplicationContextBuilder builder) {
builder = builder.properties([
'micronaut.security.oauth2.clients.cognito.client-id': 'XXX',
'micronaut.security.oauth2.clients.cognito.client-secret': 'YYY',
'micronaut.security.oauth2.clients.cognito.openid.issuer': "http://localhost:$embeddedServer.port",
])
builder.environments(Environment.TEST)
super.customizeContext(builder)
}

@Override
AOTCodeGenerator newGenerator() {
return new OpenIdProviderMetadataFetcherCodeGenerator()
}

void "verify OpenIdProviderMetadataFetcherCodeGenerator generates OpenIdProviderMetadataFetcher per openid client"() {
expect:
embeddedServer.applicationContext.containsBean(OpenIdConfigurationController)

when:
generate()

then:
assertThatGeneratedSources {
createsInitializer """private static void preloadOpenIdMetadata() {
java.util.Map<java.lang.String, java.util.function.Supplier<io.micronaut.security.oauth2.client.DefaultOpenIdProviderMetadata>> configs = new java.util.HashMap<java.lang.String, java.util.function.Supplier<io.micronaut.security.oauth2.client.DefaultOpenIdProviderMetadata>>();
context.put("cognito", AotOpenIdProviderMetadataFetcherCognito::create);
io.micronaut.core.optim.StaticOptimizations.set(io.micronaut.security.oauth2.client.DefaultOpenIdProviderMetadataFetcher.Optimizations, configs);
}"""
hasClass("AotOpenIdProviderMetadataFetcherCognito") {
withSources """package io.micronaut.test;

import io.micronaut.security.oauth2.client.DefaultOpenIdProviderMetadata;

public class AotOpenIdProviderMetadataFetcherCognito {
public static DefaultOpenIdProviderMetadata create() {
DefaultOpenIdProviderMetadata metadata = new DefaultOpenIdProviderMetadata();
metadata.setUserinfoEndpoint("https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/userInfo");
metadata.setAuthorizationEndpoint("https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/authorize");
metadata.setIdTokenSigningAlgValuesSupported("https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/authorize");
metadata.setIssuer("https://cognito-idp.us-east-1.amazonaws.com/us-east-1_4OqDoWVrZ");
metadata.setJwksUri(""https://cognito-idp.us-east-1.amazonaws.com/us-east-1_4OqDoWVrZ/.well-known/jwks.json");
List<String> responseTypesSupported = new ArrayList<>();
responseTypesSupported.add("code");
responseTypesSupported.add("token");
metadata.setResponseTypesSupported(responseTypesSupported);
List<String> scopesSupported = new ArrayList<>();
scopesSupported.add("openid");
scopesSupported.add("email");
scopesSupported.add("phone");
scopesSupported.add("profile");
metadata.setScopesSupported(scopesSupported);
List<String> subjectTypesSupported = new ArrayList<>();
subjectTypesSupported.add("public");
metadata.setSubjectTypesSupported(subjectTypesSupported);
metadata.setTokenEndpoint("https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/token");
List<String> tokenEndpointAuthMethodsSupported = new ArrayList<>();
tokenEndpointAuthMethodsSupported.add("client_secret_basic");
tokenEndpointAuthMethodsSupported.add("client_secret_post");
metadata.setTokenEndpointAuthMethodsSupported(tokenEndpointAuthMethodsSupported);
metadata.setUserinfoEndpoint("https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/userInfo");
return metadata;
}
}"""
}
}
}

@Requires(property = 'spec.name', value = 'EmbeddedServerOpenIdProviderMetadataFetcherCodeGeneratorSpec')
@Controller
static class OpenIdConfigurationController {

@Get("/.well-known/openid-configuration")
@Secured(SecurityRule.IS_ANONYMOUS)
String index() {
'{"authorization_endpoint":"https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/authorize","id_token_signing_alg_values_supported":["RS256"],"issuer":"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_4OqDoWVrZ","jwks_uri":"https://cognito-idp.us-east-1.amazonaws.com/us-east-1_4OqDoWVrZ/.well-known/jwks.json","response_types_supported":["code","token"],"scopes_supported":["openid","email","phone","profile"],"subject_types_supported":["public"],"token_endpoint":"https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/token","token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"userinfo_endpoint":"https://auth-groovycalamari.auth.us-east-1.amazoncognito.com/oauth2/userInfo"}'
}

}
}
15 changes: 15 additions & 0 deletions security-aot/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
</root>
<!--<logger name="io.micronaut.http" level="trace"/>-->
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2017-2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.oauth2.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
sdelamo marked this conversation as resolved.
Show resolved Hide resolved
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.optim.StaticOptimizations;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.security.oauth2.configuration.OpenIdClientConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;


/**
* Default implementation of {@link OpenIdProviderMetadataFetcher}.
*
* @author Sergio del Amo
* @since 3.3.0
*/
public class DefaultOpenIdProviderMetadataFetcher implements OpenIdProviderMetadataFetcher {
private static final Logger LOG = LoggerFactory.getLogger(DefaultOpenIdProviderMetadataFetcher.class);
private final static Optimizations OPTIMIZATIONS = StaticOptimizations.get(Optimizations.class).orElse(new Optimizations(Collections.emptyMap()));

private final HttpClient client;
private final ObjectMapper objectMapper;
private final OpenIdClientConfiguration openIdClientConfiguration;

/**
* AOT Optimizations.
*/
public static class Optimizations {
private final Map<String, Supplier<DefaultOpenIdProviderMetadata>> suppliers;

/**
*
* @param suppliers Map with key being the OpenID Name qualifier and
*/
public Optimizations(Map<String, Supplier<DefaultOpenIdProviderMetadata>> suppliers) {
this.suppliers = suppliers;
}

public Optional<Supplier<DefaultOpenIdProviderMetadata>> findMetadata(String name) {
return Optional.ofNullable(suppliers.get(name));
}
}

public DefaultOpenIdProviderMetadataFetcher(OpenIdClientConfiguration openIdClientConfiguration,
ObjectMapper objectMapper,
@Client HttpClient client) {
this.openIdClientConfiguration = openIdClientConfiguration;
this.objectMapper = objectMapper;
this.client = client;
}

@Override
@NonNull
public DefaultOpenIdProviderMetadata fetch() {
return OPTIMIZATIONS.findMetadata(openIdClientConfiguration.getName())
.map(Supplier::get)
.orElseGet(() -> openIdClientConfiguration.getIssuer()
.map(issuer -> {
try {
URL configurationUrl = new URL(issuer, StringUtils.prependUri(issuer.getPath(), openIdClientConfiguration.getConfigurationPath()));
if (LOG.isDebugEnabled()) {
LOG.debug("Sending request for OpenID configuration for provider [{}] to URL [{}]", openIdClientConfiguration.getName(), configurationUrl);
}
//TODO this returns ReadTimeoutException - return issuerClient.toBlocking().retrieve(configurationUrl.toString(), DefaultOpenIdProviderMetadata.class);
String json = client.toBlocking().retrieve(configurationUrl.toString(), String.class);
return objectMapper.readValue(json, DefaultOpenIdProviderMetadata.class);

} catch (MalformedURLException e) {
throw new BeanInstantiationException("Failure parsing issuer URL " + issuer.toString(), e);
} catch (HttpClientResponseException e) {
throw new BeanInstantiationException("Failed to retrieve OpenID configuration for " + openIdClientConfiguration.getName(), e);

} catch (JsonProcessingException e) {
throw new BeanInstantiationException("JSON Processing Exception parsing issuer URL returned JSON " + issuer.toString(), e);
}
}).orElse(new DefaultOpenIdProviderMetadata())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,15 @@
*/
package io.micronaut.security.oauth2.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.micronaut.context.BeanContext;
import io.micronaut.context.BeanProvider;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.exceptions.BeanInstantiationException;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.SupplierUtil;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.security.oauth2.client.condition.OpenIdClientCondition;
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration;
import io.micronaut.security.oauth2.configuration.OpenIdClientConfiguration;
Expand All @@ -44,9 +38,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.micronaut.core.annotation.Nullable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.function.Supplier;

Expand All @@ -66,48 +57,26 @@ class OpenIdClientFactory {

private final BeanContext beanContext;

private final ObjectMapper objectMapper;

/**
* @param beanContext The bean context
* @param objectMapper Object Mapper
*/
OpenIdClientFactory(BeanContext beanContext, ObjectMapper objectMapper) {
OpenIdClientFactory(BeanContext beanContext) {
this.beanContext = beanContext;
this.objectMapper = objectMapper;
}

/**
* Retrieves OpenID configuration from the provided issuer.
*
* @param oauthClientConfiguration The client configuration
* @param openIdClientConfiguration The openid client configuration
* @param issuerClient The client to request the metadata
* @param openIdProviderMetadataFetcher OpenID Provider metadata Fetcher
* @return The OpenID configuration
*/
@EachBean(OpenIdClientConfiguration.class)
DefaultOpenIdProviderMetadata openIdConfiguration(@Parameter OauthClientConfiguration oauthClientConfiguration,
@Parameter OpenIdClientConfiguration openIdClientConfiguration,
@Client HttpClient issuerClient) {
DefaultOpenIdProviderMetadata providerMetadata = openIdClientConfiguration.getIssuer()
.map(issuer -> {
try {
URL configurationUrl = new URL(issuer, StringUtils.prependUri(issuer.getPath(), openIdClientConfiguration.getConfigurationPath()));
if (LOG.isDebugEnabled()) {
LOG.debug("Sending request for OpenID configuration for provider [{}] to URL [{}]", openIdClientConfiguration.getName(), configurationUrl);
}
//TODO this returns ReadTimeoutException - return issuerClient.toBlocking().retrieve(configurationUrl.toString(), DefaultOpenIdProviderMetadata.class);
String json = issuerClient.toBlocking().retrieve(configurationUrl.toString(), String.class);
return objectMapper.readValue(json, DefaultOpenIdProviderMetadata.class);
} catch (HttpClientResponseException e) {
throw new BeanInstantiationException("Failed to retrieve OpenID configuration for " + openIdClientConfiguration.getName(), e);
} catch (MalformedURLException e) {
throw new BeanInstantiationException("Failure parsing issuer URL " + issuer.toString(), e);
} catch (JsonProcessingException e) {
throw new BeanInstantiationException("JSON Processing Exception parsing issuer URL returned JSON " + issuer.toString(), e);
}
}).orElse(new DefaultOpenIdProviderMetadata());

@Parameter OpenIdProviderMetadataFetcher openIdProviderMetadataFetcher) {
DefaultOpenIdProviderMetadata providerMetadata = openIdProviderMetadataFetcher.fetch();
overrideFromConfig(providerMetadata, openIdClientConfiguration, oauthClientConfiguration);
return providerMetadata;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2017-2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.security.oauth2.client;

import io.micronaut.core.annotation.NonNull;

/**
* Fetches OpenIdProviderMetadata for a {@link io.micronaut.security.oauth2.configuration.OpenIdClientConfiguration}.
* @author Sergio del Amo
* @since 3.3.0
*/
@FunctionalInterface
public interface OpenIdProviderMetadataFetcher {
/**
*
* @return OpenID Provider Metadata
*/
@NonNull
DefaultOpenIdProviderMetadata fetch();
}
Loading