From 0ea19598369992216e9e8abe19f880f7e23e74fe Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 7 Dec 2023 15:38:42 +0100 Subject: [PATCH 01/37] add BlockingAuthenticationProvider --- .../BlockingAuthenticationProvider.java | 29 ++++++++++++++ ...BlockingAuthenticationProviderAdapter.java | 40 +++++++++++++++++++ ...BlockingAuthenticationProviderFactory.java | 25 ++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java create mode 100644 security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java create mode 100644 security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java new file mode 100644 index 0000000000..e0c3a8288f --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java @@ -0,0 +1,29 @@ +package io.micronaut.security.authentication; + +import io.micronaut.core.annotation.Blocking; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; + +/** + * Defines an authentication provider. + * @since 4.5.0 + * @param Request + */ +@FunctionalInterface +public interface BlockingAuthenticationProvider { + /** + * Authenticates a user with the given request. If a successful authentication is + * returned, the object must be an instance of {@link Authentication}. + * + * Publishers MUST emit cold observables! This method will be called for + * all authenticators for each authentication request and it is assumed no work + * will be done until the publisher is subscribed to. + * + * @param httpRequest The http request + * @param authenticationRequest The credentials to authenticate + * @return A publisher that emits 0 or 1 responses + */ + @NonNull + @Blocking + AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest); +} diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java new file mode 100644 index 0000000000..921c218ee4 --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-2023 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.authentication; + +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; + +/** + * Adapter pattern from {@link BlockingAuthenticationProvider} to {@link AuthenticationProvider}. + * @since 4.5.0 + */ +public class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { + private final BlockingAuthenticationProvider blockingAuthenticationProvider; + private final Scheduler scheduler; + + public BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider blockingAuthenticationProvider, + Scheduler scheduler) { + this.blockingAuthenticationProvider = blockingAuthenticationProvider; + this.scheduler = scheduler; + } + @Override + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) + .subscribeOn(scheduler); + } +} diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java new file mode 100644 index 0000000000..60164f8984 --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java @@ -0,0 +1,25 @@ +package io.micronaut.security.authentication; + +import io.micronaut.context.annotation.EachBean; +import io.micronaut.context.annotation.Factory; +import io.micronaut.scheduling.TaskExecutors; +import jakarta.inject.Named; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.concurrent.ExecutorService; + +@Factory +class BlockingAuthenticationProviderFactory { + + private final Scheduler scheduler; + + BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { + this.scheduler = Schedulers.fromExecutorService(executorService); + } + + @EachBean(BlockingAuthenticationProvider.class) + AuthenticationProvider createAuthenticationProvider(BlockingAuthenticationProvider blockingAuthenticationProvider) { + return new BlockingAuthenticationProviderAdapter<>(blockingAuthenticationProvider, scheduler); + } +} From 6420364a22041285aa9e5f57036e75be9d073ce5 Mon Sep 17 00:00:00 2001 From: Jeremy Grelle Date: Mon, 11 Dec 2023 18:59:08 -0500 Subject: [PATCH 02/37] Implementation, tests, and docs. --- .../BlockingAuthenticationProvider.java | 24 ++++- ...BlockingAuthenticationProviderAdapter.java | 40 -------- ...BlockingAuthenticationProviderFactory.java | 56 ++++++++++- ...ingAuthenticationProviderUserPassword.java | 23 +++++ .../basicauth/BlockingBasicAuthSpec.groovy | 75 +++++++++++++++ .../BlockingAuthenticationProviderSpec.groovy | 92 +++++++++++++++++++ .../docs/guide/authenticationProviders.adoc | 8 ++ 7 files changed, 271 insertions(+), 47 deletions(-) delete mode 100644 security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java create mode 100644 security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java create mode 100644 security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy create mode 100644 security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java index e0c3a8288f..845f149d16 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java @@ -1,3 +1,18 @@ +/* + * Copyright 2017-2023 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.authentication; import io.micronaut.core.annotation.Blocking; @@ -9,19 +24,18 @@ * @since 4.5.0 * @param Request */ -@FunctionalInterface public interface BlockingAuthenticationProvider { + /** * Authenticates a user with the given request. If a successful authentication is * returned, the object must be an instance of {@link Authentication}. * - * Publishers MUST emit cold observables! This method will be called for - * all authenticators for each authentication request and it is assumed no work - * will be done until the publisher is subscribed to. + * Implementations of this blocking variation of {@link AuthenticationProvider} will be safely executed on a + * dedicated thread in order to not block the main reactive chain of execution. * * @param httpRequest The http request * @param authenticationRequest The credentials to authenticate - * @return A publisher that emits 0 or 1 responses + * @return An {@link AuthenticationResponse} indicating either success or failure. */ @NonNull @Blocking diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java deleted file mode 100644 index 921c218ee4..0000000000 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2017-2023 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.authentication; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Scheduler; - -/** - * Adapter pattern from {@link BlockingAuthenticationProvider} to {@link AuthenticationProvider}. - * @since 4.5.0 - */ -public class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { - private final BlockingAuthenticationProvider blockingAuthenticationProvider; - private final Scheduler scheduler; - - public BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider blockingAuthenticationProvider, - Scheduler scheduler) { - this.blockingAuthenticationProvider = blockingAuthenticationProvider; - this.scheduler = scheduler; - } - @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) - .subscribeOn(scheduler); - } -} diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java index 60164f8984..0bb0dd9123 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java @@ -1,25 +1,77 @@ +/* + * Copyright 2017-2023 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.authentication; import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; import io.micronaut.scheduling.TaskExecutors; import jakarta.inject.Named; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import java.util.concurrent.ExecutorService; +/** + * A factory for adapting {@link BlockingAuthenticationProvider} beans to expose them as {@link AuthenticationProvider}. + * + * @since 4.5.0 + */ @Factory -class BlockingAuthenticationProviderFactory { +public class BlockingAuthenticationProviderFactory { private final Scheduler scheduler; - BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { + public BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { this.scheduler = Schedulers.fromExecutorService(executorService); } + /** + * Creates an adapted {@link AuthenticationProvider} for each provided instance of {@link BlockingAuthenticationProvider}. + * + *

+ * NOTE - If there are multiple instances of {@link BlockingAuthenticationProvider} in the application context, then they + * must be annotated with a {@link jakarta.inject.Qualifier} such as {@link Named}. + *

+ * + * @param blockingAuthenticationProvider An instance of {@link BlockingAuthenticationProvider} to be adapted + * @return An {@link AuthenticationProvider} adapted from the blocking provider + * @param The request type + */ @EachBean(BlockingAuthenticationProvider.class) AuthenticationProvider createAuthenticationProvider(BlockingAuthenticationProvider blockingAuthenticationProvider) { return new BlockingAuthenticationProviderAdapter<>(blockingAuthenticationProvider, scheduler); } + + private static class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { + + private final BlockingAuthenticationProvider blockingAuthenticationProvider; + + private final Scheduler scheduler; + + private BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider blockingAuthenticationProvider, Scheduler scheduler) { + this.blockingAuthenticationProvider = blockingAuthenticationProvider; + this.scheduler = scheduler; + } + + @Override + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) + .subscribeOn(scheduler); + } + } } diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java new file mode 100644 index 0000000000..325f74f5b9 --- /dev/null +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java @@ -0,0 +1,23 @@ +package io.micronaut.docs.security.token.basicauth; + +//tag::clazz[] +import io.micronaut.context.annotation.Requires; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import jakarta.inject.Singleton; +//end::clazz[] +@Requires(property = "spec.name", value = "BlockingBasicAuthSpec") +//tag::clazz[] +@Singleton +public class BlockingAuthenticationProviderUserPassword implements BlockingAuthenticationProvider { + @Override + public AuthenticationResponse authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { + return AuthenticationResponse.success("user"); + } else { + throw AuthenticationResponse.exception(); + } + } +} +//end::clazz[] diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy new file mode 100644 index 0000000000..b9a21fc559 --- /dev/null +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy @@ -0,0 +1,75 @@ +package io.micronaut.docs.security.token.basicauth + +import io.micronaut.context.ApplicationContext +import io.micronaut.context.annotation.Replaces +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.http.HttpRequest +import io.micronaut.http.client.HttpClient +import io.micronaut.inject.ExecutableMethod +import io.micronaut.management.endpoint.EndpointSensitivityProcessor +import io.micronaut.runtime.server.EmbeddedServer +import io.micronaut.security.authentication.Authentication +import io.micronaut.security.rules.SecurityRuleResult +import io.micronaut.security.rules.SensitiveEndpointRule +import jakarta.inject.Singleton +import org.reactivestreams.Publisher +import reactor.core.publisher.Mono +import spock.lang.AutoCleanup +import spock.lang.Shared +import spock.lang.Specification + +class BlockingBasicAuthSpec extends Specification { + + @Shared + Map config = [ + 'spec.name' : 'BlockingBasicAuthSpec', + 'endpoints.beans.enabled' : true, + 'endpoints.beans.sensitive' : true, + ] + + @Shared + @AutoCleanup + EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, config as Map) + + @Shared + @AutoCleanup + HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) + + void "test /beans is secured but accesible if you supply valid credentials with Basic Auth"() { + when: + String token = 'dXNlcjpwYXNzd29yZA==' // user:passsword Base64 + client.toBlocking().exchange(HttpRequest.GET("/beans") + .header("Authorization", "Basic ${token}".toString()), String) + + then: + noExceptionThrown() + } + + def "basicAuth() sets Authorization Header with Basic base64(username:password)"() { + when: + // tag::basicAuth[] + HttpRequest request = HttpRequest.GET("/home").basicAuth('sherlock', 'password') + // end::basicAuth[] + + then: + request.headers.get('Authorization') + request.headers.get('Authorization') == "Basic ${'sherlock:password'.bytes.encodeBase64().toString()}" + } + + @Requires(property = 'spec.name', value = 'BlockingBasicAuthSpec') + @Replaces(SensitiveEndpointRule.class) + @Singleton + static class SensitiveEndpointRuleReplacement extends SensitiveEndpointRule { + SensitiveEndpointRuleReplacement(EndpointSensitivityProcessor endpointSensitivityProcessor) { + super(endpointSensitivityProcessor) + } + + @Override + protected Publisher checkSensitiveAuthenticated(@NonNull HttpRequest request, + @NonNull Authentication authentication, + @NonNull ExecutableMethod method) { + Mono.just(SecurityRuleResult.ALLOWED) + } + } +} diff --git a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy new file mode 100644 index 0000000000..255d1fae5d --- /dev/null +++ b/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy @@ -0,0 +1,92 @@ +package io.micronaut.security.authentication + +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.Nullable +import io.micronaut.http.HttpMethod +import io.micronaut.http.HttpRequest +import io.micronaut.scheduling.LoomSupport +import io.micronaut.scheduling.TaskExecutors +import io.micronaut.security.testutils.ApplicationContextSpecification +import jakarta.inject.Named +import jakarta.inject.Singleton +import reactor.core.publisher.Mono + +class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification { + + static final String EXECUTOR_NAME_MATCH = "%s-executor".formatted(LoomSupport.supported ? TaskExecutors.VIRTUAL : TaskExecutors.IO) + + SimpleBlockingAuthenticationProvider provider + + def setup() { + provider = getBean(SimpleBlockingAuthenticationProvider.class) + provider.executedThreadName = "" + } + + def "multiple BlockingAuthenticationProvider implementations are registered"() { + given: + BasicAuthAuthenticationFetcher authFetcher = getBean(BasicAuthAuthenticationFetcher.class) + + expect: + authFetcher + getApplicationContext().getBeanRegistrations(BlockingAuthenticationProvider.class).size() == 2 + } + + def "a blocking authentication provider can authenticate successfully"() { + given: + BasicAuthAuthenticationFetcher authFetcher = getBean(BasicAuthAuthenticationFetcher.class) + + when: + Authentication authentication = Mono.from(authFetcher.fetchAuthentication(HttpRequest.create(HttpMethod.POST, "/auth").basicAuth('lebowski', 'thedudeabides'))).block() + + then: + authentication != null + authentication.name == 'lebowski' + provider.executedThreadName.startsWith(EXECUTOR_NAME_MATCH) + } + + def "a blocking authentication provider can fail to authenticate"() { + given: + BasicAuthAuthenticationFetcher authFetcher = getBean(BasicAuthAuthenticationFetcher.class) + + when: + Authentication authentication = Mono.from(authFetcher.fetchAuthentication(HttpRequest.create(HttpMethod.POST, "/auth").basicAuth('smoky', 'pacifist'))).block() + + then: + authentication == null + provider.executedThreadName.startsWith(EXECUTOR_NAME_MATCH) + } + + @Override + String getSpecName() { + return "BlockingAuthenticationProviderSpec" + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") + @Singleton + @Named("SimpleBlockingAuthenticationProvider") + static class SimpleBlockingAuthenticationProvider implements BlockingAuthenticationProvider { + + private String executedThreadName + + @Override + AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { + executedThreadName = Thread.currentThread().getName() + if (authenticationRequest.getIdentity().toString() == 'lebowski' && authenticationRequest.getSecret().toString() == 'thedudeabides') { + return AuthenticationResponse.success('lebowski') + } else { + return AuthenticationResponse.failure("Over the line.") + } + } + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") + @Singleton + @Named("NoOpBlockingAuthenticationProvider") + static class NoOpBlockingAuthenticationProvider implements BlockingAuthenticationProvider { + + @Override + AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { + throw AuthenticationResponse.exception() + } + } +} diff --git a/src/main/docs/guide/authenticationProviders.adoc b/src/main/docs/guide/authenticationProviders.adoc index 7832e892c1..d960a8d525 100644 --- a/src/main/docs/guide/authenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders.adoc @@ -7,6 +7,14 @@ The following code snippet illustrates a naive implementation: include::{testssecurity}/security/token/basicauth/AuthenticationProviderUserPassword.java[tag=clazz,indent=0] ---- +The main api:security.authentication.AuthenticationProvider[] interface is a non-blocking reactive API. If you need to implement custom authentication and prefer to use a blocking imperative style, you can instead implement the api:security.authentication.BlockingAuthenticationProvider[] interface, and the framework will adapt the blocking implementation to the reactive API. Such an adapted implementation will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor in order to avoid blocking the main reactive flow. The above example could be re-implemented as: + +[source, java] +---- +include::{testssecurity}/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java[tag=clazz,indent=0] +---- +NOTE: If you need to provide multiple implementations of `BlockingAuthenticationProvider`, then each implementation must have a `@Qualifier` such as `@Named`. + The built-in <> uses every available authentication provider. The first provider that returns a successful authentication response will have its value used as the basis for the JWT token or session state. Basic authentication which is implemented as an api:security.filters.AuthenticationFetcher[] will also trigger the available api:security.authentication.AuthenticationProvider[]s. From 70a3a9055293fedac128a144378d90786287bbc2 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 10:36:37 +0100 Subject: [PATCH 03/37] BlockingAuthenticationProvider implements Named --- .../BlockingAuthenticationProvider.java | 3 ++- ...ockingAuthenticationProviderUserPassword.java | 9 +++++++++ .../BlockingAuthenticationProviderSpec.groovy | 16 ++++++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java index 845f149d16..5cc6fa00a1 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java @@ -18,13 +18,14 @@ import io.micronaut.core.annotation.Blocking; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.naming.Named; /** * Defines an authentication provider. * @since 4.5.0 * @param Request */ -public interface BlockingAuthenticationProvider { +public interface BlockingAuthenticationProvider extends Named { /** * Authenticates a user with the given request. If a successful authentication is diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java index 325f74f5b9..5504a1f1d6 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java @@ -2,15 +2,19 @@ //tag::clazz[] import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import jakarta.inject.Named; import jakarta.inject.Singleton; //end::clazz[] @Requires(property = "spec.name", value = "BlockingBasicAuthSpec") //tag::clazz[] +@Named(BlockingAuthenticationProviderUserPassword.NAME) @Singleton public class BlockingAuthenticationProviderUserPassword implements BlockingAuthenticationProvider { + public static final String NAME = "foo"; @Override public AuthenticationResponse authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { @@ -19,5 +23,10 @@ public AuthenticationResponse authenticate(T httpRequest, AuthenticationRequest< throw AuthenticationResponse.exception(); } } + + @Override + public @NonNull String getName() { + return BlockingAuthenticationProviderUserPassword.NAME; + } } //end::clazz[] diff --git a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy index 255d1fae5d..7b5b4a5cc8 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy @@ -63,8 +63,9 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") @Singleton - @Named("SimpleBlockingAuthenticationProvider") + @Named(SimpleBlockingAuthenticationProvider.NAME) static class SimpleBlockingAuthenticationProvider implements BlockingAuthenticationProvider { + static final String NAME = "SimpleBlockingAuthenticationProvider" private String executedThreadName @@ -77,16 +78,27 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification return AuthenticationResponse.failure("Over the line.") } } + + @Override + String getName() { + SimpleBlockingAuthenticationProvider.NAME + } } @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") @Singleton - @Named("NoOpBlockingAuthenticationProvider") + @Named(NoOpBlockingAuthenticationProvider.NAME) static class NoOpBlockingAuthenticationProvider implements BlockingAuthenticationProvider { + static final String NAME = "NoOpBlockingAuthenticationProvider" @Override AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { throw AuthenticationResponse.exception() } + + @Override + String getName() { + NoOpBlockingAuthenticationProvider.NAME + } } } From 792dc0804f5177755056142e37852ecd24cf8c14 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 11:16:56 +0100 Subject: [PATCH 04/37] split docs --- .../authenticationProvidersUseCases.adoc | 3 +++ .../builtInAuthenticationProviders.adoc | 1 + src/main/docs/guide/toc.yml | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/main/docs/guide/authenticationProviders/authenticationProvidersUseCases.adoc create mode 100644 src/main/docs/guide/authenticationProviders/builtInAuthenticationProviders.adoc diff --git a/src/main/docs/guide/authenticationProviders/authenticationProvidersUseCases.adoc b/src/main/docs/guide/authenticationProviders/authenticationProvidersUseCases.adoc new file mode 100644 index 0000000000..b95982e9b9 --- /dev/null +++ b/src/main/docs/guide/authenticationProviders/authenticationProvidersUseCases.adoc @@ -0,0 +1,3 @@ +The built-in <> uses every available authentication provider. The first provider that returns a successful authentication response will have its value used as the basis for the JWT token or session state. + +Basic authentication which is implemented as an api:security.filters.AuthenticationFetcher[] will also trigger the available api:security.authentication.AuthenticationProvider[]s. diff --git a/src/main/docs/guide/authenticationProviders/builtInAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/builtInAuthenticationProviders.adoc new file mode 100644 index 0000000000..c0b01937c3 --- /dev/null +++ b/src/main/docs/guide/authenticationProviders/builtInAuthenticationProviders.adoc @@ -0,0 +1 @@ +Micronaut comes with authentication providers for LDAP (api:security.ldap.LdapAuthenticationProvider[]) and the OAuth 2.0 password grant authentication flow (api:security.oauth2.endpoint.token.request.password.OauthPasswordAuthenticationProvider[] and api:security.oauth2.endpoint.token.request.password.OpenIdPasswordAuthenticationProvider[]). For any custom authentication, an authentication provider must be created. diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index 3a937ad6f0..0b7544a4c0 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -14,7 +14,10 @@ securityConfiguration: logoutHandler: Logout Handler noRedirection: Handlers without redirection redirection: Redirection Configuration -authenticationProviders: Authentication Providers +authenticationProviders: + title: Authentication Providers + authenticationProvidersUseCases: Authentication Providers Use Cases + builtInAuthenticationProviders: Built-In Authentication Providers securityRule: title: Security Rules ipPattern: IP Pattern Rule From d0b0647f87a3b20c622b7b01629f9117fcbfced7 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 11:17:22 +0100 Subject: [PATCH 05/37] add multilanguage snippet --- ...BlockingAuthenticationProviderFactory.java | 27 ++++---- ...ingAuthenticationProviderUserPassword.java | 19 +++--- .../docs/guide/authenticationProviders.adoc | 14 ----- .../blockingAuthenticationProviders.adoc | 5 ++ src/main/docs/guide/toc.yml | 1 + .../BlockingAuthenticationProviderTest.groovy | 61 +++++++++++++++++++ .../CustomAuthenticationProvider.groovy | 31 ++++++++++ .../BlockingAuthenticationProviderTest.kt | 46 ++++++++++++++ .../CustomAuthenticationProvider.kt | 31 ++++++++++ .../BlockingAuthenticationProviderTest.java | 52 ++++++++++++++++ .../CustomAuthenticationProvider.java | 33 ++++++++++ 11 files changed, 282 insertions(+), 38 deletions(-) create mode 100644 src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc create mode 100644 test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy create mode 100644 test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy create mode 100644 test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt create mode 100644 test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt create mode 100644 test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java create mode 100644 test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java index 0bb0dd9123..b12b952253 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java @@ -17,6 +17,7 @@ import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; +import io.micronaut.core.annotation.Internal; import io.micronaut.scheduling.TaskExecutors; import jakarta.inject.Named; import org.reactivestreams.Publisher; @@ -32,11 +33,12 @@ * @since 4.5.0 */ @Factory -public class BlockingAuthenticationProviderFactory { +@Internal +class BlockingAuthenticationProviderFactory { private final Scheduler scheduler; - public BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { + BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { this.scheduler = Schedulers.fromExecutorService(executorService); } @@ -57,21 +59,14 @@ AuthenticationProvider createAuthenticationProvider(BlockingAuthenticatio return new BlockingAuthenticationProviderAdapter<>(blockingAuthenticationProvider, scheduler); } - private static class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { - - private final BlockingAuthenticationProvider blockingAuthenticationProvider; - - private final Scheduler scheduler; - - private BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider blockingAuthenticationProvider, Scheduler scheduler) { - this.blockingAuthenticationProvider = blockingAuthenticationProvider; - this.scheduler = scheduler; - } + private record BlockingAuthenticationProviderAdapter( + BlockingAuthenticationProvider blockingAuthenticationProvider, + Scheduler scheduler) implements AuthenticationProvider { @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) - .subscribeOn(scheduler); + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) + .subscribeOn(scheduler); + } } - } } diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java index 5504a1f1d6..d87c6feb2d 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java @@ -1,14 +1,16 @@ package io.micronaut.docs.security.token.basicauth; -//tag::clazz[] +//tag::imports[] import io.micronaut.context.annotation.Requires; import io.micronaut.core.annotation.NonNull; +import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import io.micronaut.security.authentication.BlockingAuthenticationProvider; import jakarta.inject.Named; import jakarta.inject.Singleton; -//end::clazz[] + +//end::imports[] @Requires(property = "spec.name", value = "BlockingBasicAuthSpec") //tag::clazz[] @Named(BlockingAuthenticationProviderUserPassword.NAME) @@ -16,12 +18,13 @@ public class BlockingAuthenticationProviderUserPassword implements BlockingAuthenticationProvider { public static final String NAME = "foo"; @Override - public AuthenticationResponse authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { - return AuthenticationResponse.success("user"); - } else { - throw AuthenticationResponse.exception(); - } + public AuthenticationResponse authenticate(T httpRequest, + AuthenticationRequest authenticationRequest) { + return ( + authenticationRequest.getIdentity().equals("user") && + authenticationRequest.getSecret().equals("password") + ) ? AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); } @Override diff --git a/src/main/docs/guide/authenticationProviders.adoc b/src/main/docs/guide/authenticationProviders.adoc index d960a8d525..d3d3f94387 100644 --- a/src/main/docs/guide/authenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders.adoc @@ -6,17 +6,3 @@ The following code snippet illustrates a naive implementation: ---- include::{testssecurity}/security/token/basicauth/AuthenticationProviderUserPassword.java[tag=clazz,indent=0] ---- - -The main api:security.authentication.AuthenticationProvider[] interface is a non-blocking reactive API. If you need to implement custom authentication and prefer to use a blocking imperative style, you can instead implement the api:security.authentication.BlockingAuthenticationProvider[] interface, and the framework will adapt the blocking implementation to the reactive API. Such an adapted implementation will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor in order to avoid blocking the main reactive flow. The above example could be re-implemented as: - -[source, java] ----- -include::{testssecurity}/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java[tag=clazz,indent=0] ----- -NOTE: If you need to provide multiple implementations of `BlockingAuthenticationProvider`, then each implementation must have a `@Qualifier` such as `@Named`. - -The built-in <> uses every available authentication provider. The first provider that returns a successful authentication response will have its value used as the basis for the JWT token or session state. - -Basic authentication which is implemented as an api:security.filters.AuthenticationFetcher[] will also trigger the available api:security.authentication.AuthenticationProvider[]s. - -Micronaut comes with authentication providers for LDAP and the OAuth 2.0 password grant authentication flow. For any custom authentication, an authentication provider must be created. diff --git a/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc new file mode 100644 index 0000000000..e9d06483db --- /dev/null +++ b/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc @@ -0,0 +1,5 @@ +The main api:security.authentication.AuthenticationProvider[] interface is a non-blocking reactive API. If you prefer a blocking imperative style, you can instead implement the api:security.authentication.BlockingAuthenticationProvider[] interface, and the framework will adapt the blocking implementation to the reactive API. Such an adapted implementation will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor to avoid blocking the main reactive flow. The above example could be re-implemented as: + +snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] + +NOTE: `BlockingAuthenticationProvider` needs a `@Named` qualifier. diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index 0b7544a4c0..feae041eb7 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -16,6 +16,7 @@ securityConfiguration: redirection: Redirection Configuration authenticationProviders: title: Authentication Providers + blockingAuthenticationProviders: Blocking Authentication Providers authenticationProvidersUseCases: Authentication Providers Use Cases builtInAuthenticationProviders: Built-In Authentication Providers securityRule: diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy new file mode 100644 index 0000000000..aa4c3d9036 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy @@ -0,0 +1,61 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@MicronautTest +class BlockingAuthenticationProviderTest extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + void "blockingAuthProvider"() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("user", "password")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("user", "wrong")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } +} diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy new file mode 100644 index 0000000000..c1cc032cd6 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy @@ -0,0 +1,31 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.http.HttpRequest +import io.micronaut.security.authentication.AuthenticationFailureReason +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.BlockingAuthenticationProvider +import jakarta.inject.Named + +@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +//tag::clazz[] +@Named(CustomAuthenticationProvider.NAME) +class CustomAuthenticationProvider implements BlockingAuthenticationProvider> { + static final String NAME = "foo" + + @Override + AuthenticationResponse authenticate(HttpRequest httpRequest, + AuthenticationRequest authenticationRequest) { + (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") ? + AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + } + + @Override + @NonNull String getName() { + NAME + } +} +//end::clazz[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt new file mode 100644 index 0000000000..f8b7edddac --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt @@ -0,0 +1,46 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@MicronautTest +internal class BlockingAuthenticationProviderTest { + @Test + fun blockingAuthProvider(@Client("/") httpClient: HttpClient) { + val client = httpClient.toBlocking() + val json = Assertions.assertDoesNotThrow { + client.retrieve(createRequest("user", "password")) + } + val expected = """{"message":"Hello World"}""" + Assertions.assertEquals(expected, json) + val ex = Assertions.assertThrows(HttpClientResponseException::class.java) { + client.retrieve(createRequest("user", "wrong")) + } + Assertions.assertEquals(HttpStatus.UNAUTHORIZED, ex.status) + } + + private fun createRequest(userName: String, password: String): HttpRequest<*> { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Controller("/messages") + internal class HelloWorldController { + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + fun index(): Map = mapOf("message" to "Hello World") + } +} diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt new file mode 100644 index 0000000000..64ede4ae41 --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt @@ -0,0 +1,31 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.http.HttpRequest +import io.micronaut.security.authentication.AuthenticationFailureReason +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.BlockingAuthenticationProvider +import jakarta.inject.Named + +@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +//tag::clazz[] +@Named(CustomAuthenticationProvider.NAME) +class CustomAuthenticationProvider : BlockingAuthenticationProvider> { + override fun authenticate( + httpRequest: HttpRequest<*>, + authenticationRequest: AuthenticationRequest<*, *> + ): AuthenticationResponse { + return if (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") + AuthenticationResponse.success("user") + else AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + } + + override fun getName(): String = NAME + + companion object { + const val NAME = "foo" + } +} +//end::clazz[] diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java new file mode 100644 index 0000000000..fa971bf0c7 --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java @@ -0,0 +1,52 @@ +package io.micronaut.security.docs.blockingauthenticationprovider; + +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@MicronautTest +class BlockingAuthenticationProviderTest { + + @Test + void blockingAuthProvider(@Client("/") HttpClient httpClient) { + BlockingHttpClient client = httpClient.toBlocking(); + String json = assertDoesNotThrow(() -> client.retrieve(createRequest("user", "password"))); + String expected = """ + {"message":"Hello World"}"""; + assertEquals(expected, json); + HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.retrieve(createRequest("user", "wrong"))); + assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatus()); + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password); + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + return Collections.singletonMap("message", "Hello World"); + } + } +} diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java new file mode 100644 index 0000000000..d99ca12846 --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java @@ -0,0 +1,33 @@ +package io.micronaut.security.docs.blockingauthenticationprovider; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.AuthenticationFailureReason; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import jakarta.inject.Named; + +@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +//tag::clazz[] +@Named(CustomAuthenticationProvider.NAME) +class CustomAuthenticationProvider implements BlockingAuthenticationProvider> { + static final String NAME = "foo"; + + @Override + public AuthenticationResponse authenticate(HttpRequest httpRequest, + AuthenticationRequest authenticationRequest) { + return ( + authenticationRequest.getIdentity().equals("user") && + authenticationRequest.getSecret().equals("password") + ) ? AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); + } + + @Override + public @NonNull String getName() { + return NAME; + } +} +//end::clazz[] From 278a68aaa2c904d8109190aea4bb2d6c5d048905 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 11:25:49 +0100 Subject: [PATCH 06/37] remove import --- .../CustomAuthenticationProvider.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt index 64ede4ae41..dd9b2f6e60 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt @@ -1,7 +1,6 @@ package io.micronaut.security.docs.blockingauthenticationprovider import io.micronaut.context.annotation.Requires -import io.micronaut.core.annotation.NonNull import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest From 03ddaf3b34722ba8c4687d71bc2cdc4385058792 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 18:56:46 +0100 Subject: [PATCH 07/37] revert use class again --- ...BlockingAuthenticationProviderFactory.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java index b12b952253..cb9b426681 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java @@ -59,14 +59,21 @@ AuthenticationProvider createAuthenticationProvider(BlockingAuthenticatio return new BlockingAuthenticationProviderAdapter<>(blockingAuthenticationProvider, scheduler); } - private record BlockingAuthenticationProviderAdapter( - BlockingAuthenticationProvider blockingAuthenticationProvider, - Scheduler scheduler) implements AuthenticationProvider { + private static class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { + + private final BlockingAuthenticationProvider blockingAuthenticationProvider; + + private final Scheduler scheduler; + + private BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider blockingAuthenticationProvider, Scheduler scheduler) { + this.blockingAuthenticationProvider = blockingAuthenticationProvider; + this.scheduler = scheduler; + } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) - .subscribeOn(scheduler); - } + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) + .subscribeOn(scheduler); } + } } From 74337d41ea5408d85a534fdd6f01569da793b833 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 20:04:41 +0100 Subject: [PATCH 08/37] Blocking authentication provider impr (#1532) * BlockingAuthenticationProvider implements Named * split docs * add multilanguage snippet * remove import * revert use class again --- .../BlockingAuthenticationProvider.java | 3 +- ...BlockingAuthenticationProviderFactory.java | 8 ++- ...ingAuthenticationProviderUserPassword.java | 28 ++++++--- .../BlockingAuthenticationProviderSpec.groovy | 16 ++++- .../docs/guide/authenticationProviders.adoc | 8 --- .../blockingAuthenticationProviders.adoc | 5 ++ src/main/docs/guide/toc.yml | 1 + .../BlockingAuthenticationProviderTest.groovy | 61 +++++++++++++++++++ .../CustomAuthenticationProvider.groovy | 31 ++++++++++ .../BlockingAuthenticationProviderTest.kt | 46 ++++++++++++++ .../CustomAuthenticationProvider.kt | 30 +++++++++ .../BlockingAuthenticationProviderTest.java | 52 ++++++++++++++++ .../CustomAuthenticationProvider.java | 33 ++++++++++ 13 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc create mode 100644 test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy create mode 100644 test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy create mode 100644 test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt create mode 100644 test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt create mode 100644 test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java create mode 100644 test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java index 845f149d16..5cc6fa00a1 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java @@ -18,13 +18,14 @@ import io.micronaut.core.annotation.Blocking; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.naming.Named; /** * Defines an authentication provider. * @since 4.5.0 * @param Request */ -public interface BlockingAuthenticationProvider { +public interface BlockingAuthenticationProvider extends Named { /** * Authenticates a user with the given request. If a successful authentication is diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java index 0bb0dd9123..cb9b426681 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java @@ -17,6 +17,7 @@ import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; +import io.micronaut.core.annotation.Internal; import io.micronaut.scheduling.TaskExecutors; import jakarta.inject.Named; import org.reactivestreams.Publisher; @@ -32,11 +33,12 @@ * @since 4.5.0 */ @Factory -public class BlockingAuthenticationProviderFactory { +@Internal +class BlockingAuthenticationProviderFactory { private final Scheduler scheduler; - public BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { + BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { this.scheduler = Schedulers.fromExecutorService(executorService); } @@ -71,7 +73,7 @@ private BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider @Override public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) - .subscribeOn(scheduler); + .subscribeOn(scheduler); } } } diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java index 325f74f5b9..d87c6feb2d 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java @@ -1,23 +1,35 @@ package io.micronaut.docs.security.token.basicauth; -//tag::clazz[] +//tag::imports[] import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import jakarta.inject.Named; import jakarta.inject.Singleton; -//end::clazz[] + +//end::imports[] @Requires(property = "spec.name", value = "BlockingBasicAuthSpec") //tag::clazz[] +@Named(BlockingAuthenticationProviderUserPassword.NAME) @Singleton public class BlockingAuthenticationProviderUserPassword implements BlockingAuthenticationProvider { + public static final String NAME = "foo"; + @Override + public AuthenticationResponse authenticate(T httpRequest, + AuthenticationRequest authenticationRequest) { + return ( + authenticationRequest.getIdentity().equals("user") && + authenticationRequest.getSecret().equals("password") + ) ? AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); + } + @Override - public AuthenticationResponse authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { - return AuthenticationResponse.success("user"); - } else { - throw AuthenticationResponse.exception(); - } + public @NonNull String getName() { + return BlockingAuthenticationProviderUserPassword.NAME; } } //end::clazz[] diff --git a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy index 255d1fae5d..7b5b4a5cc8 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy @@ -63,8 +63,9 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") @Singleton - @Named("SimpleBlockingAuthenticationProvider") + @Named(SimpleBlockingAuthenticationProvider.NAME) static class SimpleBlockingAuthenticationProvider implements BlockingAuthenticationProvider { + static final String NAME = "SimpleBlockingAuthenticationProvider" private String executedThreadName @@ -77,16 +78,27 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification return AuthenticationResponse.failure("Over the line.") } } + + @Override + String getName() { + SimpleBlockingAuthenticationProvider.NAME + } } @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") @Singleton - @Named("NoOpBlockingAuthenticationProvider") + @Named(NoOpBlockingAuthenticationProvider.NAME) static class NoOpBlockingAuthenticationProvider implements BlockingAuthenticationProvider { + static final String NAME = "NoOpBlockingAuthenticationProvider" @Override AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { throw AuthenticationResponse.exception() } + + @Override + String getName() { + NoOpBlockingAuthenticationProvider.NAME + } } } diff --git a/src/main/docs/guide/authenticationProviders.adoc b/src/main/docs/guide/authenticationProviders.adoc index c5648f2545..d3d3f94387 100644 --- a/src/main/docs/guide/authenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders.adoc @@ -6,11 +6,3 @@ The following code snippet illustrates a naive implementation: ---- include::{testssecurity}/security/token/basicauth/AuthenticationProviderUserPassword.java[tag=clazz,indent=0] ---- - -The main api:security.authentication.AuthenticationProvider[] interface is a non-blocking reactive API. If you need to implement custom authentication and prefer to use a blocking imperative style, you can instead implement the api:security.authentication.BlockingAuthenticationProvider[] interface, and the framework will adapt the blocking implementation to the reactive API. Such an adapted implementation will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor in order to avoid blocking the main reactive flow. The above example could be re-implemented as: - -[source, java] ----- -include::{testssecurity}/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java[tag=clazz,indent=0] ----- -NOTE: If you need to provide multiple implementations of `BlockingAuthenticationProvider`, then each implementation must have a `@Qualifier` such as `@Named`. diff --git a/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc new file mode 100644 index 0000000000..e9d06483db --- /dev/null +++ b/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc @@ -0,0 +1,5 @@ +The main api:security.authentication.AuthenticationProvider[] interface is a non-blocking reactive API. If you prefer a blocking imperative style, you can instead implement the api:security.authentication.BlockingAuthenticationProvider[] interface, and the framework will adapt the blocking implementation to the reactive API. Such an adapted implementation will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor to avoid blocking the main reactive flow. The above example could be re-implemented as: + +snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] + +NOTE: `BlockingAuthenticationProvider` needs a `@Named` qualifier. diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index 0b7544a4c0..feae041eb7 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -16,6 +16,7 @@ securityConfiguration: redirection: Redirection Configuration authenticationProviders: title: Authentication Providers + blockingAuthenticationProviders: Blocking Authentication Providers authenticationProvidersUseCases: Authentication Providers Use Cases builtInAuthenticationProviders: Built-In Authentication Providers securityRule: diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy new file mode 100644 index 0000000000..aa4c3d9036 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy @@ -0,0 +1,61 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@MicronautTest +class BlockingAuthenticationProviderTest extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + void "blockingAuthProvider"() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("user", "password")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("user", "wrong")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } +} diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy new file mode 100644 index 0000000000..c1cc032cd6 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy @@ -0,0 +1,31 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.http.HttpRequest +import io.micronaut.security.authentication.AuthenticationFailureReason +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.BlockingAuthenticationProvider +import jakarta.inject.Named + +@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +//tag::clazz[] +@Named(CustomAuthenticationProvider.NAME) +class CustomAuthenticationProvider implements BlockingAuthenticationProvider> { + static final String NAME = "foo" + + @Override + AuthenticationResponse authenticate(HttpRequest httpRequest, + AuthenticationRequest authenticationRequest) { + (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") ? + AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + } + + @Override + @NonNull String getName() { + NAME + } +} +//end::clazz[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt new file mode 100644 index 0000000000..f8b7edddac --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt @@ -0,0 +1,46 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@MicronautTest +internal class BlockingAuthenticationProviderTest { + @Test + fun blockingAuthProvider(@Client("/") httpClient: HttpClient) { + val client = httpClient.toBlocking() + val json = Assertions.assertDoesNotThrow { + client.retrieve(createRequest("user", "password")) + } + val expected = """{"message":"Hello World"}""" + Assertions.assertEquals(expected, json) + val ex = Assertions.assertThrows(HttpClientResponseException::class.java) { + client.retrieve(createRequest("user", "wrong")) + } + Assertions.assertEquals(HttpStatus.UNAUTHORIZED, ex.status) + } + + private fun createRequest(userName: String, password: String): HttpRequest<*> { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Controller("/messages") + internal class HelloWorldController { + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + fun index(): Map = mapOf("message" to "Hello World") + } +} diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt new file mode 100644 index 0000000000..dd9b2f6e60 --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt @@ -0,0 +1,30 @@ +package io.micronaut.security.docs.blockingauthenticationprovider + +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.security.authentication.AuthenticationFailureReason +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.BlockingAuthenticationProvider +import jakarta.inject.Named + +@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +//tag::clazz[] +@Named(CustomAuthenticationProvider.NAME) +class CustomAuthenticationProvider : BlockingAuthenticationProvider> { + override fun authenticate( + httpRequest: HttpRequest<*>, + authenticationRequest: AuthenticationRequest<*, *> + ): AuthenticationResponse { + return if (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") + AuthenticationResponse.success("user") + else AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + } + + override fun getName(): String = NAME + + companion object { + const val NAME = "foo" + } +} +//end::clazz[] diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java new file mode 100644 index 0000000000..fa971bf0c7 --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java @@ -0,0 +1,52 @@ +package io.micronaut.security.docs.blockingauthenticationprovider; + +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@MicronautTest +class BlockingAuthenticationProviderTest { + + @Test + void blockingAuthProvider(@Client("/") HttpClient httpClient) { + BlockingHttpClient client = httpClient.toBlocking(); + String json = assertDoesNotThrow(() -> client.retrieve(createRequest("user", "password"))); + String expected = """ + {"message":"Hello World"}"""; + assertEquals(expected, json); + HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.retrieve(createRequest("user", "wrong"))); + assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatus()); + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password); + } + + @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + return Collections.singletonMap("message", "Hello World"); + } + } +} diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java new file mode 100644 index 0000000000..d99ca12846 --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java @@ -0,0 +1,33 @@ +package io.micronaut.security.docs.blockingauthenticationprovider; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.AuthenticationFailureReason; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import jakarta.inject.Named; + +@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +//tag::clazz[] +@Named(CustomAuthenticationProvider.NAME) +class CustomAuthenticationProvider implements BlockingAuthenticationProvider> { + static final String NAME = "foo"; + + @Override + public AuthenticationResponse authenticate(HttpRequest httpRequest, + AuthenticationRequest authenticationRequest) { + return ( + authenticationRequest.getIdentity().equals("user") && + authenticationRequest.getSecret().equals("password") + ) ? AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); + } + + @Override + public @NonNull String getName() { + return NAME; + } +} +//end::clazz[] From 3251149a9479e73a2542d47a65ba7af1188f725d Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Tue, 12 Dec 2023 20:08:13 +0100 Subject: [PATCH 09/37] Update security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java --- .../authentication/BlockingAuthenticationProviderFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java index cb9b426681..ecfccf24c3 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java +++ b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java @@ -59,7 +59,7 @@ AuthenticationProvider createAuthenticationProvider(BlockingAuthenticatio return new BlockingAuthenticationProviderAdapter<>(blockingAuthenticationProvider, scheduler); } - private static class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { + private static final class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { private final BlockingAuthenticationProvider blockingAuthenticationProvider; From 8660cce71dc48909dae346b73e0f04cbe33aad90 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 11:49:48 +0100 Subject: [PATCH 10/37] rename to ImperativeAuthenticationProvider and @Blocking --- ...BlockingAuthenticationProviderFactory.java | 79 ---------------- ... => ImperativeAuthenticationProvider.java} | 17 ++-- ...perativeAuthenticationProviderFactory.java | 89 +++++++++++++++++++ ...ImperativeAuthenticationProviderUtils.java | 68 ++++++++++++++ ...veAuthenticationProviderUserPassword.java} | 8 +- ...perativeAuthenticationProviderSpec.groovy} | 34 +++---- ...tiveAuthenticationProviderUtilsTest.groovy | 84 +++++++++++++++++ .../blockingAuthenticationProviders.adoc | 5 -- .../imperativeAuthenticationProviders.adoc | 8 ++ src/main/docs/guide/toc.yml | 2 +- .../CustomAuthenticationProvider.groovy | 6 +- ...perativeAuthenticationProviderTest.groovy} | 6 +- .../CustomAuthenticationProvider.kt | 7 +- ...> ImperativeAuthenticationProviderTest.kt} | 6 +- .../CustomAuthenticationProvider.java | 8 +- ...ImperativeAuthenticationProviderTest.java} | 6 +- 16 files changed, 300 insertions(+), 133 deletions(-) delete mode 100644 security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java rename security/src/main/java/io/micronaut/security/authentication/{BlockingAuthenticationProvider.java => ImperativeAuthenticationProvider.java} (70%) create mode 100644 security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java create mode 100644 security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java rename security/src/test/groovy/io/micronaut/docs/security/token/basicauth/{BlockingAuthenticationProviderUserPassword.java => ImperativeAuthenticationProviderUserPassword.java} (78%) rename security/src/test/groovy/io/micronaut/security/authentication/{BlockingAuthenticationProviderSpec.groovy => ImperativeAuthenticationProviderSpec.groovy} (69%) create mode 100644 security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy delete mode 100644 src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc create mode 100644 src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc rename test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/{BlockingAuthenticationProviderTest.groovy => ImperativeAuthenticationProviderTest.groovy} (88%) rename test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/{BlockingAuthenticationProviderTest.kt => ImperativeAuthenticationProviderTest.kt} (88%) rename test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/{BlockingAuthenticationProviderTest.java => ImperativeAuthenticationProviderTest.java} (89%) diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java deleted file mode 100644 index cb9b426681..0000000000 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProviderFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2017-2023 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.authentication; - -import io.micronaut.context.annotation.EachBean; -import io.micronaut.context.annotation.Factory; -import io.micronaut.core.annotation.Internal; -import io.micronaut.scheduling.TaskExecutors; -import jakarta.inject.Named; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; - -import java.util.concurrent.ExecutorService; - -/** - * A factory for adapting {@link BlockingAuthenticationProvider} beans to expose them as {@link AuthenticationProvider}. - * - * @since 4.5.0 - */ -@Factory -@Internal -class BlockingAuthenticationProviderFactory { - - private final Scheduler scheduler; - - BlockingAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService) { - this.scheduler = Schedulers.fromExecutorService(executorService); - } - - /** - * Creates an adapted {@link AuthenticationProvider} for each provided instance of {@link BlockingAuthenticationProvider}. - * - *

- * NOTE - If there are multiple instances of {@link BlockingAuthenticationProvider} in the application context, then they - * must be annotated with a {@link jakarta.inject.Qualifier} such as {@link Named}. - *

- * - * @param blockingAuthenticationProvider An instance of {@link BlockingAuthenticationProvider} to be adapted - * @return An {@link AuthenticationProvider} adapted from the blocking provider - * @param The request type - */ - @EachBean(BlockingAuthenticationProvider.class) - AuthenticationProvider createAuthenticationProvider(BlockingAuthenticationProvider blockingAuthenticationProvider) { - return new BlockingAuthenticationProviderAdapter<>(blockingAuthenticationProvider, scheduler); - } - - private static class BlockingAuthenticationProviderAdapter implements AuthenticationProvider { - - private final BlockingAuthenticationProvider blockingAuthenticationProvider; - - private final Scheduler scheduler; - - private BlockingAuthenticationProviderAdapter(BlockingAuthenticationProvider blockingAuthenticationProvider, Scheduler scheduler) { - this.blockingAuthenticationProvider = blockingAuthenticationProvider; - this.scheduler = scheduler; - } - - @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - return Mono.fromCallable(() -> blockingAuthenticationProvider.authenticate(httpRequest, authenticationRequest)) - .subscribeOn(scheduler); - } - } -} diff --git a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProvider.java similarity index 70% rename from security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java rename to security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProvider.java index 5cc6fa00a1..5bd8572bc6 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BlockingAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProvider.java @@ -15,30 +15,29 @@ */ package io.micronaut.security.authentication; +import io.micronaut.context.annotation.Executable; import io.micronaut.core.annotation.Blocking; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.naming.Named; /** - * Defines an authentication provider. + * Defines an Authentication Provider with an imperative style. * @since 4.5.0 * @param Request */ -public interface BlockingAuthenticationProvider extends Named { +public interface ImperativeAuthenticationProvider extends Named { /** - * Authenticates a user with the given request. If a successful authentication is - * returned, the object must be an instance of {@link Authentication}. - * - * Implementations of this blocking variation of {@link AuthenticationProvider} will be safely executed on a + * Authenticates a user with the given request. If a successful authentication is returned, the object must be an instance of {@link Authentication}. + * If your implementation is blocking, annotate the overriden method with {@link Blocking} and it will be safely executed on a * dedicated thread in order to not block the main reactive chain of execution. * * @param httpRequest The http request - * @param authenticationRequest The credentials to authenticate + * @param authRequest The credentials to authenticate * @return An {@link AuthenticationResponse} indicating either success or failure. */ @NonNull - @Blocking - AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest); + @Executable + AuthenticationResponse authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authRequest); } diff --git a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java new file mode 100644 index 0000000000..a91d7a6060 --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017-2023 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.authentication; + +import io.micronaut.context.BeanContext; +import io.micronaut.context.annotation.EachBean; +import io.micronaut.context.annotation.Factory; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.scheduling.TaskExecutors; +import jakarta.inject.Named; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.concurrent.ExecutorService; + +/** + * A factory for adapting {@link ImperativeAuthenticationProvider} beans to expose them as {@link AuthenticationProvider}. + * + * @since 4.5.0 + */ +@Factory +@Internal +class ImperativeAuthenticationProviderFactory { + + private final Scheduler scheduler; + private final BeanContext beanContext; + + ImperativeAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService, + BeanContext beanContext) { + this.scheduler = Schedulers.fromExecutorService(executorService); + this.beanContext = beanContext; + } + + /** + * Creates an adapted {@link AuthenticationProvider} for each provided instance of {@link ImperativeAuthenticationProvider}. + * + *

+ * NOTE - If there are multiple instances of {@link ImperativeAuthenticationProvider} in the application context, then they + * must be annotated with a {@link jakarta.inject.Qualifier} such as {@link Named}. + *

+ * + * @param imperativeAuthenticationProvider An instance of {@link ImperativeAuthenticationProvider} to be adapted + * @return An {@link AuthenticationProvider} adapted from the blocking provider + * @param The request type + */ + @EachBean(ImperativeAuthenticationProvider.class) + AuthenticationProvider createAuthenticationProvider(ImperativeAuthenticationProvider imperativeAuthenticationProvider) { + return new ImperativeAuthenticationProviderAdapter<>(imperativeAuthenticationProvider, + ImperativeAuthenticationProviderUtils.isAuthenticateBlocking(beanContext, imperativeAuthenticationProvider) ? scheduler : null); + } + + private static final class ImperativeAuthenticationProviderAdapter implements AuthenticationProvider { + + @NonNull + private final ImperativeAuthenticationProvider imperativeAuthenticationProvider; + + @Nullable + private final Scheduler scheduler; + + private ImperativeAuthenticationProviderAdapter(@NonNull ImperativeAuthenticationProvider imperativeAuthenticationProvider, + @Nullable Scheduler scheduler) { + this.imperativeAuthenticationProvider = imperativeAuthenticationProvider; + this.scheduler = scheduler; + } + + @Override + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + Mono authenticationResponseMono = Mono.fromCallable(() -> imperativeAuthenticationProvider.authenticate(httpRequest, authenticationRequest)); + return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono; + } + } +} diff --git a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java b/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java new file mode 100644 index 0000000000..a2e236af35 --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017-2023 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.authentication; + +import io.micronaut.context.BeanContext; +import io.micronaut.context.BeanRegistration; +import io.micronaut.core.annotation.Blocking; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.http.HttpRequest; +import io.micronaut.inject.BeanDefinition; +import io.micronaut.inject.ExecutableMethod; + +import java.lang.annotation.Annotation; +import java.util.Optional; + +/** + * Utility class to check whether {@link ImperativeAuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}. + */ +@Internal +public final class ImperativeAuthenticationProviderUtils { + private static final String METHOD_AUTHENTICATE = "authenticate"; + + private ImperativeAuthenticationProviderUtils() { + } + + public static boolean isAuthenticateBlocking(BeanContext beanContext, + @NonNull ImperativeAuthenticationProvider authenticationProvider) { + if (isMethodBlocking(beanContext, authenticationProvider, METHOD_AUTHENTICATE, Object.class, AuthenticationRequest.class)) { + return true; + } + return isMethodBlocking(beanContext, authenticationProvider, METHOD_AUTHENTICATE, HttpRequest.class, AuthenticationRequest.class); + } + + private static boolean isMethodBlocking(BeanContext beanContext, + @NonNull Object bean, + String methodName, + Class... argumentTypes) { + Optional> beanDefinitionOptional = beanContext.findBeanRegistration(bean).map(BeanRegistration::getBeanDefinition); + if (beanDefinitionOptional.isEmpty()) { + return false; + } + BeanDefinition beanDefinition = beanDefinitionOptional.get(); + Optional> methodOptional = beanDefinition.findMethod(methodName, argumentTypes); + return methodOptional.filter(ImperativeAuthenticationProviderUtils::isBlockingMethod).isPresent(); + } + + private static boolean isBlockingMethod(ExecutableMethod executableMethod) { + return isMethodAnnotatedWith(executableMethod, Blocking.class); + } + + private static boolean isMethodAnnotatedWith(ExecutableMethod executableMethod, Class annotationClass) { + return executableMethod.getAnnotationMetadata().hasAnnotation(annotationClass); + } +} diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java similarity index 78% rename from security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java rename to security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java index d87c6feb2d..7cdb65d2bf 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingAuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java @@ -6,16 +6,16 @@ import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import io.micronaut.security.authentication.ImperativeAuthenticationProvider; import jakarta.inject.Named; import jakarta.inject.Singleton; //end::imports[] @Requires(property = "spec.name", value = "BlockingBasicAuthSpec") //tag::clazz[] -@Named(BlockingAuthenticationProviderUserPassword.NAME) +@Named(ImperativeAuthenticationProviderUserPassword.NAME) @Singleton -public class BlockingAuthenticationProviderUserPassword implements BlockingAuthenticationProvider { +public class ImperativeAuthenticationProviderUserPassword implements ImperativeAuthenticationProvider { public static final String NAME = "foo"; @Override public AuthenticationResponse authenticate(T httpRequest, @@ -29,7 +29,7 @@ public AuthenticationResponse authenticate(T httpRequest, @Override public @NonNull String getName() { - return BlockingAuthenticationProviderUserPassword.NAME; + return ImperativeAuthenticationProviderUserPassword.NAME; } } //end::clazz[] diff --git a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy similarity index 69% rename from security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy rename to security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy index 7b5b4a5cc8..6841c770ae 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/BlockingAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy @@ -1,6 +1,7 @@ package io.micronaut.security.authentication import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.Blocking import io.micronaut.core.annotation.Nullable import io.micronaut.http.HttpMethod import io.micronaut.http.HttpRequest @@ -11,24 +12,24 @@ import jakarta.inject.Named import jakarta.inject.Singleton import reactor.core.publisher.Mono -class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification { +class ImperativeAuthenticationProviderSpec extends ApplicationContextSpecification { static final String EXECUTOR_NAME_MATCH = "%s-executor".formatted(LoomSupport.supported ? TaskExecutors.VIRTUAL : TaskExecutors.IO) - SimpleBlockingAuthenticationProvider provider + SimpleImperativeAuthenticationProvider provider def setup() { - provider = getBean(SimpleBlockingAuthenticationProvider.class) + provider = getBean(SimpleImperativeAuthenticationProvider.class) provider.executedThreadName = "" } - def "multiple BlockingAuthenticationProvider implementations are registered"() { + def "multiple ImperativeAuthenticationProvider implementations are registered"() { given: BasicAuthAuthenticationFetcher authFetcher = getBean(BasicAuthAuthenticationFetcher.class) expect: authFetcher - getApplicationContext().getBeanRegistrations(BlockingAuthenticationProvider.class).size() == 2 + getApplicationContext().getBeanRegistrations(ImperativeAuthenticationProvider.class).size() == 2 } def "a blocking authentication provider can authenticate successfully"() { @@ -58,18 +59,19 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification @Override String getSpecName() { - return "BlockingAuthenticationProviderSpec" + return "ImperativeAuthenticationProviderSpec" } - @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderSpec") @Singleton - @Named(SimpleBlockingAuthenticationProvider.NAME) - static class SimpleBlockingAuthenticationProvider implements BlockingAuthenticationProvider { - static final String NAME = "SimpleBlockingAuthenticationProvider" + @Named(SimpleImperativeAuthenticationProvider.NAME) + static class SimpleImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + static final String NAME = "SimpleImperativeAuthenticationProvider" private String executedThreadName @Override + @Blocking AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { executedThreadName = Thread.currentThread().getName() if (authenticationRequest.getIdentity().toString() == 'lebowski' && authenticationRequest.getSecret().toString() == 'thedudeabides') { @@ -81,15 +83,15 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification @Override String getName() { - SimpleBlockingAuthenticationProvider.NAME + SimpleImperativeAuthenticationProvider.NAME } } - @Requires(property = "spec.name", value = "BlockingAuthenticationProviderSpec") + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderSpec") @Singleton - @Named(NoOpBlockingAuthenticationProvider.NAME) - static class NoOpBlockingAuthenticationProvider implements BlockingAuthenticationProvider { - static final String NAME = "NoOpBlockingAuthenticationProvider" + @Named(NoOpImperativeAuthenticationProvider.NAME) + static class NoOpImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + static final String NAME = "NoOpImperativeAuthenticationProvider" @Override AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { @@ -98,7 +100,7 @@ class BlockingAuthenticationProviderSpec extends ApplicationContextSpecification @Override String getName() { - NoOpBlockingAuthenticationProvider.NAME + NoOpImperativeAuthenticationProvider.NAME } } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy new file mode 100644 index 0000000000..ab0f61291d --- /dev/null +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy @@ -0,0 +1,84 @@ +package io.micronaut.security.authentication + +import io.micronaut.context.BeanContext +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.Blocking +import io.micronaut.core.annotation.NonNull +import io.micronaut.core.annotation.Nullable +import io.micronaut.http.HttpRequest +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import jakarta.inject.Named +import jakarta.inject.Singleton +import spock.lang.Specification + +@Property(name = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") +@MicronautTest(startApplication = false) +class ImperativeAuthenticationProviderUtilsTest extends Specification { + + @Inject + BeanContext beanContext + + void "#clazz authenticate method is #description"(boolean isBlocking, + Class clazz, + String description) { + expect: + isBlocking == ImperativeAuthenticationProviderUtils.isAuthenticateBlocking(beanContext, beanContext.getBean(clazz)) + + where: + isBlocking | clazz + true | BlockingImperativeAuthenticationProvider.class + true | BlockingWithGenericImperativeAuthenticationProvider + false | NonBlockingImperativeAuthenticationProvider.class + description = isBlocking ? "is annotated with @Blocking" : "is not annotated with @Blocking" + } + + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") + @Singleton + @Named("foo") + static class BlockingImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + @Override + @Blocking + AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { + return AuthenticationResponse.failure() + } + + @Override + String getName() { + "foo" + } + } + + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") + @Singleton + @Named("foo") + static class BlockingWithGenericImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + @Override + @Blocking + AuthenticationResponse authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authRequest) { + return AuthenticationResponse.failure() + } + + @Override + String getName() { + "foo" + } + } + + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") + @Singleton + @Named("bar") + static class NonBlockingImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + @Override + AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { + return AuthenticationResponse.failure() + } + + @Override + String getName() { + "bar" + } + } + +} diff --git a/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc deleted file mode 100644 index e9d06483db..0000000000 --- a/src/main/docs/guide/authenticationProviders/blockingAuthenticationProviders.adoc +++ /dev/null @@ -1,5 +0,0 @@ -The main api:security.authentication.AuthenticationProvider[] interface is a non-blocking reactive API. If you prefer a blocking imperative style, you can instead implement the api:security.authentication.BlockingAuthenticationProvider[] interface, and the framework will adapt the blocking implementation to the reactive API. Such an adapted implementation will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor to avoid blocking the main reactive flow. The above example could be re-implemented as: - -snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] - -NOTE: `BlockingAuthenticationProvider` needs a `@Named` qualifier. diff --git a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc new file mode 100644 index 0000000000..35acb18119 --- /dev/null +++ b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc @@ -0,0 +1,8 @@ +The main api:security.authentication.AuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.ImperativeAuthenticationProvider[] interface: + +snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] + +NOTE: `ImperativeAuthenticationProvider` needs a `@Named` qualifier. + +IMPORTANT: If your implementation is blocking (e.g., you fetch the user credentials from a database in a blocking way to check against the supplied authentication request), annotate the `authenticate` method with `@Blocking`. If the `authenticate` method is annotated with `@Blocking`, it will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor to avoid blocking the main reactive flow. + diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index feae041eb7..c5d2820607 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -16,7 +16,7 @@ securityConfiguration: redirection: Redirection Configuration authenticationProviders: title: Authentication Providers - blockingAuthenticationProviders: Blocking Authentication Providers + imperativeAuthenticationProviders: ImperativeAuthenticationProvider authenticationProvidersUseCases: Authentication Providers Use Cases builtInAuthenticationProviders: Built-In Authentication Providers securityRule: diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy index c1cc032cd6..d70c0a90cf 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy @@ -6,13 +6,13 @@ import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse -import io.micronaut.security.authentication.BlockingAuthenticationProvider +import io.micronaut.security.authentication.ImperativeAuthenticationProvider import jakarta.inject.Named -@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +@Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") //tag::clazz[] @Named(CustomAuthenticationProvider.NAME) -class CustomAuthenticationProvider implements BlockingAuthenticationProvider> { +class CustomAuthenticationProvider implements ImperativeAuthenticationProvider> { static final String NAME = "foo" @Override diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.groovy similarity index 88% rename from test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy rename to test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.groovy index aa4c3d9036..14d7914191 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.groovy @@ -16,9 +16,9 @@ import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Specification -@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@Property(name = "spec.name", value = "ImperativeAuthenticationProviderTest") @MicronautTest -class BlockingAuthenticationProviderTest extends Specification { +class ImperativeAuthenticationProviderTest extends Specification { @Inject @Client("/") @@ -48,7 +48,7 @@ class BlockingAuthenticationProviderTest extends Specification { return HttpRequest.GET("/messages").basicAuth(userName, password) } - @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") @Controller("/messages") static class HelloWorldController { diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt index dd9b2f6e60..8c5e1606e6 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt @@ -5,13 +5,14 @@ import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse -import io.micronaut.security.authentication.BlockingAuthenticationProvider +import io.micronaut.security.authentication.ImperativeAuthenticationProvider import jakarta.inject.Named -@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +@Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") //tag::clazz[] @Named(CustomAuthenticationProvider.NAME) -class CustomAuthenticationProvider : BlockingAuthenticationProvider> { +class CustomAuthenticationProvider : + ImperativeAuthenticationProvider> { override fun authenticate( httpRequest: HttpRequest<*>, authenticationRequest: AuthenticationRequest<*, *> diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.kt similarity index 88% rename from test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt rename to test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.kt index f8b7edddac..1cb12f579d 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.kt @@ -15,9 +15,9 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@Property(name = "spec.name", value = "ImperativeAuthenticationProviderTest") @MicronautTest -internal class BlockingAuthenticationProviderTest { +internal class ImperativeAuthenticationProviderTest { @Test fun blockingAuthProvider(@Client("/") httpClient: HttpClient) { val client = httpClient.toBlocking() @@ -36,7 +36,7 @@ internal class BlockingAuthenticationProviderTest { return HttpRequest.GET("/messages").basicAuth(userName, password) } - @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") @Controller("/messages") internal class HelloWorldController { @Secured(SecurityRule.IS_AUTHENTICATED) diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java index d99ca12846..dae36ad408 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java @@ -6,13 +6,13 @@ import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.BlockingAuthenticationProvider; +import io.micronaut.security.authentication.ImperativeAuthenticationProvider; import jakarta.inject.Named; -@Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") +@Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") //tag::clazz[] @Named(CustomAuthenticationProvider.NAME) -class CustomAuthenticationProvider implements BlockingAuthenticationProvider> { +class CustomAuthenticationProvider implements ImperativeAuthenticationProvider> { static final String NAME = "foo"; @Override @@ -20,7 +20,7 @@ public AuthenticationResponse authenticate(HttpRequest httpRequest, AuthenticationRequest authenticationRequest) { return ( authenticationRequest.getIdentity().equals("user") && - authenticationRequest.getSecret().equals("password") + authenticationRequest.getSecret().equals("password") ) ? AuthenticationResponse.success("user") : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); } diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.java similarity index 89% rename from test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java rename to test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.java index fa971bf0c7..3cac5feb21 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/BlockingAuthenticationProviderTest.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.java @@ -20,9 +20,9 @@ import static org.junit.jupiter.api.Assertions.*; -@Property(name = "spec.name", value = "BlockingAuthenticationProviderTest") +@Property(name = "spec.name", value = "ImperativeAuthenticationProviderTest") @MicronautTest -class BlockingAuthenticationProviderTest { +class ImperativeAuthenticationProviderTest { @Test void blockingAuthProvider(@Client("/") HttpClient httpClient) { @@ -39,7 +39,7 @@ private HttpRequest createRequest(String userName, String password) { return HttpRequest.GET("/messages").basicAuth(userName, password); } - @Requires(property = "spec.name", value = "BlockingAuthenticationProviderTest") + @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") @Controller("/messages") static class HelloWorldController { From c795eaada8c7253f0a82eef4334195a575273eb8 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 14:21:23 +0100 Subject: [PATCH 11/37] deprecated AuthenticationProvider with Publisher --- .../CustomAuthenticationProvider.java | 4 +- .../token/jwt/signature/jwks/JwksSpec.groovy | 4 +- .../OauthPasswordAuthenticationProvider.java | 4 +- .../password/PasswordGrantFactory.java | 6 +- .../PasswordGrantConditionSpec.groovy | 6 +- .../security/session/ContextPathSpec.groovy | 4 +- .../AuthenticationProvider.java | 21 ++----- .../authentication/Authenticator.java | 32 ++++++++-- .../BasicAuthAuthenticationFetcher.java | 2 +- .../AuthenticationProvider.java} | 11 +++- .../AuthenticationProviderFactory.java} | 34 ++++++----- .../AuthenticationProviderUtils.java} | 13 ++-- .../ReactiveAuthenticationProvider.java | 46 ++++++++++++++ .../AuthenticationProviderUserPassword.groovy | 4 +- .../AuthenticationProviderUserPassword.groovy | 4 +- .../AuthenticationProviderUserPassword.java | 4 +- ...iveAuthenticationProviderUserPassword.java | 4 +- .../authentication/AuthenticatorSpec.groovy | 25 ++++---- ...mperativeAuthenticationProviderSpec.groovy | 33 +++++----- ...tiveAuthenticationProviderUtilsTest.groovy | 28 +++++---- .../security/MockAuthenticationProvider.java | 4 +- .../docs/guide/authenticationProviders.adoc | 7 +-- .../imperativeAuthenticationProviders.adoc | 2 +- src/main/docs/guide/toc.yml | 4 +- ...oovy => AuthenticationProviderTest.groovy} | 6 +- .../CustomAuthenticationProvider.groovy | 14 +++-- .../CustomAuthenticationProvider.groovy | 30 ++++++++++ .../ReactiveAuthenticationProviderTest.groovy | 60 +++++++++++++++++++ ...rTest.kt => AuthenticationProviderTest.kt} | 6 +- .../CustomAuthenticationProvider.kt | 8 ++- .../CustomAuthenticationProvider.kt | 29 +++++++++ .../ReactiveAuthenticationProviderTest.kt | 46 ++++++++++++++ .../MockAuthenticationProvider.java | 4 +- ...t.java => AuthenticationProviderTest.java} | 6 +- .../CustomAuthenticationProvider.java | 8 ++- .../CustomAuthenticationProvider.java | 30 ++++++++++ .../ReactiveAuthenticationProviderTest.java | 52 ++++++++++++++++ 37 files changed, 461 insertions(+), 144 deletions(-) rename security/src/main/java/io/micronaut/security/authentication/{ImperativeAuthenticationProvider.java => provider/AuthenticationProvider.java} (74%) rename security/src/main/java/io/micronaut/security/authentication/{ImperativeAuthenticationProviderFactory.java => provider/AuthenticationProviderFactory.java} (59%) rename security/src/main/java/io/micronaut/security/authentication/{ImperativeAuthenticationProviderUtils.java => provider/AuthenticationProviderUtils.java} (82%) create mode 100644 security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java rename test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/{ImperativeAuthenticationProviderTest.groovy => AuthenticationProviderTest.groovy} (88%) create mode 100644 test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy create mode 100644 test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.groovy rename test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/{ImperativeAuthenticationProviderTest.kt => AuthenticationProviderTest.kt} (88%) create mode 100644 test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt create mode 100644 test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.kt rename test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/{ImperativeAuthenticationProviderTest.java => AuthenticationProviderTest.java} (89%) create mode 100644 test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java create mode 100644 test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java diff --git a/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java b/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java index 1e1ec5fd3c..880e4be15c 100644 --- a/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java +++ b/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java @@ -2,7 +2,7 @@ package io.micronaut.docs.jwtclaimsoverride; import io.micronaut.context.annotation.Requires; -import io.micronaut.security.authentication.AuthenticationProvider; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import jakarta.inject.Singleton; @@ -14,7 +14,7 @@ @Requires(property = "spec.name", value = "jwtclaimsoverride") //tag::clazz[] @Singleton -public class CustomAuthenticationProvider implements AuthenticationProvider { +public class CustomAuthenticationProvider implements ReactiveAuthenticationProvider { @Override public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { diff --git a/security-jwt/src/test/groovy/io/micronaut/security/token/jwt/signature/jwks/JwksSpec.groovy b/security-jwt/src/test/groovy/io/micronaut/security/token/jwt/signature/jwks/JwksSpec.groovy index d277971b11..29bae70490 100644 --- a/security-jwt/src/test/groovy/io/micronaut/security/token/jwt/signature/jwks/JwksSpec.groovy +++ b/security-jwt/src/test/groovy/io/micronaut/security/token/jwt/signature/jwks/JwksSpec.groovy @@ -18,7 +18,7 @@ import io.micronaut.http.annotation.Produces import io.micronaut.http.client.HttpClient import io.micronaut.runtime.server.EmbeddedServer import io.micronaut.security.annotation.Secured -import io.micronaut.security.authentication.AuthenticationProvider +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import io.micronaut.security.authentication.UsernamePasswordCredentials import io.micronaut.security.rules.SecurityRule import io.micronaut.security.testutils.authprovider.MockAuthenticationProvider @@ -77,7 +77,7 @@ class JwksSpec extends Specification { RSAJwkProvider, JwkProvider, RSASignatureGeneratorConfiguration, - AuthenticationProvider, + ReactiveAuthenticationProvider, ]) { gatewayEmbeddedServer.applicationContext.getBean(beanClazz) } diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java index 7b5b8849e4..1fbc45cd8b 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java @@ -15,9 +15,9 @@ */ package io.micronaut.security.oauth2.endpoint.token.request.password; -import io.micronaut.security.authentication.AuthenticationProvider; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.AuthenticationProvider; import io.micronaut.security.oauth2.configuration.OauthClientConfiguration; import io.micronaut.security.oauth2.configuration.endpoints.SecureEndpointConfiguration; import io.micronaut.security.oauth2.endpoint.AuthenticationMethod; @@ -32,7 +32,7 @@ import reactor.core.publisher.Flux; /** - * An {@link AuthenticationProvider} that delegates to an OAuth 2.0 provider using the + * A {@link AuthenticationProvider} that delegates to an OAuth 2.0 provider using the * password grant flow. * * @author Sergio del Amo diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/PasswordGrantFactory.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/PasswordGrantFactory.java index b8ac4122e9..8c9e5f8f1c 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/PasswordGrantFactory.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/PasswordGrantFactory.java @@ -21,7 +21,7 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.Nullable; -import io.micronaut.security.authentication.AuthenticationProvider; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.oauth2.client.OpenIdProviderMetadata; import io.micronaut.security.oauth2.configuration.OauthClientConfiguration; import io.micronaut.security.oauth2.endpoint.token.request.TokenEndpointClient; @@ -31,7 +31,7 @@ import io.micronaut.security.oauth2.endpoint.token.response.validation.OpenIdTokenResponseValidator; /** - * Factory creating {@link AuthenticationProvider} beans that delegate + * Factory creating {@link ReactiveAuthenticationProvider} beans that delegate * to the password grant flow of an OAuth 2.0 or OpenID provider. * * @author James Kleeh @@ -61,7 +61,7 @@ class PasswordGrantFactory { */ @EachBean(OauthClientConfiguration.class) @Requires(condition = PasswordGrantCondition.class) - AuthenticationProvider passwordGrantProvider( + ReactiveAuthenticationProvider passwordGrantProvider( @Parameter OauthClientConfiguration clientConfiguration, @Parameter @Nullable OauthAuthenticationMapper authenticationMapper, @Parameter @Nullable OpenIdAuthenticationMapper openIdAuthenticationMapper, diff --git a/security-oauth2/src/test/groovy/io/micronaut/security/oauth2/client/condition/PasswordGrantConditionSpec.groovy b/security-oauth2/src/test/groovy/io/micronaut/security/oauth2/client/condition/PasswordGrantConditionSpec.groovy index c5d07ec838..1b5cb788c7 100644 --- a/security-oauth2/src/test/groovy/io/micronaut/security/oauth2/client/condition/PasswordGrantConditionSpec.groovy +++ b/security-oauth2/src/test/groovy/io/micronaut/security/oauth2/client/condition/PasswordGrantConditionSpec.groovy @@ -6,7 +6,7 @@ import io.micronaut.context.annotation.Requires import io.micronaut.core.annotation.Nullable import io.micronaut.json.JsonMapper import io.micronaut.json.tree.JsonNode -import io.micronaut.security.authentication.AuthenticationProvider +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse import io.micronaut.security.oauth2.configuration.OauthClientConfiguration @@ -64,10 +64,10 @@ class PasswordGrantConditionSpec extends Specification { ApplicationContext ctx = ApplicationContext.run(PROPS + properties) expect: - ctx.containsBean(AuthenticationProvider) + ctx.containsBean(ReactiveAuthenticationProvider) when: - ctx.getBean(AuthenticationProvider) + ctx.getBean(ReactiveAuthenticationProvider) then: noExceptionThrown() diff --git a/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy b/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy index aa6d52e743..b18e0c63aa 100644 --- a/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy +++ b/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy @@ -9,7 +9,7 @@ import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.security.annotation.Secured -import io.micronaut.security.authentication.AuthenticationProvider +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse import io.micronaut.security.rules.SecurityRule @@ -71,7 +71,7 @@ class ContextPathSpec extends EmbeddedServerSpecification { @Requires(property = 'spec.name', value = 'ContextPathSpec') @Singleton - static class MockAuthenticationProvider implements AuthenticationProvider { + static class MockAuthenticationProvider implements ReactiveAuthenticationProvider { @Override Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { diff --git a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java index 355a1c7c75..095d90c4d2 100644 --- a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java @@ -15,8 +15,7 @@ */ package io.micronaut.security.authentication; -import io.micronaut.core.annotation.Nullable; -import org.reactivestreams.Publisher; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; /** * Defines an authentication provider. @@ -25,20 +24,8 @@ * @author Graeme Rocher * @since 1.0 * @param Request + * @deprecated Use {@link io.micronaut.security.authentication.provider.AuthenticationProvider} for an imperative API or {@link ReactiveAuthenticationProvider} for a Reactive API instead. */ -public interface AuthenticationProvider { - - /** - * Authenticates a user with the given request. If a successful authentication is - * returned, the object must be an instance of {@link Authentication}. - * - * Publishers MUST emit cold observables! This method will be called for - * all authenticators for each authentication request and it is assumed no work - * will be done until the publisher is subscribed to. - * - * @param httpRequest The http request - * @param authenticationRequest The credentials to authenticate - * @return A publisher that emits 0 or 1 responses - */ - Publisher authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest); +@Deprecated(forRemoval = true, since = "4.5.0") +public interface AuthenticationProvider extends ReactiveAuthenticationProvider { } diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index ac1aeb545d..f047dfa7c9 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -16,10 +16,15 @@ package io.micronaut.security.authentication; import io.micronaut.core.annotation.NonNull; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.config.AuthenticationStrategy; import io.micronaut.security.config.SecurityConfiguration; +import jakarta.inject.Inject; import jakarta.inject.Singleton; + +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -32,7 +37,7 @@ import reactor.core.publisher.Mono; /** - * An Authenticator operates on several {@link AuthenticationProvider} instances returning the first + * An Authenticator operates on several {@link ReactiveAuthenticationProvider} instances returning the first * authenticated {@link AuthenticationResponse}. * * @author Sergio del Amo @@ -46,15 +51,32 @@ public class Authenticator { private static final Logger LOG = LoggerFactory.getLogger(Authenticator.class); protected final Collection> authenticationProviders; + + protected final List> reactiveAuthenticationProviders; private final SecurityConfiguration securityConfiguration; /** * @param authenticationProviders A list of available authentication providers * @param securityConfiguration The security configuration */ + @Inject + public Authenticator(List> authenticationProviders, + SecurityConfiguration securityConfiguration) { + this.reactiveAuthenticationProviders = authenticationProviders; + this.securityConfiguration = securityConfiguration; + this.authenticationProviders = Collections.emptyList(); + } + + /** + * @param authenticationProviders A list of available authentication providers + * @param securityConfiguration The security configuration + * @deprecated Use {@link Authenticator(List, SecurityConfiguration)} instead. + */ + @Deprecated(forRemoval = true, since = "4.5.0") public Authenticator(Collection> authenticationProviders, SecurityConfiguration securityConfiguration) { this.authenticationProviders = authenticationProviders; + this.reactiveAuthenticationProviders = new ArrayList<>(authenticationProviders); this.securityConfiguration = securityConfiguration; } @@ -66,17 +88,17 @@ public Authenticator(Collection> authenticationProvide * @return A publisher that emits {@link AuthenticationResponse} objects */ public Publisher authenticate(T request, AuthenticationRequest authenticationRequest) { - if (this.authenticationProviders == null) { + if (this.reactiveAuthenticationProviders == null) { return Flux.empty(); } if (LOG.isDebugEnabled()) { - LOG.debug(authenticationProviders.stream().map(AuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); + LOG.debug(reactiveAuthenticationProviders.stream().map(ReactiveAuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); } Flux[] emptyArr = new Flux[0]; if (securityConfiguration != null && securityConfiguration.getAuthenticationProviderStrategy() == AuthenticationStrategy.ALL) { return Flux.mergeDelayError(1, - authenticationProviders.stream() + reactiveAuthenticationProviders.stream() .map(provider -> Flux.from(provider.authenticate(request, authenticationRequest)) .switchMap(this::handleResponse) .switchIfEmpty(Flux.error(() -> new AuthenticationException("Provider did not respond. Authentication rejected")))) @@ -87,7 +109,7 @@ public Publisher authenticate(T request, AuthenticationR .flux(); } else { AtomicReference lastError = new AtomicReference<>(); - Flux authentication = Flux.mergeDelayError(1, authenticationProviders.stream() + Flux authentication = Flux.mergeDelayError(1, reactiveAuthenticationProviders.stream() .map(auth -> auth.authenticate(request, authenticationRequest)) .map(Flux::from) .map(sequence -> sequence.switchMap(this::handleResponse).onErrorResume(t -> { diff --git a/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java b/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java index 613bb94060..16fb7d02cf 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java +++ b/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java @@ -30,7 +30,7 @@ /** * An implementation of {@link AuthenticationFetcher} that decodes a username * and password from the Authorization header and authenticates the credentials - * against any {@link AuthenticationProvider}s available. + * against any {@link io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider}s available. */ @Requires(classes = HttpRequest.class) @Requires(property = BasicAuthAuthenticationConfiguration.PREFIX + ".enabled", notEquals = StringUtils.FALSE) diff --git a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java similarity index 74% rename from security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProvider.java rename to security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index 5bd8572bc6..3194f0fc6e 100644 --- a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -13,23 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.security.authentication; +package io.micronaut.security.authentication.provider; import io.micronaut.context.annotation.Executable; import io.micronaut.core.annotation.Blocking; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.naming.Named; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; /** * Defines an Authentication Provider with an imperative style. * @since 4.5.0 * @param Request */ -public interface ImperativeAuthenticationProvider extends Named { +public interface AuthenticationProvider extends Named { /** - * Authenticates a user with the given request. If a successful authentication is returned, the object must be an instance of {@link Authentication}. + * Authenticates a user with the given request. + * If authenticated successfully return {@link AuthenticationResponse#success(String)}. + * If not authenticated return {@link AuthenticationResponse#failure()}. * If your implementation is blocking, annotate the overriden method with {@link Blocking} and it will be safely executed on a * dedicated thread in order to not block the main reactive chain of execution. * diff --git a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java similarity index 59% rename from security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java rename to security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java index a91d7a6060..c345617d6a 100644 --- a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderFactory.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.security.authentication; +package io.micronaut.security.authentication.provider; import io.micronaut.context.BeanContext; import io.micronaut.context.annotation.EachBean; @@ -22,6 +22,8 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; import jakarta.inject.Named; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -31,58 +33,58 @@ import java.util.concurrent.ExecutorService; /** - * A factory for adapting {@link ImperativeAuthenticationProvider} beans to expose them as {@link AuthenticationProvider}. + * A factory for adapting {@link AuthenticationProvider} beans to expose them as {@link ReactiveAuthenticationProvider}. * * @since 4.5.0 */ @Factory @Internal -class ImperativeAuthenticationProviderFactory { +class AuthenticationProviderFactory { private final Scheduler scheduler; private final BeanContext beanContext; - ImperativeAuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService, + AuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService, BeanContext beanContext) { this.scheduler = Schedulers.fromExecutorService(executorService); this.beanContext = beanContext; } /** - * Creates an adapted {@link AuthenticationProvider} for each provided instance of {@link ImperativeAuthenticationProvider}. + * Creates an adapted {@link AuthenticationProvider} for each provided instance of {@link AuthenticationProvider}. * *

- * NOTE - If there are multiple instances of {@link ImperativeAuthenticationProvider} in the application context, then they + * NOTE - If there are multiple instances of {@link AuthenticationProvider} in the application context, then they * must be annotated with a {@link jakarta.inject.Qualifier} such as {@link Named}. *

* - * @param imperativeAuthenticationProvider An instance of {@link ImperativeAuthenticationProvider} to be adapted + * @param authenticationProvider An instance of {@link AuthenticationProvider} to be adapted * @return An {@link AuthenticationProvider} adapted from the blocking provider * @param The request type */ - @EachBean(ImperativeAuthenticationProvider.class) - AuthenticationProvider createAuthenticationProvider(ImperativeAuthenticationProvider imperativeAuthenticationProvider) { - return new ImperativeAuthenticationProviderAdapter<>(imperativeAuthenticationProvider, - ImperativeAuthenticationProviderUtils.isAuthenticateBlocking(beanContext, imperativeAuthenticationProvider) ? scheduler : null); + @EachBean(AuthenticationProvider.class) + ReactiveAuthenticationProvider createAuthenticationProvider(AuthenticationProvider authenticationProvider) { + return new AuthenticationProviderAdapter<>(authenticationProvider, + AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, authenticationProvider) ? scheduler : null); } - private static final class ImperativeAuthenticationProviderAdapter implements AuthenticationProvider { + private static final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { @NonNull - private final ImperativeAuthenticationProvider imperativeAuthenticationProvider; + private final AuthenticationProvider authenticationProvider; @Nullable private final Scheduler scheduler; - private ImperativeAuthenticationProviderAdapter(@NonNull ImperativeAuthenticationProvider imperativeAuthenticationProvider, + private AuthenticationProviderAdapter(@NonNull AuthenticationProvider authenticationProvider, @Nullable Scheduler scheduler) { - this.imperativeAuthenticationProvider = imperativeAuthenticationProvider; + this.authenticationProvider = authenticationProvider; this.scheduler = scheduler; } @Override public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - Mono authenticationResponseMono = Mono.fromCallable(() -> imperativeAuthenticationProvider.authenticate(httpRequest, authenticationRequest)); + Mono authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(httpRequest, authenticationRequest)); return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono; } } diff --git a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java similarity index 82% rename from security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java rename to security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java index a2e236af35..c42926062a 100644 --- a/security/src/main/java/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtils.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.security.authentication; +package io.micronaut.security.authentication.provider; import io.micronaut.context.BeanContext; import io.micronaut.context.BeanRegistration; @@ -23,22 +23,23 @@ import io.micronaut.http.HttpRequest; import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.ExecutableMethod; +import io.micronaut.security.authentication.AuthenticationRequest; import java.lang.annotation.Annotation; import java.util.Optional; /** - * Utility class to check whether {@link ImperativeAuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}. + * Utility class to check whether {@link AuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}. */ @Internal -public final class ImperativeAuthenticationProviderUtils { +public final class AuthenticationProviderUtils { private static final String METHOD_AUTHENTICATE = "authenticate"; - private ImperativeAuthenticationProviderUtils() { + private AuthenticationProviderUtils() { } public static boolean isAuthenticateBlocking(BeanContext beanContext, - @NonNull ImperativeAuthenticationProvider authenticationProvider) { + @NonNull AuthenticationProvider authenticationProvider) { if (isMethodBlocking(beanContext, authenticationProvider, METHOD_AUTHENTICATE, Object.class, AuthenticationRequest.class)) { return true; } @@ -55,7 +56,7 @@ private static boolean isMethodBlocking(BeanContext beanContext, } BeanDefinition beanDefinition = beanDefinitionOptional.get(); Optional> methodOptional = beanDefinition.findMethod(methodName, argumentTypes); - return methodOptional.filter(ImperativeAuthenticationProviderUtils::isBlockingMethod).isPresent(); + return methodOptional.filter(AuthenticationProviderUtils::isBlockingMethod).isPresent(); } private static boolean isBlockingMethod(ExecutableMethod executableMethod) { diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java new file mode 100644 index 0000000000..39bff8ad0a --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright 2017-2023 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.authentication.provider; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.async.annotation.SingleResult; +import io.micronaut.security.authentication.Authentication; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import org.reactivestreams.Publisher; + +/** + * Defines a Reactive authentication provider. + * + * @since 4.5.0 + * @param Request + */ +public interface ReactiveAuthenticationProvider { + + /** + * Authenticates a user with the given request. If a successful authentication is + * returned, the object must be an instance of {@link Authentication}. + * + * Publishers MUST emit cold observables! This method will be called for + * all authenticators for each authentication request and it is assumed no work + * will be done until the publisher is subscribed to. + * + * @param httpRequest The http request + * @param authenticationRequest The credentials to authenticate + * @return A publisher that emits 0 or 1 responses + */ + @SingleResult + Publisher authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest); +} diff --git a/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy index 9204ee8b53..e08b7f98ee 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy @@ -1,7 +1,7 @@ package io.micronaut.docs.security.authentication import io.micronaut.context.annotation.Requires -import io.micronaut.security.authentication.AuthenticationProvider +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse import jakarta.inject.Singleton @@ -14,7 +14,7 @@ import reactor.core.publisher.FluxSink @Requires(property = "spec.name", value = "authenticationparam") //tag::clazz[] @Singleton -class AuthenticationProviderUserPassword implements AuthenticationProvider { +class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { @Override diff --git a/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy index 0560edd10d..58bbe9b73b 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy @@ -1,7 +1,7 @@ package io.micronaut.docs.security.principalparam import io.micronaut.context.annotation.Requires -import io.micronaut.security.authentication.AuthenticationProvider +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse import jakarta.inject.Singleton @@ -13,7 +13,7 @@ import reactor.core.publisher.FluxSink @Requires(property = "spec.name", value = "principalparam") //tag::clazz[] @Singleton -class AuthenticationProviderUserPassword implements AuthenticationProvider { +class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { @Override Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java index cb95a52cb3..a207dcaa65 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java @@ -2,7 +2,7 @@ //tag::clazz[] import io.micronaut.context.annotation.Requires; -import io.micronaut.security.authentication.AuthenticationProvider; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import jakarta.inject.Singleton; @@ -13,7 +13,7 @@ @Requires(property = "spec.name", value = "docsbasicauth") //tag::clazz[] @Singleton -public class AuthenticationProviderUserPassword implements AuthenticationProvider { +public class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { @Override public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java index 7cdb65d2bf..fd3900dbb5 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java @@ -6,7 +6,7 @@ import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.ImperativeAuthenticationProvider; +import io.micronaut.security.authentication.provider.AuthenticationProvider; import jakarta.inject.Named; import jakarta.inject.Singleton; @@ -15,7 +15,7 @@ //tag::clazz[] @Named(ImperativeAuthenticationProviderUserPassword.NAME) @Singleton -public class ImperativeAuthenticationProviderUserPassword implements ImperativeAuthenticationProvider { +public class ImperativeAuthenticationProviderUserPassword implements AuthenticationProvider { public static final String NAME = "foo"; @Override public AuthenticationResponse authenticate(T httpRequest, diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorSpec.groovy index 7784b58c9b..effda03654 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorSpec.groovy @@ -1,6 +1,7 @@ package io.micronaut.security.authentication import groovy.transform.AutoImplement +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import io.micronaut.security.config.AuthenticationStrategy import io.micronaut.security.config.SecurityConfiguration import io.micronaut.security.config.SecurityConfigurationProperties @@ -37,10 +38,10 @@ class AuthenticatorSpec extends Specification { void "if any authentication provider throws exception, continue with authentication"() { given: - def authProviderExceptionRaiser = Stub(AuthenticationProvider) { + def authProviderExceptionRaiser = Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> { Flux.error( new Exception('Authentication provider raised exception') ) } } - def authProviderOK = Stub(AuthenticationProvider) { + def authProviderOK = Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({emitter -> emitter.next(AuthenticationResponse.success("admin")) emitter.complete() @@ -59,7 +60,7 @@ class AuthenticatorSpec extends Specification { void "if no authentication provider can authentication, the last error is sent back"() { given: - def authProviderFailed = Stub(AuthenticationProvider) { + def authProviderFailed = Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.error(AuthenticationResponse.exception()) }, FluxSink.OverflowStrategy.ERROR) @@ -77,15 +78,15 @@ class AuthenticatorSpec extends Specification { void "test authentication strategy all with error and empty"() { given: def providers = [ - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.error(AuthenticationResponse.exception("failed")) }, FluxSink.OverflowStrategy.ERROR) }, - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.empty() }, - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.next(AuthenticationResponse.success("a")) emitter.complete() @@ -106,12 +107,12 @@ class AuthenticatorSpec extends Specification { void "test authentication strategy all with error"() { given: def providers = [ - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.error(AuthenticationResponse.exception("failed")) }, FluxSink.OverflowStrategy.ERROR) }, - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.next(AuthenticationResponse.success("a")) emitter.complete() @@ -132,13 +133,13 @@ class AuthenticatorSpec extends Specification { void "test authentication strategy success first"() { given: def providers = [ - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.next(AuthenticationResponse.success("a")) emitter.complete() }, FluxSink.OverflowStrategy.ERROR) }, - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.error(AuthenticationResponse.exception("failed")) }, FluxSink.OverflowStrategy.ERROR) @@ -158,13 +159,13 @@ class AuthenticatorSpec extends Specification { void "test authentication strategy multiple successes"() { given: def providers = [ - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.next(AuthenticationResponse.success("a")) emitter.complete() }, FluxSink.OverflowStrategy.ERROR) }, - Stub(AuthenticationProvider) { + Stub(ReactiveAuthenticationProvider) { authenticate(_, _) >> Flux.create({ emitter -> emitter.next(AuthenticationResponse.success("b")) emitter.complete() diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy index 6841c770ae..44d7c3d79c 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy @@ -7,29 +7,30 @@ import io.micronaut.http.HttpMethod import io.micronaut.http.HttpRequest import io.micronaut.scheduling.LoomSupport import io.micronaut.scheduling.TaskExecutors +import io.micronaut.security.authentication.provider.AuthenticationProvider import io.micronaut.security.testutils.ApplicationContextSpecification import jakarta.inject.Named import jakarta.inject.Singleton import reactor.core.publisher.Mono -class ImperativeAuthenticationProviderSpec extends ApplicationContextSpecification { +class AuthenticationProviderSpec extends ApplicationContextSpecification { static final String EXECUTOR_NAME_MATCH = "%s-executor".formatted(LoomSupport.supported ? TaskExecutors.VIRTUAL : TaskExecutors.IO) - SimpleImperativeAuthenticationProvider provider + SimpleAuthenticationProvider provider def setup() { - provider = getBean(SimpleImperativeAuthenticationProvider.class) + provider = getBean(SimpleAuthenticationProvider.class) provider.executedThreadName = "" } - def "multiple ImperativeAuthenticationProvider implementations are registered"() { + def "multiple AuthenticationProvider implementations are registered"() { given: BasicAuthAuthenticationFetcher authFetcher = getBean(BasicAuthAuthenticationFetcher.class) expect: authFetcher - getApplicationContext().getBeanRegistrations(ImperativeAuthenticationProvider.class).size() == 2 + getApplicationContext().getBeanRegistrations(AuthenticationProvider.class).size() == 2 } def "a blocking authentication provider can authenticate successfully"() { @@ -59,14 +60,14 @@ class ImperativeAuthenticationProviderSpec extends ApplicationContextSpecificati @Override String getSpecName() { - return "ImperativeAuthenticationProviderSpec" + return "AuthenticationProviderSpec" } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderSpec") + @Requires(property = "spec.name", value = "AuthenticationProviderSpec") @Singleton - @Named(SimpleImperativeAuthenticationProvider.NAME) - static class SimpleImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { - static final String NAME = "SimpleImperativeAuthenticationProvider" + @Named(SimpleAuthenticationProvider.NAME) + static class SimpleAuthenticationProvider implements AuthenticationProvider { + static final String NAME = "SimpleAuthenticationProvider" private String executedThreadName @@ -83,15 +84,15 @@ class ImperativeAuthenticationProviderSpec extends ApplicationContextSpecificati @Override String getName() { - SimpleImperativeAuthenticationProvider.NAME + SimpleAuthenticationProvider.NAME } } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderSpec") + @Requires(property = "spec.name", value = "AuthenticationProviderSpec") @Singleton - @Named(NoOpImperativeAuthenticationProvider.NAME) - static class NoOpImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { - static final String NAME = "NoOpImperativeAuthenticationProvider" + @Named(NoOpAuthenticationProvider.NAME) + static class NoOpAuthenticationProvider implements AuthenticationProvider { + static final String NAME = "NoOpAuthenticationProvider" @Override AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { @@ -100,7 +101,7 @@ class ImperativeAuthenticationProviderSpec extends ApplicationContextSpecificati @Override String getName() { - NoOpImperativeAuthenticationProvider.NAME + NoOpAuthenticationProvider.NAME } } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy index ab0f61291d..ff05cad669 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy @@ -7,37 +7,39 @@ import io.micronaut.core.annotation.Blocking import io.micronaut.core.annotation.NonNull import io.micronaut.core.annotation.Nullable import io.micronaut.http.HttpRequest +import io.micronaut.security.authentication.provider.AuthenticationProvider +import io.micronaut.security.authentication.provider.AuthenticationProviderUtils import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import jakarta.inject.Named import jakarta.inject.Singleton import spock.lang.Specification -@Property(name = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") +@Property(name = "spec.name", value = "AuthenticationProviderUtilsTest") @MicronautTest(startApplication = false) -class ImperativeAuthenticationProviderUtilsTest extends Specification { +class AuthenticationProviderUtilsTest extends Specification { @Inject BeanContext beanContext void "#clazz authenticate method is #description"(boolean isBlocking, - Class clazz, + Class clazz, String description) { expect: - isBlocking == ImperativeAuthenticationProviderUtils.isAuthenticateBlocking(beanContext, beanContext.getBean(clazz)) + isBlocking == AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, beanContext.getBean(clazz)) where: isBlocking | clazz - true | BlockingImperativeAuthenticationProvider.class - true | BlockingWithGenericImperativeAuthenticationProvider - false | NonBlockingImperativeAuthenticationProvider.class + true | BlockingAuthenticationProvider.class + true | BlockingWithGenericAuthenticationProvider + false | NonBlockingAuthenticationProvider.class description = isBlocking ? "is annotated with @Blocking" : "is not annotated with @Blocking" } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") + @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton @Named("foo") - static class BlockingImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + static class BlockingAuthenticationProvider implements AuthenticationProvider { @Override @Blocking AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { @@ -50,10 +52,10 @@ class ImperativeAuthenticationProviderUtilsTest extends Specification { } } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") + @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton @Named("foo") - static class BlockingWithGenericImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + static class BlockingWithGenericAuthenticationProvider implements AuthenticationProvider { @Override @Blocking AuthenticationResponse authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authRequest) { @@ -66,10 +68,10 @@ class ImperativeAuthenticationProviderUtilsTest extends Specification { } } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderUtilsTest") + @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton @Named("bar") - static class NonBlockingImperativeAuthenticationProvider implements ImperativeAuthenticationProvider { + static class NonBlockingAuthenticationProvider implements AuthenticationProvider { @Override AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() diff --git a/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java b/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java index a7f7ed820a..2aada2b902 100644 --- a/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java +++ b/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java @@ -15,7 +15,7 @@ */ package io.micronaut.security; -import io.micronaut.security.authentication.AuthenticationProvider; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import java.util.Collections; @@ -28,7 +28,7 @@ /** * Utility class to mock authentication scenarios. */ -public class MockAuthenticationProvider implements AuthenticationProvider { +public class MockAuthenticationProvider implements ReactiveAuthenticationProvider { private final List successAuthenticationScenarioList; private final List failedAuthenticationScenarios; diff --git a/src/main/docs/guide/authenticationProviders.adoc b/src/main/docs/guide/authenticationProviders.adoc index d3d3f94387..ab6b253f47 100644 --- a/src/main/docs/guide/authenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders.adoc @@ -1,8 +1,5 @@ -To authenticate users you must provide implementations of api:security.authentication.AuthenticationProvider[]. +To authenticate users you must provide implementations of api:security.authentication.provider.ReactiveAuthenticationProvider[]. The following code snippet illustrates a naive implementation: -[source, java] ----- -include::{testssecurity}/security/token/basicauth/AuthenticationProviderUserPassword.java[tag=clazz,indent=0] ----- +snippet::io.micronaut.security.docs.reactiveauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] diff --git a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc index 35acb18119..16d8306adb 100644 --- a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc @@ -1,4 +1,4 @@ -The main api:security.authentication.AuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.ImperativeAuthenticationProvider[] interface: +The api:security.authentication.provider.ReactiveAuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.ImperativeAuthenticationProvider[] interface: snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index c5d2820607..9f0faa79c9 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -15,8 +15,8 @@ securityConfiguration: noRedirection: Handlers without redirection redirection: Redirection Configuration authenticationProviders: - title: Authentication Providers - imperativeAuthenticationProviders: ImperativeAuthenticationProvider + title: Reactive Authentication Providers + imperativeAuthenticationProviders: Authentication Provider authenticationProvidersUseCases: Authentication Providers Use Cases builtInAuthenticationProviders: Built-In Authentication Providers securityRule: diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.groovy similarity index 88% rename from test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.groovy rename to test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.groovy index 14d7914191..1957fc9e8b 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.groovy @@ -16,9 +16,9 @@ import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import spock.lang.Specification -@Property(name = "spec.name", value = "ImperativeAuthenticationProviderTest") +@Property(name = "spec.name", value = "AuthenticationProviderTest") @MicronautTest -class ImperativeAuthenticationProviderTest extends Specification { +class AuthenticationProviderTest extends Specification { @Inject @Client("/") @@ -48,7 +48,7 @@ class ImperativeAuthenticationProviderTest extends Specification { return HttpRequest.GET("/messages").basicAuth(userName, password) } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") + @Requires(property = "spec.name", value = "AuthenticationProviderTest") @Controller("/messages") static class HelloWorldController { diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy index d70c0a90cf..a1c3797866 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy @@ -6,21 +6,23 @@ import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse -import io.micronaut.security.authentication.ImperativeAuthenticationProvider +import io.micronaut.security.authentication.provider.AuthenticationProvider import jakarta.inject.Named +import jakarta.inject.Singleton -@Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") +@Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] @Named(CustomAuthenticationProvider.NAME) -class CustomAuthenticationProvider implements ImperativeAuthenticationProvider> { +@Singleton +class CustomAuthenticationProvider implements AuthenticationProvider> { static final String NAME = "foo" @Override AuthenticationResponse authenticate(HttpRequest httpRequest, AuthenticationRequest authenticationRequest) { - (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") ? - AuthenticationResponse.success("user") : - AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") + ? AuthenticationResponse.success("user") + : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) } @Override diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy new file mode 100644 index 0000000000..f14edda5e5 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy @@ -0,0 +1,30 @@ +package io.micronaut.security.docs.reactiveauthenticationprovider; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.async.annotation.SingleResult; +import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.AuthenticationFailureReason; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; +import jakarta.inject.Singleton; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +@Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") +//tag::clazz[] +@Singleton +class CustomAuthenticationProvider implements ReactiveAuthenticationProvider> { + @Override + @SingleResult + Publisher authenticate(HttpRequest httpRequest, + AuthenticationRequest authRequest) { + AuthenticationResponse rsp = (authRequest.identity == "user" && authRequest.secret == "password") + ? AuthenticationResponse.success("user") + : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + return Mono.create(emitter -> { + emitter.success(rsp) + }) + } +} +//end::clazz[] diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.groovy new file mode 100644 index 0000000000..91cf2692c6 --- /dev/null +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.groovy @@ -0,0 +1,60 @@ +package io.micronaut.security.docs.reactiveauthenticationprovider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@Property(name = "spec.name", value = "ReactiveAuthenticationProviderTest") +@MicronautTest +class ReactiveAuthenticationProviderTest extends Specification { + @Inject + @Client("/") + HttpClient httpClient + + void authProvider() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("user", "password")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("user", "wrong")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.getStatus() + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } +} diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.kt similarity index 88% rename from test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.kt rename to test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.kt index 1cb12f579d..fda2c5abdf 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.kt @@ -15,9 +15,9 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -@Property(name = "spec.name", value = "ImperativeAuthenticationProviderTest") +@Property(name = "spec.name", value = "AuthenticationProviderTest") @MicronautTest -internal class ImperativeAuthenticationProviderTest { +internal class AuthenticationProviderTest { @Test fun blockingAuthProvider(@Client("/") httpClient: HttpClient) { val client = httpClient.toBlocking() @@ -36,7 +36,7 @@ internal class ImperativeAuthenticationProviderTest { return HttpRequest.GET("/messages").basicAuth(userName, password) } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") + @Requires(property = "spec.name", value = "AuthenticationProviderTest") @Controller("/messages") internal class HelloWorldController { @Secured(SecurityRule.IS_AUTHENTICATED) diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt index 8c5e1606e6..5234fec885 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt @@ -5,14 +5,16 @@ import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse -import io.micronaut.security.authentication.ImperativeAuthenticationProvider +import io.micronaut.security.authentication.provider.AuthenticationProvider import jakarta.inject.Named +import jakarta.inject.Singleton -@Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") +@Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] @Named(CustomAuthenticationProvider.NAME) +@Singleton class CustomAuthenticationProvider : - ImperativeAuthenticationProvider> { + AuthenticationProvider> { override fun authenticate( httpRequest: HttpRequest<*>, authenticationRequest: AuthenticationRequest<*, *> diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt new file mode 100644 index 0000000000..33e5e344a1 --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt @@ -0,0 +1,29 @@ +package io.micronaut.security.docs.reactiveauthenticationprovider + +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.security.authentication.AuthenticationFailureReason +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider +import jakarta.inject.Singleton +import org.reactivestreams.Publisher +import reactor.core.publisher.Mono + +@Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") +//tag::clazz[] +@Singleton +class CustomAuthenticationProvider : + ReactiveAuthenticationProvider> { + override fun authenticate( + httpRequest: HttpRequest<*>?, + authenticationRequest: AuthenticationRequest<*, *> + ): Publisher { + val rsp = if (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") + AuthenticationResponse.success("user") + else AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + return Mono.create { emitter -> emitter.success(rsp) } + } + +} +//end::clazz[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.kt new file mode 100644 index 0000000000..2e6e5b4897 --- /dev/null +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.kt @@ -0,0 +1,46 @@ +package io.micronaut.security.docs.reactiveauthenticationprovider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.junit5.annotation.MicronautTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +@Property(name = "spec.name", value = "ReactiveAuthenticationProviderTest") +@MicronautTest +internal class ReactiveAuthenticationProviderTest { + @Test + fun blockingAuthProvider(@Client("/") httpClient: HttpClient) { + val client = httpClient.toBlocking() + val json = Assertions.assertDoesNotThrow { + client.retrieve(createRequest("user", "password")) + } + val expected = """{"message":"Hello World"}""" + Assertions.assertEquals(expected, json) + val ex = Assertions.assertThrows(HttpClientResponseException::class.java) { + client.retrieve(createRequest("user", "wrong")) + } + Assertions.assertEquals(HttpStatus.UNAUTHORIZED, ex.status) + } + + private fun createRequest(userName: String, password: String): HttpRequest<*> { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") + @Controller("/messages") + internal class HelloWorldController { + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + fun index(): Map = mapOf("message" to "Hello World") + } +} diff --git a/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java b/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java index d5c3e38d9a..f5b9fc3640 100644 --- a/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java +++ b/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java @@ -15,7 +15,7 @@ */ package io.micronaut.security.testutils.authprovider; -import io.micronaut.security.authentication.AuthenticationProvider; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; @@ -29,7 +29,7 @@ /** * Utility class to mock authentication scenarios. */ -public class MockAuthenticationProvider implements AuthenticationProvider { +public class MockAuthenticationProvider implements ReactiveAuthenticationProvider { private final List successAuthenticationScenarioList; private final List failedAuthenticationScenarios; diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java similarity index 89% rename from test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.java rename to test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java index 11f1f97554..b59c2b1600 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/ImperativeAuthenticationProviderTest.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java @@ -20,9 +20,9 @@ import static org.junit.jupiter.api.Assertions.*; -@Property(name = "spec.name", value = "ImperativeAuthenticationProviderTest") +@Property(name = "spec.name", value = "AuthenticationProviderTest") @MicronautTest -class ImperativeAuthenticationProviderTest { +class AuthenticationProviderTest { @Test void authProvider(@Client("/") HttpClient httpClient) { @@ -39,7 +39,7 @@ private HttpRequest createRequest(String userName, String password) { return HttpRequest.GET("/messages").basicAuth(userName, password); } - @Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") + @Requires(property = "spec.name", value = "AuthenticationProviderTest") @Controller("/messages") static class HelloWorldController { diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java index dae36ad408..f546832ed1 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java @@ -6,13 +6,15 @@ import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.ImperativeAuthenticationProvider; +import io.micronaut.security.authentication.provider.AuthenticationProvider; import jakarta.inject.Named; +import jakarta.inject.Singleton; -@Requires(property = "spec.name", value = "ImperativeAuthenticationProviderTest") +@Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] @Named(CustomAuthenticationProvider.NAME) -class CustomAuthenticationProvider implements ImperativeAuthenticationProvider> { +@Singleton +class CustomAuthenticationProvider implements AuthenticationProvider> { static final String NAME = "foo"; @Override diff --git a/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java new file mode 100644 index 0000000000..98e7f052f5 --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java @@ -0,0 +1,30 @@ +package io.micronaut.security.docs.reactiveauthenticationprovider; + +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.async.annotation.SingleResult; +import io.micronaut.http.HttpRequest; +import io.micronaut.security.authentication.AuthenticationFailureReason; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; +import jakarta.inject.Singleton; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +@Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") +//tag::clazz[] +@Singleton +class CustomAuthenticationProvider implements ReactiveAuthenticationProvider> { + @Override + @SingleResult + public Publisher authenticate(HttpRequest httpRequest, + AuthenticationRequest authRequest) { + AuthenticationResponse rsp = authRequest.getIdentity().equals("user") && authRequest.getSecret().equals("password") + ? AuthenticationResponse.success("user") + : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); + return Mono.create(emitter -> { + emitter.success(rsp); + }); + } +} +//end::clazz[] diff --git a/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java new file mode 100644 index 0000000000..1af5d9b44b --- /dev/null +++ b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java @@ -0,0 +1,52 @@ +package io.micronaut.security.docs.reactiveauthenticationprovider; + +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@Property(name = "spec.name", value = "ReactiveAuthenticationProviderTest") +@MicronautTest +class ReactiveAuthenticationProviderTest { + + @Test + void authProvider(@Client("/") HttpClient httpClient) { + BlockingHttpClient client = httpClient.toBlocking(); + String json = assertDoesNotThrow(() -> client.retrieve(createRequest("user", "password"))); + String expected = """ + {"message":"Hello World"}"""; + assertEquals(expected, json); + HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.retrieve(createRequest("user", "wrong"))); + assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatus()); + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password); + } + + @Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + return Collections.singletonMap("message", "Hello World"); + } + } +} From 4b689cb189cfbe660d964dc50e8a39b8a2de9840 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 16:42:31 +0100 Subject: [PATCH 12/37] =?UTF-8?q?Don=E2=80=99t=20wrap=20create=20ReactiveA?= =?UTF-8?q?uthenticationProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only create ReactiveAuthenticationProvider from imperative if there is any ReactiveAuthenticationProvider or if there is a blocking imperative AuthenticationProvider --- .../authentication/Authenticator.java | 163 +++++++++++++++--- .../provider/AuthenticationProvider.java | 6 +- .../AuthenticationProviderAdapter.java | 69 ++++++++ .../AuthenticationProviderFactory.java | 91 ---------- .../ReactiveAuthenticationProvider.java | 3 +- .../AuthenticatorAllImperativeSpec.groovy | 115 ++++++++++++ .../AuthenticatorImperativeSpec.groovy | 107 ++++++++++++ 7 files changed, 431 insertions(+), 123 deletions(-) create mode 100644 security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java delete mode 100644 security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java create mode 100644 security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy create mode 100644 security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index f047dfa7c9..4d7b6a37a1 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -15,19 +15,21 @@ */ package io.micronaut.security.authentication; +import io.micronaut.context.BeanContext; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.order.OrderUtil; +import io.micronaut.core.util.CollectionUtils; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.security.authentication.provider.AuthenticationProvider; +import io.micronaut.security.authentication.provider.AuthenticationProviderAdapter; +import io.micronaut.security.authentication.provider.AuthenticationProviderUtils; import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.config.AuthenticationStrategy; import io.micronaut.security.config.SecurityConfiguration; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.inject.Singleton; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +37,16 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.FluxSink; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; /** * An Authenticator operates on several {@link ReactiveAuthenticationProvider} instances returning the first @@ -50,72 +62,156 @@ public class Authenticator { private static final Logger LOG = LoggerFactory.getLogger(Authenticator.class); - protected final Collection> authenticationProviders; + /** + * + * @deprecated Unused. To be removed in the next major version. + */ + @Deprecated(forRemoval = true, since = "4.5.0") + protected final Collection> authenticationProviders; + + private final List> reactiveAuthenticationProviders; - protected final List> reactiveAuthenticationProviders; + private final BeanContext beanContext; + private final List> imperativeAuthenticationProviders; private final SecurityConfiguration securityConfiguration; + private final Scheduler scheduler; + /** - * @param authenticationProviders A list of available authentication providers + * @param beanContext Bean Context + * @param reactiveAuthenticationProviders A list of available Reactive authentication providers + * @param authenticationProviders A list of available imperative authentication providers + * @param executorService BLOCKING executor service * @param securityConfiguration The security configuration */ @Inject - public Authenticator(List> authenticationProviders, + public Authenticator(BeanContext beanContext, + List> reactiveAuthenticationProviders, + List> authenticationProviders, + @Named(TaskExecutors.BLOCKING) ExecutorService executorService, SecurityConfiguration securityConfiguration) { - this.reactiveAuthenticationProviders = authenticationProviders; + this.beanContext = beanContext; + this.reactiveAuthenticationProviders = reactiveAuthenticationProviders; this.securityConfiguration = securityConfiguration; + this.imperativeAuthenticationProviders = authenticationProviders; + this.scheduler = Schedulers.fromExecutorService(executorService); this.authenticationProviders = Collections.emptyList(); } /** * @param authenticationProviders A list of available authentication providers - * @param securityConfiguration The security configuration + * @param securityConfiguration The security configuration * @deprecated Use {@link Authenticator(List, SecurityConfiguration)} instead. */ @Deprecated(forRemoval = true, since = "4.5.0") - public Authenticator(Collection> authenticationProviders, + public Authenticator(Collection> authenticationProviders, SecurityConfiguration securityConfiguration) { + this.beanContext = null; this.authenticationProviders = authenticationProviders; this.reactiveAuthenticationProviders = new ArrayList<>(authenticationProviders); this.securityConfiguration = securityConfiguration; + this.scheduler = null; + this.imperativeAuthenticationProviders = Collections.emptyList(); } /** * Authenticates the user with the provided credentials. * - * @param request The HTTP request + * @param httpRequest The HTTP request * @param authenticationRequest Represents a request to authenticate. * @return A publisher that emits {@link AuthenticationResponse} objects */ - public Publisher authenticate(T request, AuthenticationRequest authenticationRequest) { - if (this.reactiveAuthenticationProviders == null) { + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + if (CollectionUtils.isEmpty(reactiveAuthenticationProviders) && CollectionUtils.isEmpty(imperativeAuthenticationProviders)) { + return Mono.empty(); + } + if (LOG.isDebugEnabled() && imperativeAuthenticationProviders != null) { + LOG.debug(imperativeAuthenticationProviders.stream().map(AuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); + } + if (LOG.isDebugEnabled() && reactiveAuthenticationProviders != null) { + LOG.debug(reactiveAuthenticationProviders.stream().map(ReactiveAuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); + } + if (CollectionUtils.isEmpty(reactiveAuthenticationProviders) && imperativeAuthenticationProviders != null && !anyImperativeAuthenticationProviderIsBlocking()) { + return handleResponse(authenticate(httpRequest, authenticationRequest, imperativeAuthenticationProviders, securityConfiguration)); + } + return authenticate(httpRequest, authenticationRequest, everyProviderSorted()); + } + + private boolean anyImperativeAuthenticationProviderIsBlocking() { + return imperativeAuthenticationProviders.stream().anyMatch(provider -> AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, provider)); + } + + @NonNull + private static AuthenticationResponse authenticate(@NonNull T httpRequest, + @NonNull AuthenticationRequest authenticationRequest, + @NonNull List> authenticationProviders, + @Nullable SecurityConfiguration securityConfiguration) { + if (securityConfiguration != null && securityConfiguration.getAuthenticationProviderStrategy() == AuthenticationStrategy.ALL) { + return authenticateAll(httpRequest, authenticationRequest, authenticationProviders); + } + return authenticationProviders.stream() + .map(provider -> authenticationResponse(provider, httpRequest, authenticationRequest)) + .filter(AuthenticationResponse::isAuthenticated) + .findFirst() + .orElseGet(AuthenticationResponse::failure); + } + + @NonNull + private static AuthenticationResponse authenticateAll(@NonNull T httpRequest, + @NonNull AuthenticationRequest authenticationRequest, + @NonNull List> authenticationProviders) { + List authenticationResponses = authenticationProviders.stream() + .map(provider -> authenticationResponse(provider, httpRequest, authenticationRequest)) + .toList(); + if (CollectionUtils.isEmpty(authenticationResponses)) { + return AuthenticationResponse.failure(); + } + return authenticationResponses.stream().allMatch(AuthenticationResponse::isAuthenticated) + ? authenticationResponses.get(0) + : AuthenticationResponse.failure(); + } + + private List> everyProviderSorted() { + List> providers = new ArrayList<>(reactiveAuthenticationProviders); + if (beanContext != null) { + providers.addAll(imperativeAuthenticationProviders.stream() + .map(imperativeAuthenticationProvider -> new AuthenticationProviderAdapter<>(beanContext, scheduler, imperativeAuthenticationProvider)).toList()); + } + OrderUtil.sort(providers); + return providers; + } + + private Publisher authenticate(T request, + AuthenticationRequest authenticationRequest, + List> providers) { + if (providers == null) { return Flux.empty(); } if (LOG.isDebugEnabled()) { - LOG.debug(reactiveAuthenticationProviders.stream().map(ReactiveAuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); + LOG.debug(providers.stream().map(ReactiveAuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); } Flux[] emptyArr = new Flux[0]; if (securityConfiguration != null && securityConfiguration.getAuthenticationProviderStrategy() == AuthenticationStrategy.ALL) { return Flux.mergeDelayError(1, - reactiveAuthenticationProviders.stream() + providers.stream() .map(provider -> Flux.from(provider.authenticate(request, authenticationRequest)) - .switchMap(this::handleResponse) + .switchMap(Authenticator::handleResponse) .switchIfEmpty(Flux.error(() -> new AuthenticationException("Provider did not respond. Authentication rejected")))) - .collect(Collectors.toList()) + .toList() .toArray(emptyArr)) .last() .onErrorResume(t -> Mono.just(authenticationResponseForThrowable(t))) .flux(); } else { AtomicReference lastError = new AtomicReference<>(); - Flux authentication = Flux.mergeDelayError(1, reactiveAuthenticationProviders.stream() + Flux authentication = Flux.mergeDelayError(1, providers.stream() .map(auth -> auth.authenticate(request, authenticationRequest)) .map(Flux::from) - .map(sequence -> sequence.switchMap(this::handleResponse).onErrorResume(t -> { + .map(sequence -> sequence.switchMap(Authenticator::handleResponse).onErrorResume(t -> { lastError.set(t); return Flux.empty(); - })).collect(Collectors.toList()) + })).toList() .toArray(emptyArr)); return authentication.take(1) @@ -140,16 +236,27 @@ public Publisher authenticate(T request, AuthenticationR } } - private Flux handleResponse(AuthenticationResponse response) { + private static Mono handleResponse(AuthenticationResponse response) { if (response.isAuthenticated()) { - return Flux.just(response); + return Mono.just(response); } else { - return Flux.error(new AuthenticationException(response)); + return Mono.error(new AuthenticationException(response)); + } + } + + @NonNull + private static AuthenticationResponse authenticationResponse(@NonNull AuthenticationProvider provider, + @NonNull T httpRequest, + @NonNull AuthenticationRequest authenticationRequest) { + try { + return provider.authenticate(httpRequest, authenticationRequest); + } catch (Throwable t) { + return authenticationResponseForThrowable(t); } } @NonNull - private AuthenticationResponse authenticationResponseForThrowable(Throwable t) { + private static AuthenticationResponse authenticationResponseForThrowable(Throwable t) { if (Exceptions.isMultiple(t)) { List exceptions = Exceptions.unwrapMultiple(t); return new AuthenticationFailed(exceptions.get(exceptions.size() - 1).getMessage()); diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index 3194f0fc6e..91f172c819 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -20,16 +20,16 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.naming.Named; -import io.micronaut.security.authentication.Authentication; +import io.micronaut.core.order.Ordered; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; /** - * Defines an Authentication Provider with an imperative style. + * Defines an API to authenticate a user with the given request. * @since 4.5.0 * @param Request */ -public interface AuthenticationProvider extends Named { +public interface AuthenticationProvider extends Ordered, Named { /** * Authenticates a user with the given request. diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java new file mode 100644 index 0000000000..9365d95abf --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017-2023 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.authentication.provider; + +import io.micronaut.context.BeanContext; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.annotation.Nullable; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.AuthenticationResponse; +import jakarta.inject.Named; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; +import java.util.concurrent.ExecutorService; + +/** + * Adapts between {@link AuthenticationProvider} to {@link ReactiveAuthenticationProvider}. + * @param Request + */ +@Internal +public class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { + + @NonNull + private final AuthenticationProvider authenticationProvider; + + @NonNull + private final Scheduler scheduler; + + public AuthenticationProviderAdapter(BeanContext beanContext, + @Named(TaskExecutors.BLOCKING) ExecutorService executorService, + @NonNull AuthenticationProvider authenticationProvider) { + this(beanContext, Schedulers.fromExecutorService(executorService), authenticationProvider); + } + + public AuthenticationProviderAdapter(BeanContext beanContext, + Scheduler scheduler, + @NonNull AuthenticationProvider authenticationProvider) { + this(authenticationProvider, + AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, authenticationProvider) ? scheduler : null); + } + + public AuthenticationProviderAdapter(@NonNull AuthenticationProvider authenticationProvider, + @Nullable Scheduler scheduler) { + this.authenticationProvider = authenticationProvider; + this.scheduler = scheduler; + } + + @Override + public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + Mono authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(httpRequest, authenticationRequest)); + return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono; + } +} diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java deleted file mode 100644 index c345617d6a..0000000000 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderFactory.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2017-2023 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.authentication.provider; - -import io.micronaut.context.BeanContext; -import io.micronaut.context.annotation.EachBean; -import io.micronaut.context.annotation.Factory; -import io.micronaut.core.annotation.Internal; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.core.annotation.Nullable; -import io.micronaut.scheduling.TaskExecutors; -import io.micronaut.security.authentication.AuthenticationRequest; -import io.micronaut.security.authentication.AuthenticationResponse; -import jakarta.inject.Named; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; - -import java.util.concurrent.ExecutorService; - -/** - * A factory for adapting {@link AuthenticationProvider} beans to expose them as {@link ReactiveAuthenticationProvider}. - * - * @since 4.5.0 - */ -@Factory -@Internal -class AuthenticationProviderFactory { - - private final Scheduler scheduler; - private final BeanContext beanContext; - - AuthenticationProviderFactory(@Named(TaskExecutors.BLOCKING) ExecutorService executorService, - BeanContext beanContext) { - this.scheduler = Schedulers.fromExecutorService(executorService); - this.beanContext = beanContext; - } - - /** - * Creates an adapted {@link AuthenticationProvider} for each provided instance of {@link AuthenticationProvider}. - * - *

- * NOTE - If there are multiple instances of {@link AuthenticationProvider} in the application context, then they - * must be annotated with a {@link jakarta.inject.Qualifier} such as {@link Named}. - *

- * - * @param authenticationProvider An instance of {@link AuthenticationProvider} to be adapted - * @return An {@link AuthenticationProvider} adapted from the blocking provider - * @param The request type - */ - @EachBean(AuthenticationProvider.class) - ReactiveAuthenticationProvider createAuthenticationProvider(AuthenticationProvider authenticationProvider) { - return new AuthenticationProviderAdapter<>(authenticationProvider, - AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, authenticationProvider) ? scheduler : null); - } - - private static final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { - - @NonNull - private final AuthenticationProvider authenticationProvider; - - @Nullable - private final Scheduler scheduler; - - private AuthenticationProviderAdapter(@NonNull AuthenticationProvider authenticationProvider, - @Nullable Scheduler scheduler) { - this.authenticationProvider = authenticationProvider; - this.scheduler = scheduler; - } - - @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - Mono authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(httpRequest, authenticationRequest)); - return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono; - } - } -} diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index 39bff8ad0a..0d0bab2b08 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -16,6 +16,7 @@ package io.micronaut.security.authentication.provider; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.async.annotation.SingleResult; +import io.micronaut.core.order.Ordered; import io.micronaut.security.authentication.Authentication; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; @@ -27,7 +28,7 @@ * @since 4.5.0 * @param Request */ -public interface ReactiveAuthenticationProvider { +public interface ReactiveAuthenticationProvider extends Ordered { /** * Authenticates a user with the given request. If a successful authentication is diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy new file mode 100644 index 0000000000..d5a3263472 --- /dev/null +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy @@ -0,0 +1,115 @@ +package io.micronaut.security.authentication + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.core.annotation.Nullable +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import jakarta.inject.Named +import spock.lang.Specification + +@Property(name = "micronaut.security.authentication-provider-strategy", value = "ALL") +@Property(name = "spec.name", value = "AuthenticatorAllImperativeSpec") +@MicronautTest +class AuthenticatorAllImperativeSpec extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + void "imperative auth provider"() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("watson", "password")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("sherlock", "password")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + + when: + client.retrieve(createRequest("moriarty", "password")) + + then: + ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + + when: + client.retrieve(createRequest("user", "wrong")) + + then: + ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "AuthenticatorAllImperativeSpec") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } + + @Requires(property = "spec.name", value = "AuthenticatorAllImperativeSpec") + @Named(SherlockAuthenticationProvider.NAME) + static class SherlockAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { + static final String NAME = "sherlock" + @Override + AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + if (authRequest.identity == NAME || authRequest.identity == 'watson') { + return AuthenticationResponse.success(authRequest.identity.toString()) + } + AuthenticationResponse.failure() + } + + @Override + String getName() { + NAME + } + } + + @Requires(property = "spec.name", value = "AuthenticatorAllImperativeSpec") + @Named(MoriartyAuthenticationProvider.NAME) + static class MoriartyAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { + static final String NAME = "moriarty" + @Override + AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + if (authRequest.identity == NAME || authRequest.identity == 'watson') { + return AuthenticationResponse.success(authRequest.identity.toString()) + } + AuthenticationResponse.failure() + } + + @Override + String getName() { + NAME + } + } +} diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy new file mode 100644 index 0000000000..8d90c52f88 --- /dev/null +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy @@ -0,0 +1,107 @@ +package io.micronaut.security.authentication + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.core.annotation.Nullable +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import jakarta.inject.Named +import spock.lang.Specification + +@Property(name = "spec.name", value = "AuthenticatorImperativeSpec") +@MicronautTest +class AuthenticatorImperativeSpec extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + void "imperative auth provider"() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("sherlock", "password")) + + then: + noExceptionThrown() + expected == json + + when: + json = client.retrieve(createRequest("moriarty", "password")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("user", "wrong")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + } + + private HttpRequest createRequest(String userName, String password) { + return HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "AuthenticatorImperativeSpec") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } + + @Requires(property = "spec.name", value = "AuthenticatorImperativeSpec") + @Named(SherlockAuthenticationProvider.NAME) + static class SherlockAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { + static final String NAME = "sherlock" + @Override + AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + if (authRequest.identity == NAME) { + return AuthenticationResponse.success(NAME) + } + AuthenticationResponse.failure() + } + + @Override + String getName() { + NAME + } + } + + @Requires(property = "spec.name", value = "AuthenticatorImperativeSpec") + @Named(MoriartyAuthenticationProvider.NAME) + static class MoriartyAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { + static final String NAME = "moriarty" + @Override + AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + if (authRequest.identity == NAME) { + return AuthenticationResponse.success(NAME) + } + AuthenticationResponse.failure() + } + + @Override + String getName() { + NAME + } + } +} From 4fd4a9bb52e1d0c403fe8d057862d735fc30b556 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 16:47:23 +0100 Subject: [PATCH 13/37] Update security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java --- .../request/password/OauthPasswordAuthenticationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java index 1fbc45cd8b..c7e55b41e2 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java @@ -32,7 +32,7 @@ import reactor.core.publisher.Flux; /** - * A {@link AuthenticationProvider} that delegates to an OAuth 2.0 provider using the + * An {@link AuthenticationProvider} that delegates to an OAuth 2.0 provider using the * password grant flow. * * @author Sergio del Amo From 0fff4a669a8813ba361abf519f8bd1ad51d48077 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 16:50:39 +0100 Subject: [PATCH 14/37] remove unused constructor --- .../provider/AuthenticationProviderAdapter.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java index 9365d95abf..29935e60b0 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java @@ -19,15 +19,11 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.scheduling.TaskExecutors; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import jakarta.inject.Named; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import reactor.core.scheduler.Schedulers; -import java.util.concurrent.ExecutorService; /** * Adapts between {@link AuthenticationProvider} to {@link ReactiveAuthenticationProvider}. @@ -42,12 +38,6 @@ public class AuthenticationProviderAdapter implements ReactiveAuthenticationP @NonNull private final Scheduler scheduler; - public AuthenticationProviderAdapter(BeanContext beanContext, - @Named(TaskExecutors.BLOCKING) ExecutorService executorService, - @NonNull AuthenticationProvider authenticationProvider) { - this(beanContext, Schedulers.fromExecutorService(executorService), authenticationProvider); - } - public AuthenticationProviderAdapter(BeanContext beanContext, Scheduler scheduler, @NonNull AuthenticationProvider authenticationProvider) { From 650b456abe2d01b16507250f6e272d998846a009 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 16:54:22 +0100 Subject: [PATCH 15/37] remove local variables --- .../provider/AuthenticationProviderUtils.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java index c42926062a..09483659ac 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java @@ -21,12 +21,10 @@ import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.http.HttpRequest; -import io.micronaut.inject.BeanDefinition; import io.micronaut.inject.ExecutableMethod; import io.micronaut.security.authentication.AuthenticationRequest; import java.lang.annotation.Annotation; -import java.util.Optional; /** * Utility class to check whether {@link AuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}. @@ -50,13 +48,13 @@ private static boolean isMethodBlocking(BeanContext beanContext, @NonNull Object bean, String methodName, Class... argumentTypes) { - Optional> beanDefinitionOptional = beanContext.findBeanRegistration(bean).map(BeanRegistration::getBeanDefinition); - if (beanDefinitionOptional.isEmpty()) { - return false; - } - BeanDefinition beanDefinition = beanDefinitionOptional.get(); - Optional> methodOptional = beanDefinition.findMethod(methodName, argumentTypes); - return methodOptional.filter(AuthenticationProviderUtils::isBlockingMethod).isPresent(); + return beanContext.findBeanRegistration(bean) + .map(BeanRegistration::getBeanDefinition) + .map(beanDefinition -> beanDefinition + .findMethod(methodName, argumentTypes) + .filter(AuthenticationProviderUtils::isBlockingMethod) + .isPresent()) + .orElse(false); } private static boolean isBlockingMethod(ExecutableMethod executableMethod) { From 977db5e93840ef36d7e4314385be164f13161e25 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 16:58:34 +0100 Subject: [PATCH 16/37] remove mentions to Imperative --- .../ReactiveAuthenticationProvider.java | 4 ++- .../AuthenticationProviderUserPassword.java | 36 ++++++++++--------- ...iveAuthenticationProviderUserPassword.java | 35 ------------------ .../imperativeAuthenticationProviders.adoc | 4 +-- 4 files changed, 24 insertions(+), 55 deletions(-) delete mode 100644 security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index 0d0bab2b08..c421baf745 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -14,6 +14,7 @@ * limitations under the License. */ package io.micronaut.security.authentication.provider; +import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.async.annotation.SingleResult; import io.micronaut.core.order.Ordered; @@ -43,5 +44,6 @@ public interface ReactiveAuthenticationProvider extends Ordered { * @return A publisher that emits 0 or 1 responses */ @SingleResult - Publisher authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest); + Publisher authenticate(@Nullable T httpRequest, + @NonNull AuthenticationRequest authenticationRequest); } diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java index a207dcaa65..ccd158c3a7 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java @@ -1,29 +1,31 @@ package io.micronaut.docs.security.token.basicauth; -//tag::clazz[] import io.micronaut.context.annotation.Requires; -import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.provider.AuthenticationProvider; +import jakarta.inject.Named; import jakarta.inject.Singleton; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -//end::clazz[] -@Requires(property = "spec.name", value = "docsbasicauth") -//tag::clazz[] +@Requires(property = "spec.name", value = "BlockingBasicAuthSpec") +@Named(AuthenticationProviderUserPassword.NAME) @Singleton -public class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { +public class AuthenticationProviderUserPassword implements AuthenticationProvider { + public static final String NAME = "foo"; + @Override + public AuthenticationResponse authenticate(T httpRequest, + AuthenticationRequest authenticationRequest) { + return ( + authenticationRequest.getIdentity().equals("user") && + authenticationRequest.getSecret().equals("password") + ) ? AuthenticationResponse.success("user") : + AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); + } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - return Mono.create(emitter -> { - if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { - emitter.success(AuthenticationResponse.success("user")); - } else { - emitter.error(AuthenticationResponse.exception()); - } - }); + public @NonNull String getName() { + return AuthenticationProviderUserPassword.NAME; } } -//end::clazz[] diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java deleted file mode 100644 index fd3900dbb5..0000000000 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/ImperativeAuthenticationProviderUserPassword.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.micronaut.docs.security.token.basicauth; - -//tag::imports[] -import io.micronaut.context.annotation.Requires; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.security.authentication.AuthenticationFailureReason; -import io.micronaut.security.authentication.AuthenticationRequest; -import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.provider.AuthenticationProvider; -import jakarta.inject.Named; -import jakarta.inject.Singleton; - -//end::imports[] -@Requires(property = "spec.name", value = "BlockingBasicAuthSpec") -//tag::clazz[] -@Named(ImperativeAuthenticationProviderUserPassword.NAME) -@Singleton -public class ImperativeAuthenticationProviderUserPassword implements AuthenticationProvider { - public static final String NAME = "foo"; - @Override - public AuthenticationResponse authenticate(T httpRequest, - AuthenticationRequest authenticationRequest) { - return ( - authenticationRequest.getIdentity().equals("user") && - authenticationRequest.getSecret().equals("password") - ) ? AuthenticationResponse.success("user") : - AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); - } - - @Override - public @NonNull String getName() { - return ImperativeAuthenticationProviderUserPassword.NAME; - } -} -//end::clazz[] diff --git a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc index 16d8306adb..0bf4050725 100644 --- a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc @@ -1,8 +1,8 @@ -The api:security.authentication.provider.ReactiveAuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.ImperativeAuthenticationProvider[] interface: +The api:security.authentication.provider.ReactiveAuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.provider.AuthenticationProvider[] interface: snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] -NOTE: `ImperativeAuthenticationProvider` needs a `@Named` qualifier. +NOTE: `AuthenticationProvider` needs a `@Named` qualifier. IMPORTANT: If your implementation is blocking (e.g., you fetch the user credentials from a database in a blocking way to check against the supplied authentication request), annotate the `authenticate` method with `@Blocking`. If the `authenticate` method is annotated with `@Blocking`, it will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor to avoid blocking the main reactive flow. From 385e36c9f4bf53783f4da6397c921681e403c50c Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 17:11:11 +0100 Subject: [PATCH 17/37] =?UTF-8?q?don=E2=80=99t=20extend=20Named?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../provider/AuthenticationProvider.java | 3 +- .../AuthenticationProviderUserPassword.groovy | 20 ++++++++++++ .../AuthenticationProviderUserPassword.java | 31 ------------------- .../token/basicauth/BasicAuthSpec.groovy | 2 ++ .../AuthenticatorAllImperativeSpec.groovy | 21 +++---------- .../AuthenticatorImperativeSpec.groovy | 25 +++++---------- ...mperativeAuthenticationProviderSpec.groovy | 16 ---------- ...tiveAuthenticationProviderUtilsTest.groovy | 19 ------------ .../imperativeAuthenticationProviders.adoc | 2 -- .../CustomAuthenticationProvider.groovy | 7 ----- .../CustomAuthenticationProvider.kt | 8 ----- .../CustomAuthenticationProvider.java | 9 ------ 12 files changed, 35 insertions(+), 128 deletions(-) create mode 100644 security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy delete mode 100644 security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index 91f172c819..f01e8bfff9 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -19,7 +19,6 @@ import io.micronaut.core.annotation.Blocking; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.naming.Named; import io.micronaut.core.order.Ordered; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; @@ -29,7 +28,7 @@ * @since 4.5.0 * @param Request */ -public interface AuthenticationProvider extends Ordered, Named { +public interface AuthenticationProvider extends Ordered { /** * Authenticates a user with the given request. diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy new file mode 100644 index 0000000000..48cfded02b --- /dev/null +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy @@ -0,0 +1,20 @@ +package io.micronaut.docs.security.token.basicauth + +import io.micronaut.context.annotation.Requires +import io.micronaut.security.authentication.AuthenticationFailureReason +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.provider.AuthenticationProvider +import jakarta.inject.Singleton + +@Requires(property = "spec.name", value = "BlockingBasicAuthSpec") +@Singleton +class AuthenticationProviderUserPassword implements AuthenticationProvider { + @Override + AuthenticationResponse authenticate(T httpRequest, + AuthenticationRequest authenticationRequest) { + (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) + ? AuthenticationResponse.success("user") + : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) + } +} diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java deleted file mode 100644 index ccd158c3a7..0000000000 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.micronaut.docs.security.token.basicauth; - -import io.micronaut.context.annotation.Requires; -import io.micronaut.core.annotation.NonNull; -import io.micronaut.security.authentication.AuthenticationFailureReason; -import io.micronaut.security.authentication.AuthenticationRequest; -import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.provider.AuthenticationProvider; -import jakarta.inject.Named; -import jakarta.inject.Singleton; - -@Requires(property = "spec.name", value = "BlockingBasicAuthSpec") -@Named(AuthenticationProviderUserPassword.NAME) -@Singleton -public class AuthenticationProviderUserPassword implements AuthenticationProvider { - public static final String NAME = "foo"; - @Override - public AuthenticationResponse authenticate(T httpRequest, - AuthenticationRequest authenticationRequest) { - return ( - authenticationRequest.getIdentity().equals("user") && - authenticationRequest.getSecret().equals("password") - ) ? AuthenticationResponse.success("user") : - AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); - } - - @Override - public @NonNull String getName() { - return AuthenticationProviderUserPassword.NAME; - } -} diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy index 7c7150e5c5..a0437c8e85 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy @@ -17,6 +17,7 @@ import jakarta.inject.Singleton import org.reactivestreams.Publisher import reactor.core.publisher.Mono import spock.lang.AutoCleanup +import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification @@ -37,6 +38,7 @@ class BasicAuthSpec extends Specification implements YamlAsciidocTagCleaner { @AutoCleanup HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) + @Ignore void "test /beans is secured but accesible if you supply valid credentials with Basic Auth"() { when: String token = 'dXNlcjpwYXNzd29yZA==' // user:passsword Base64 diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy index d5a3263472..e3ecdf4112 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy @@ -17,6 +17,7 @@ import io.micronaut.security.rules.SecurityRule import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import jakarta.inject.Named +import jakarta.inject.Singleton import spock.lang.Specification @Property(name = "micronaut.security.authentication-provider-strategy", value = "ALL") @@ -78,38 +79,26 @@ class AuthenticatorAllImperativeSpec extends Specification { } @Requires(property = "spec.name", value = "AuthenticatorAllImperativeSpec") - @Named(SherlockAuthenticationProvider.NAME) + @Singleton static class SherlockAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { - static final String NAME = "sherlock" @Override AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { - if (authRequest.identity == NAME || authRequest.identity == 'watson') { + if (authRequest.identity == "sherlock" || authRequest.identity == 'watson') { return AuthenticationResponse.success(authRequest.identity.toString()) } AuthenticationResponse.failure() } - - @Override - String getName() { - NAME - } } @Requires(property = "spec.name", value = "AuthenticatorAllImperativeSpec") - @Named(MoriartyAuthenticationProvider.NAME) + @Singleton static class MoriartyAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { - static final String NAME = "moriarty" @Override AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { - if (authRequest.identity == NAME || authRequest.identity == 'watson') { + if (authRequest.identity == "moriarty" || authRequest.identity == 'watson') { return AuthenticationResponse.success(authRequest.identity.toString()) } AuthenticationResponse.failure() } - - @Override - String getName() { - NAME - } } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy index 8d90c52f88..bf257ed091 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy @@ -17,6 +17,7 @@ import io.micronaut.security.rules.SecurityRule import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import jakarta.inject.Named +import jakarta.inject.Singleton import spock.lang.Specification @Property(name = "spec.name", value = "AuthenticatorImperativeSpec") @@ -70,38 +71,26 @@ class AuthenticatorImperativeSpec extends Specification { } @Requires(property = "spec.name", value = "AuthenticatorImperativeSpec") - @Named(SherlockAuthenticationProvider.NAME) + @Singleton static class SherlockAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { - static final String NAME = "sherlock" @Override AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { - if (authRequest.identity == NAME) { - return AuthenticationResponse.success(NAME) + if (authRequest.identity == "sherlock") { + return AuthenticationResponse.success(authRequest.identity.toString()) } AuthenticationResponse.failure() } - - @Override - String getName() { - NAME - } } @Requires(property = "spec.name", value = "AuthenticatorImperativeSpec") - @Named(MoriartyAuthenticationProvider.NAME) + @Singleton static class MoriartyAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { - static final String NAME = "moriarty" @Override AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { - if (authRequest.identity == NAME) { - return AuthenticationResponse.success(NAME) + if (authRequest.identity == "moriarty") { + return AuthenticationResponse.success(authRequest.identity.toString()) } AuthenticationResponse.failure() } - - @Override - String getName() { - NAME - } } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy index 44d7c3d79c..ee0590742e 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy @@ -65,10 +65,7 @@ class AuthenticationProviderSpec extends ApplicationContextSpecification { @Requires(property = "spec.name", value = "AuthenticationProviderSpec") @Singleton - @Named(SimpleAuthenticationProvider.NAME) static class SimpleAuthenticationProvider implements AuthenticationProvider { - static final String NAME = "SimpleAuthenticationProvider" - private String executedThreadName @Override @@ -81,27 +78,14 @@ class AuthenticationProviderSpec extends ApplicationContextSpecification { return AuthenticationResponse.failure("Over the line.") } } - - @Override - String getName() { - SimpleAuthenticationProvider.NAME - } } @Requires(property = "spec.name", value = "AuthenticationProviderSpec") @Singleton - @Named(NoOpAuthenticationProvider.NAME) static class NoOpAuthenticationProvider implements AuthenticationProvider { - static final String NAME = "NoOpAuthenticationProvider" - @Override AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { throw AuthenticationResponse.exception() } - - @Override - String getName() { - NoOpAuthenticationProvider.NAME - } } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy index ff05cad669..3167b44f18 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy @@ -11,7 +11,6 @@ import io.micronaut.security.authentication.provider.AuthenticationProvider import io.micronaut.security.authentication.provider.AuthenticationProviderUtils import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject -import jakarta.inject.Named import jakarta.inject.Singleton import spock.lang.Specification @@ -38,49 +37,31 @@ class AuthenticationProviderUtilsTest extends Specification { @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton - @Named("foo") static class BlockingAuthenticationProvider implements AuthenticationProvider { @Override @Blocking AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } - - @Override - String getName() { - "foo" - } } @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton - @Named("foo") static class BlockingWithGenericAuthenticationProvider implements AuthenticationProvider { @Override @Blocking AuthenticationResponse authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } - - @Override - String getName() { - "foo" - } } @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton - @Named("bar") static class NonBlockingAuthenticationProvider implements AuthenticationProvider { @Override AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } - - @Override - String getName() { - "bar" - } } } diff --git a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc index 0bf4050725..c58ca2706f 100644 --- a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc @@ -2,7 +2,5 @@ The api:security.authentication.provider.ReactiveAuthenticationProvider[] interf snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] -NOTE: `AuthenticationProvider` needs a `@Named` qualifier. - IMPORTANT: If your implementation is blocking (e.g., you fetch the user credentials from a database in a blocking way to check against the supplied authentication request), annotate the `authenticate` method with `@Blocking`. If the `authenticate` method is annotated with `@Blocking`, it will be executed in a managed thread using the `TaskExecutors.BLOCKING` executor to avoid blocking the main reactive flow. diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy index a1c3797866..ec3dc5fd0d 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy @@ -12,10 +12,8 @@ import jakarta.inject.Singleton @Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] -@Named(CustomAuthenticationProvider.NAME) @Singleton class CustomAuthenticationProvider implements AuthenticationProvider> { - static final String NAME = "foo" @Override AuthenticationResponse authenticate(HttpRequest httpRequest, @@ -24,10 +22,5 @@ class CustomAuthenticationProvider implements AuthenticationProvider> { @@ -23,11 +21,5 @@ class CustomAuthenticationProvider : AuthenticationResponse.success("user") else AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) } - - override fun getName(): String = NAME - - companion object { - const val NAME = "foo" - } } //end::clazz[] diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java index f546832ed1..ce8996bede 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java @@ -1,21 +1,17 @@ package io.micronaut.security.docs.blockingauthenticationprovider; import io.micronaut.context.annotation.Requires; -import io.micronaut.core.annotation.NonNull; import io.micronaut.http.HttpRequest; import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; import io.micronaut.security.authentication.provider.AuthenticationProvider; -import jakarta.inject.Named; import jakarta.inject.Singleton; @Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] -@Named(CustomAuthenticationProvider.NAME) @Singleton class CustomAuthenticationProvider implements AuthenticationProvider> { - static final String NAME = "foo"; @Override public AuthenticationResponse authenticate(HttpRequest httpRequest, @@ -26,10 +22,5 @@ public AuthenticationResponse authenticate(HttpRequest httpRequest, ) ? AuthenticationResponse.success("user") : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); } - - @Override - public @NonNull String getName() { - return NAME; - } } //end::clazz[] From bea22d0b24160d979e214a61819ebb06c641ea77 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 17:14:04 +0100 Subject: [PATCH 18/37] remove ignore --- .../authentication/provider/ReactiveAuthenticationProvider.java | 1 + .../token/basicauth/AuthenticationProviderUserPassword.groovy | 2 +- .../docs/security/token/basicauth/BasicAuthSpec.groovy | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index c421baf745..772de8efdc 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -43,6 +43,7 @@ public interface ReactiveAuthenticationProvider extends Ordered { * @param authenticationRequest The credentials to authenticate * @return A publisher that emits 0 or 1 responses */ + @NonNull @SingleResult Publisher authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authenticationRequest); diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy index 48cfded02b..5160f105f2 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy @@ -7,7 +7,7 @@ import io.micronaut.security.authentication.AuthenticationResponse import io.micronaut.security.authentication.provider.AuthenticationProvider import jakarta.inject.Singleton -@Requires(property = "spec.name", value = "BlockingBasicAuthSpec") +@Requires(property = "spec.name", value = "docsbasicauth") @Singleton class AuthenticationProviderUserPassword implements AuthenticationProvider { @Override diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy index a0437c8e85..9db774a510 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy @@ -38,7 +38,6 @@ class BasicAuthSpec extends Specification implements YamlAsciidocTagCleaner { @AutoCleanup HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) - @Ignore void "test /beans is secured but accesible if you supply valid credentials with Basic Auth"() { when: String token = 'dXNlcjpwYXNzd29yZA==' // user:passsword Base64 From 4445f950edc826b8333871348cd3456621db1bfe Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 17:15:14 +0100 Subject: [PATCH 19/37] remove test --- .../token/basicauth/BasicAuthSpec.groovy | 1 - .../basicauth/BlockingBasicAuthSpec.groovy | 75 ------------------- 2 files changed, 76 deletions(-) delete mode 100644 security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy index 9db774a510..7c7150e5c5 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BasicAuthSpec.groovy @@ -17,7 +17,6 @@ import jakarta.inject.Singleton import org.reactivestreams.Publisher import reactor.core.publisher.Mono import spock.lang.AutoCleanup -import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy deleted file mode 100644 index b9a21fc559..0000000000 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/BlockingBasicAuthSpec.groovy +++ /dev/null @@ -1,75 +0,0 @@ -package io.micronaut.docs.security.token.basicauth - -import io.micronaut.context.ApplicationContext -import io.micronaut.context.annotation.Replaces -import io.micronaut.context.annotation.Requires -import io.micronaut.core.annotation.NonNull -import io.micronaut.http.HttpRequest -import io.micronaut.http.client.HttpClient -import io.micronaut.inject.ExecutableMethod -import io.micronaut.management.endpoint.EndpointSensitivityProcessor -import io.micronaut.runtime.server.EmbeddedServer -import io.micronaut.security.authentication.Authentication -import io.micronaut.security.rules.SecurityRuleResult -import io.micronaut.security.rules.SensitiveEndpointRule -import jakarta.inject.Singleton -import org.reactivestreams.Publisher -import reactor.core.publisher.Mono -import spock.lang.AutoCleanup -import spock.lang.Shared -import spock.lang.Specification - -class BlockingBasicAuthSpec extends Specification { - - @Shared - Map config = [ - 'spec.name' : 'BlockingBasicAuthSpec', - 'endpoints.beans.enabled' : true, - 'endpoints.beans.sensitive' : true, - ] - - @Shared - @AutoCleanup - EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer, config as Map) - - @Shared - @AutoCleanup - HttpClient client = embeddedServer.applicationContext.createBean(HttpClient, embeddedServer.getURL()) - - void "test /beans is secured but accesible if you supply valid credentials with Basic Auth"() { - when: - String token = 'dXNlcjpwYXNzd29yZA==' // user:passsword Base64 - client.toBlocking().exchange(HttpRequest.GET("/beans") - .header("Authorization", "Basic ${token}".toString()), String) - - then: - noExceptionThrown() - } - - def "basicAuth() sets Authorization Header with Basic base64(username:password)"() { - when: - // tag::basicAuth[] - HttpRequest request = HttpRequest.GET("/home").basicAuth('sherlock', 'password') - // end::basicAuth[] - - then: - request.headers.get('Authorization') - request.headers.get('Authorization') == "Basic ${'sherlock:password'.bytes.encodeBase64().toString()}" - } - - @Requires(property = 'spec.name', value = 'BlockingBasicAuthSpec') - @Replaces(SensitiveEndpointRule.class) - @Singleton - static class SensitiveEndpointRuleReplacement extends SensitiveEndpointRule { - SensitiveEndpointRuleReplacement(EndpointSensitivityProcessor endpointSensitivityProcessor) { - super(endpointSensitivityProcessor) - } - - @Override - protected Publisher checkSensitiveAuthenticated(@NonNull HttpRequest request, - @NonNull Authentication authentication, - @NonNull ExecutableMethod method) { - Mono.just(SecurityRuleResult.ALLOWED) - } - } -} From dbbe711079ac7a080de0a46048193554cf9ac7a9 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 17:19:05 +0100 Subject: [PATCH 20/37] remove @NonNull --- .../authentication/provider/AuthenticationProviderAdapter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java index 29935e60b0..b2f9db1202 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java @@ -35,7 +35,6 @@ public class AuthenticationProviderAdapter implements ReactiveAuthenticationP @NonNull private final AuthenticationProvider authenticationProvider; - @NonNull private final Scheduler scheduler; public AuthenticationProviderAdapter(BeanContext beanContext, From c546809c48f61b156bb136b0a8043155fd5ef3cc Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 17:21:50 +0100 Subject: [PATCH 21/37] add package-info.java --- .../authentication/provider/package-info.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 security/src/main/java/io/micronaut/security/authentication/provider/package-info.java diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java b/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java new file mode 100644 index 0000000000..5a56348feb --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2017-2023 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. + */ +/** + * Authentication provider API. + * @author Sergio del Amo + * @since 4.5.0 + */ +package io.micronaut.security.authentication.provider; \ No newline at end of file From 0de5040e92f1bff90fdf2f23ab6900d8d392afa3 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 13 Dec 2023 19:55:48 +0100 Subject: [PATCH 22/37] add HttpRequestAuthenticationProvider and HttpRequestReactiveAuthenticationProvider --- .../HttpRequestAuthenticationProvider.java | 26 ++++++ ...RequestReactiveAuthenticationProvider.java | 26 ++++++ ...tpRequestAuthenticationProviderSpec.groovy | 80 ++++++++++++++++++ ...tReactiveAuthenticationProviderSpec.groovy | 82 +++++++++++++++++++ .../docs/guide/authenticationProviders.adoc | 2 +- .../imperativeAuthenticationProviders.adoc | 2 +- 6 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java create mode 100644 security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java create mode 100644 security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProviderSpec.groovy create mode 100644 security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProviderSpec.groovy diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java new file mode 100644 index 0000000000..ab64db37a0 --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017-2023 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.authentication.provider; + +import io.micronaut.http.HttpRequest; + +/** + * {@link AuthenticationProvider} for {@link HttpRequest}. + * @author Sergio del Amo + * @since 4.5.0 + */ +public interface HttpRequestAuthenticationProvider extends AuthenticationProvider { +} diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java new file mode 100644 index 0000000000..73c7799ac2 --- /dev/null +++ b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017-2023 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.authentication.provider; + +import io.micronaut.http.HttpRequest; + +/** + * {@link ReactiveAuthenticationProvider} for {@link HttpRequest}. + * @author Sergio del Amo + * @since 4.5.0 + */ +public interface HttpRequestReactiveAuthenticationProvider extends ReactiveAuthenticationProvider { +} diff --git a/security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProviderSpec.groovy new file mode 100644 index 0000000000..ac4f31afa7 --- /dev/null +++ b/security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProviderSpec.groovy @@ -0,0 +1,80 @@ +package io.micronaut.security.authentication.provider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.core.annotation.Nullable +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.MutableHttpRequest +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import jakarta.inject.Singleton +import spock.lang.Specification + +@Property(name = "spec.name", value = "HttpRequestAuthenticationProviderSpec") +@MicronautTest +class HttpRequestAuthenticationProviderSpec extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + void "imperative auth provider"() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("sherlock", "password").header("X-API-Version", "v1")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("sherlock", "password")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + } + + private MutableHttpRequest createRequest(String userName, String password) { + HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "HttpRequestAuthenticationProviderSpec") + @Singleton + static class SherlockAuthenticationProvider implements HttpRequestAuthenticationProvider { + @Override + AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { + if (httpRequest.headers.contains("X-API-Version") && authRequest.identity == "sherlock") { + return AuthenticationResponse.success(authRequest.identity.toString()) + } + AuthenticationResponse.failure() + } + } + + + @Requires(property = "spec.name", value = "HttpRequestAuthenticationProviderSpec") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } +} diff --git a/security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProviderSpec.groovy new file mode 100644 index 0000000000..470af3617a --- /dev/null +++ b/security/src/test/groovy/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProviderSpec.groovy @@ -0,0 +1,82 @@ +package io.micronaut.security.authentication.provider + +import io.micronaut.context.annotation.Property +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.core.annotation.Nullable +import io.micronaut.core.async.annotation.SingleResult +import io.micronaut.http.HttpRequest +import io.micronaut.http.HttpStatus +import io.micronaut.http.MutableHttpRequest +import io.micronaut.http.annotation.Controller +import io.micronaut.http.annotation.Get +import io.micronaut.http.client.BlockingHttpClient +import io.micronaut.http.client.HttpClient +import io.micronaut.http.client.annotation.Client +import io.micronaut.http.client.exceptions.HttpClientResponseException +import io.micronaut.security.annotation.Secured +import io.micronaut.security.authentication.AuthenticationRequest +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.rules.SecurityRule +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.reactivestreams.Publisher +import reactor.core.publisher.Mono +import spock.lang.Specification + +@Property(name = "spec.name", value = "HttpRequestReactiveAuthenticationProviderSpec") +@MicronautTest +class HttpRequestReactiveAuthenticationProviderSpec extends Specification { + + @Inject + @Client("/") + HttpClient httpClient + + void "imperative auth provider"() { + given: + BlockingHttpClient client = httpClient.toBlocking() + String expected = '{"message":"Hello World"}' + + when: + String json = client.retrieve(createRequest("sherlock", "password").header("X-API-Version", "v1")) + + then: + noExceptionThrown() + expected == json + + when: + client.retrieve(createRequest("sherlock", "password")) + + then: + HttpClientResponseException ex = thrown() + HttpStatus.UNAUTHORIZED == ex.status + } + + private MutableHttpRequest createRequest(String userName, String password) { + HttpRequest.GET("/messages").basicAuth(userName, password) + } + + @Requires(property = "spec.name", value = "HttpRequestReactiveAuthenticationProviderSpec") + @Singleton + static class SherlockAuthenticationProvider implements HttpRequestReactiveAuthenticationProvider { + @Override + @SingleResult + Publisher authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { + Mono.just((httpRequest.headers.contains("X-API-Version") && authRequest.identity == "sherlock") + ? AuthenticationResponse.success(authRequest.identity.toString()) + : AuthenticationResponse.failure()) + } + } + + @Requires(property = "spec.name", value = "HttpRequestReactiveAuthenticationProviderSpec") + @Controller("/messages") + static class HelloWorldController { + + @Secured(SecurityRule.IS_AUTHENTICATED) + @Get + Map index() { + [message: "Hello World"] + } + } +} diff --git a/src/main/docs/guide/authenticationProviders.adoc b/src/main/docs/guide/authenticationProviders.adoc index ab6b253f47..7957fb145b 100644 --- a/src/main/docs/guide/authenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders.adoc @@ -1,4 +1,4 @@ -To authenticate users you must provide implementations of api:security.authentication.provider.ReactiveAuthenticationProvider[]. +To authenticate users you must provide implementations of api:security.authentication.provider.ReactiveAuthenticationProvider[] or api:security.authentication.provider.HttpRequestReactiveAuthenticationProvider[]. The following code snippet illustrates a naive implementation: diff --git a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc index c58ca2706f..ea566abc9d 100644 --- a/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc +++ b/src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc @@ -1,4 +1,4 @@ -The api:security.authentication.provider.ReactiveAuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.provider.AuthenticationProvider[] interface: +The api:security.authentication.provider.ReactiveAuthenticationProvider[] interface is a reactive API. If you prefer an imperative style, you can instead implement the api:security.authentication.provider.AuthenticationProvider[] or api:security.authentication.provider.HttpRequestAuthenticationProvider[] interface: snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"] From f9008a27501fe1c90795cb635ac294ef6379b122 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 06:38:27 +0100 Subject: [PATCH 23/37] Update security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java Co-authored-by: Jeremy Grelle --- .../security/authentication/AuthenticationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java index 095d90c4d2..95c6d05555 100644 --- a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java @@ -24,7 +24,7 @@ * @author Graeme Rocher * @since 1.0 * @param Request - * @deprecated Use {@link io.micronaut.security.authentication.provider.AuthenticationProvider} for an imperative API or {@link ReactiveAuthenticationProvider} for a Reactive API instead. + * @deprecated Use {@link io.micronaut.security.authentication.provider.AuthenticationProvider} for an imperative API or {@link ReactiveAuthenticationProvider} for a reactive API instead. */ @Deprecated(forRemoval = true, since = "4.5.0") public interface AuthenticationProvider extends ReactiveAuthenticationProvider { From bc4f0aeb423cc29ae0fa423e4d237dd54e1a4cd1 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 06:38:36 +0100 Subject: [PATCH 24/37] Update security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java Co-authored-by: Jeremy Grelle --- .../authentication/provider/ReactiveAuthenticationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index 772de8efdc..0fce642fe8 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -24,7 +24,7 @@ import org.reactivestreams.Publisher; /** - * Defines a Reactive authentication provider. + * Defines a reactive authentication provider. * * @since 4.5.0 * @param Request From 395d89d4aa17ee2edc6c5a07686ecf749602cb8d Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 06:38:47 +0100 Subject: [PATCH 25/37] Update security/src/main/java/io/micronaut/security/authentication/Authenticator.java Co-authored-by: Jeremy Grelle --- .../io/micronaut/security/authentication/Authenticator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index 4d7b6a37a1..54763a5d9e 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -101,7 +101,7 @@ public Authenticator(BeanContext beanContext, /** * @param authenticationProviders A list of available authentication providers * @param securityConfiguration The security configuration - * @deprecated Use {@link Authenticator(List, SecurityConfiguration)} instead. + * @deprecated Use {@link Authenticator#Authenticator(BeanContext, List, List, ExecutorService, SecurityConfiguration)} instead. */ @Deprecated(forRemoval = true, since = "4.5.0") public Authenticator(Collection> authenticationProviders, From 8001291896502f083133dc31335b240ccc2ba395 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 13:31:02 +0100 Subject: [PATCH 26/37] httpRequest with requestContext --- .../CustomAuthenticationProvider.java | 2 +- .../ldap/LdapAuthenticationProvider.java | 2 +- .../OauthPasswordAuthenticationProvider.java | 2 +- .../OpenIdPasswordAuthenticationProvider.java | 6 ++--- .../security/session/ContextPathSpec.groovy | 2 +- .../authentication/Authenticator.java | 22 +++++++++---------- .../provider/AuthenticationProvider.java | 4 ++-- .../AuthenticationProviderAdapter.java | 4 ++-- .../ReactiveAuthenticationProvider.java | 2 +- .../DefaultIntrospectionProcessor.java | 14 ++++++------ .../introspection/IntrospectionProcessor.java | 4 ++-- .../AuthenticationProviderUserPassword.groovy | 2 +- .../AuthenticationProviderUserPassword.groovy | 2 +- .../AuthenticationProviderUserPassword.groovy | 2 +- .../AuthenticatorAllImperativeSpec.groovy | 4 ++-- .../AuthenticatorImperativeSpec.groovy | 4 ++-- ...mperativeAuthenticationProviderSpec.groovy | 4 ++-- ...tiveAuthenticationProviderUtilsTest.groovy | 2 +- .../security/MockAuthenticationProvider.java | 2 +- .../MockAuthenticationProvider.java | 2 +- 20 files changed, 44 insertions(+), 44 deletions(-) diff --git a/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java b/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java index 880e4be15c..8f4a562410 100644 --- a/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java +++ b/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java @@ -17,7 +17,7 @@ public class CustomAuthenticationProvider implements ReactiveAuthenticationProvider { @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create(emitter -> { emitter.next(AuthenticationResponse.success("sherlock", Collections.singletonMap("email", "sherlock@micronaut.example"))); emitter.complete(); diff --git a/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java b/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java index 59dfebec6d..ea0f5109c5 100644 --- a/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java +++ b/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java @@ -86,7 +86,7 @@ public LdapAuthenticationProvider(LdapConfiguration configuration, } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { Flux reactiveSequence = Flux.create(emitter -> { String username = authenticationRequest.getIdentity().toString(); String password = authenticationRequest.getSecret().toString(); diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java index c7e55b41e2..dd6264f1bb 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java @@ -61,7 +61,7 @@ public OauthPasswordAuthenticationProvider(TokenEndpointClient tokenEndpointClie } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { OauthPasswordTokenRequestContext context = new OauthPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration); diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java index 5156285f81..50e4838f77 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java @@ -77,12 +77,12 @@ public OpenIdPasswordAuthenticationProvider(OauthClientConfiguration clientConfi } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { - OpenIdPasswordTokenRequestContext requestContext = new OpenIdPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration); + OpenIdPasswordTokenRequestContext openIdPasswordTokenRequestContext = new OpenIdPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration); return Flux.from( - tokenEndpointClient.sendRequest(requestContext)) + tokenEndpointClient.sendRequest(openIdPasswordTokenRequestContext)) .switchMap(response -> { Optional jwt = tokenResponseValidator.validate(clientConfiguration, openIdProviderMetadata, response, null); if (jwt.isPresent()) { diff --git a/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy b/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy index b18e0c63aa..2c5505762d 100644 --- a/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy +++ b/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy @@ -74,7 +74,7 @@ class ContextPathSpec extends EmbeddedServerSpecification { static class MockAuthenticationProvider implements ReactiveAuthenticationProvider { @Override - Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Mono.create(emitter -> { if (authenticationRequest.identity =="user" && authenticationRequest.secret == "password") { emitter.success(AuthenticationResponse.success("user")) diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index 54763a5d9e..8705e5ff17 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -117,11 +117,11 @@ public Authenticator(Collection authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { if (CollectionUtils.isEmpty(reactiveAuthenticationProviders) && CollectionUtils.isEmpty(imperativeAuthenticationProviders)) { return Mono.empty(); } @@ -132,9 +132,9 @@ public Publisher authenticate(T httpRequest, Authenticat LOG.debug(reactiveAuthenticationProviders.stream().map(ReactiveAuthenticationProvider::getClass).map(Class::getName).collect(Collectors.joining())); } if (CollectionUtils.isEmpty(reactiveAuthenticationProviders) && imperativeAuthenticationProviders != null && !anyImperativeAuthenticationProviderIsBlocking()) { - return handleResponse(authenticate(httpRequest, authenticationRequest, imperativeAuthenticationProviders, securityConfiguration)); + return handleResponse(authenticate(requestContext, authenticationRequest, imperativeAuthenticationProviders, securityConfiguration)); } - return authenticate(httpRequest, authenticationRequest, everyProviderSorted()); + return authenticate(requestContext, authenticationRequest, everyProviderSorted()); } private boolean anyImperativeAuthenticationProviderIsBlocking() { @@ -142,26 +142,26 @@ private boolean anyImperativeAuthenticationProviderIsBlocking() { } @NonNull - private static AuthenticationResponse authenticate(@NonNull T httpRequest, + private static AuthenticationResponse authenticate(@NonNull T requestContext, @NonNull AuthenticationRequest authenticationRequest, @NonNull List> authenticationProviders, @Nullable SecurityConfiguration securityConfiguration) { if (securityConfiguration != null && securityConfiguration.getAuthenticationProviderStrategy() == AuthenticationStrategy.ALL) { - return authenticateAll(httpRequest, authenticationRequest, authenticationProviders); + return authenticateAll(requestContext, authenticationRequest, authenticationProviders); } return authenticationProviders.stream() - .map(provider -> authenticationResponse(provider, httpRequest, authenticationRequest)) + .map(provider -> authenticationResponse(provider, requestContext, authenticationRequest)) .filter(AuthenticationResponse::isAuthenticated) .findFirst() .orElseGet(AuthenticationResponse::failure); } @NonNull - private static AuthenticationResponse authenticateAll(@NonNull T httpRequest, + private static AuthenticationResponse authenticateAll(@NonNull T requestContext, @NonNull AuthenticationRequest authenticationRequest, @NonNull List> authenticationProviders) { List authenticationResponses = authenticationProviders.stream() - .map(provider -> authenticationResponse(provider, httpRequest, authenticationRequest)) + .map(provider -> authenticationResponse(provider, requestContext, authenticationRequest)) .toList(); if (CollectionUtils.isEmpty(authenticationResponses)) { return AuthenticationResponse.failure(); @@ -246,10 +246,10 @@ private static Mono handleResponse(AuthenticationRespons @NonNull private static AuthenticationResponse authenticationResponse(@NonNull AuthenticationProvider provider, - @NonNull T httpRequest, + @NonNull T requestContext, @NonNull AuthenticationRequest authenticationRequest) { try { - return provider.authenticate(httpRequest, authenticationRequest); + return provider.authenticate(requestContext, authenticationRequest); } catch (Throwable t) { return authenticationResponseForThrowable(t); } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index f01e8bfff9..a9969f85b0 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -37,11 +37,11 @@ public interface AuthenticationProvider extends Ordered { * If your implementation is blocking, annotate the overriden method with {@link Blocking} and it will be safely executed on a * dedicated thread in order to not block the main reactive chain of execution. * - * @param httpRequest The http request + * @param requestContext The context request (typically an HTTP Request). * @param authRequest The credentials to authenticate * @return An {@link AuthenticationResponse} indicating either success or failure. */ @NonNull @Executable - AuthenticationResponse authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authRequest); + AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest); } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java index b2f9db1202..417e4af2a6 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java @@ -51,8 +51,8 @@ public AuthenticationProviderAdapter(@NonNull AuthenticationProvider authenti } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { - Mono authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(httpRequest, authenticationRequest)); + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + Mono authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(requestContext, authenticationRequest)); return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono; } } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index 0fce642fe8..a1fae4790c 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -45,6 +45,6 @@ public interface ReactiveAuthenticationProvider extends Ordered { */ @NonNull @SingleResult - Publisher authenticate(@Nullable T httpRequest, + Publisher authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authenticationRequest); } diff --git a/security/src/main/java/io/micronaut/security/endpoints/introspection/DefaultIntrospectionProcessor.java b/security/src/main/java/io/micronaut/security/endpoints/introspection/DefaultIntrospectionProcessor.java index c9aa8d56e9..7cd78d176d 100644 --- a/security/src/main/java/io/micronaut/security/endpoints/introspection/DefaultIntrospectionProcessor.java +++ b/security/src/main/java/io/micronaut/security/endpoints/introspection/DefaultIntrospectionProcessor.java @@ -87,12 +87,12 @@ public DefaultIntrospectionProcessor(Collection> tokenValidato @NonNull @Override public Publisher introspect(@NonNull IntrospectionRequest introspectionRequest, - @NonNull T httpRequest) { + @NonNull T requestContext) { String token = introspectionRequest.getToken(); return Flux.fromIterable(tokenValidators) - .flatMap(tokenValidator -> tokenValidator.validateToken(token, httpRequest)) + .flatMap(tokenValidator -> tokenValidator.validateToken(token, requestContext)) .next() - .map(authentication -> createIntrospectionResponse(authentication, httpRequest)) + .map(authentication -> createIntrospectionResponse(authentication, requestContext)) .defaultIfEmpty(emptyIntrospectionResponse(token)) .flux(); } @@ -123,19 +123,19 @@ protected IntrospectionResponse emptyIntrospectionResponse(@NonNull String token @NonNull @Override public Publisher introspect(@NonNull Authentication authentication, - @NonNull T httpRequest) { - return Flux.just(createIntrospectionResponse(authentication, httpRequest)); + @NonNull T requestContext) { + return Flux.just(createIntrospectionResponse(authentication, requestContext)); } /** * Creates an {@link IntrospectionResponse} for an {@link Authentication}. * @param authentication Authentication - * @param httpRequest HTTP Request + * @param requestContext HTTP Request * @return an {@link IntrospectionResponse} */ @NonNull public IntrospectionResponse createIntrospectionResponse(@NonNull Authentication authentication, - @NonNull T httpRequest) { + @NonNull T requestContext) { return new IntrospectionResponse(true, resolveTokenType(authentication).orElse(null), resolveScope(authentication).orElse(null), diff --git a/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java b/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java index c1d35ff597..c70b009ed3 100644 --- a/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java +++ b/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java @@ -36,7 +36,7 @@ public interface IntrospectionProcessor { */ @NonNull Publisher introspect(@NonNull IntrospectionRequest introspectionRequest, - @NonNull T httpRequest); + @NonNull T requestContext); /** * @@ -46,5 +46,5 @@ Publisher introspect(@NonNull IntrospectionRequest intros */ @NonNull Publisher introspect(@NonNull Authentication authentication, - @NonNull T httpRequest); + @NonNull T requestContext); } diff --git a/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy index e08b7f98ee..06397b800e 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy @@ -18,7 +18,7 @@ class AuthenticationProviderUserPassword implements ReactiveAuthenticationPro @Override - Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create({emitter -> if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { emitter.next(AuthenticationResponse.success("user", ["ROLE_USER"])) diff --git a/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy index 58bbe9b73b..b60594fb4c 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy @@ -16,7 +16,7 @@ import reactor.core.publisher.FluxSink class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { @Override - Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create({emitter -> if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { emitter.next(AuthenticationResponse.success("user")) diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy index 5160f105f2..5ca43b10b2 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy @@ -11,7 +11,7 @@ import jakarta.inject.Singleton @Singleton class AuthenticationProviderUserPassword implements AuthenticationProvider { @Override - AuthenticationResponse authenticate(T httpRequest, + AuthenticationResponse authenticate(T requestContext, AuthenticationRequest authenticationRequest) { (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) ? AuthenticationResponse.success("user") diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy index e3ecdf4112..ecc7b5c8c0 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorAllImperativeSpec.groovy @@ -82,7 +82,7 @@ class AuthenticatorAllImperativeSpec extends Specification { @Singleton static class SherlockAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable Object requestContext, @NonNull AuthenticationRequest authRequest) { if (authRequest.identity == "sherlock" || authRequest.identity == 'watson') { return AuthenticationResponse.success(authRequest.identity.toString()) } @@ -94,7 +94,7 @@ class AuthenticatorAllImperativeSpec extends Specification { @Singleton static class MoriartyAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable Object requestContext, @NonNull AuthenticationRequest authRequest) { if (authRequest.identity == "moriarty" || authRequest.identity == 'watson') { return AuthenticationResponse.success(authRequest.identity.toString()) } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy index bf257ed091..867dc87816 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/AuthenticatorImperativeSpec.groovy @@ -74,7 +74,7 @@ class AuthenticatorImperativeSpec extends Specification { @Singleton static class SherlockAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable Object requestContext, @NonNull AuthenticationRequest authRequest) { if (authRequest.identity == "sherlock") { return AuthenticationResponse.success(authRequest.identity.toString()) } @@ -86,7 +86,7 @@ class AuthenticatorImperativeSpec extends Specification { @Singleton static class MoriartyAuthenticationProvider implements io.micronaut.security.authentication.provider.AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable Object httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable Object requestContext, @NonNull AuthenticationRequest authRequest) { if (authRequest.identity == "moriarty") { return AuthenticationResponse.success(authRequest.identity.toString()) } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy index ee0590742e..4f2c21a896 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy @@ -70,7 +70,7 @@ class AuthenticationProviderSpec extends ApplicationContextSpecification { @Override @Blocking - AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { + AuthenticationResponse authenticate(@Nullable T requestContext, AuthenticationRequest authenticationRequest) { executedThreadName = Thread.currentThread().getName() if (authenticationRequest.getIdentity().toString() == 'lebowski' && authenticationRequest.getSecret().toString() == 'thedudeabides') { return AuthenticationResponse.success('lebowski') @@ -84,7 +84,7 @@ class AuthenticationProviderSpec extends ApplicationContextSpecification { @Singleton static class NoOpAuthenticationProvider implements AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable T httpRequest, AuthenticationRequest authenticationRequest) { + AuthenticationResponse authenticate(@Nullable T requestContext, AuthenticationRequest authenticationRequest) { throw AuthenticationResponse.exception() } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy index 3167b44f18..60550a57ed 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy @@ -50,7 +50,7 @@ class AuthenticationProviderUtilsTest extends Specification { static class BlockingWithGenericAuthenticationProvider implements AuthenticationProvider { @Override @Blocking - AuthenticationResponse authenticate(@Nullable T httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } } diff --git a/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java b/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java index 2aada2b902..067368f3a5 100644 --- a/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java +++ b/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java @@ -52,7 +52,7 @@ public MockAuthenticationProvider(List successAut } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create(emitter -> { Optional successAuth = successAuthenticationScenarioList.stream() .filter(scenario -> { diff --git a/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java b/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java index f5b9fc3640..dad24d2c1e 100644 --- a/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java +++ b/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java @@ -53,7 +53,7 @@ public MockAuthenticationProvider(List successAut } @Override - public Publisher authenticate(T httpRequest, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create(emitter -> { Optional successAuth = successAuthenticationScenarioList.stream() .filter(scenario -> { From c737dd155138cd26f01ed3e9ab2fb3b9a1d4acfd Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 14:31:08 +0100 Subject: [PATCH 27/37] annotate with @Indexed --- .../authentication/provider/AuthenticationProvider.java | 2 ++ .../provider/ReactiveAuthenticationProvider.java | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index a9969f85b0..0113ccc0b1 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -17,6 +17,7 @@ import io.micronaut.context.annotation.Executable; import io.micronaut.core.annotation.Blocking; +import io.micronaut.core.annotation.Indexed; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.order.Ordered; @@ -28,6 +29,7 @@ * @since 4.5.0 * @param Request */ +@Indexed(AuthenticationProvider.class) public interface AuthenticationProvider extends Ordered { /** diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index a1fae4790c..ffb7d30aff 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -14,6 +14,7 @@ * limitations under the License. */ package io.micronaut.security.authentication.provider; +import io.micronaut.core.annotation.Indexed; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; import io.micronaut.core.async.annotation.SingleResult; @@ -29,6 +30,7 @@ * @since 4.5.0 * @param Request */ +@Indexed(ReactiveAuthenticationProvider.class) public interface ReactiveAuthenticationProvider extends Ordered { /** @@ -39,7 +41,7 @@ public interface ReactiveAuthenticationProvider extends Ordered { * all authenticators for each authentication request and it is assumed no work * will be done until the publisher is subscribed to. * - * @param httpRequest The http request + * @param requestContext rquest context (it may be an HTTP request). * @param authenticationRequest The credentials to authenticate * @return A publisher that emits 0 or 1 responses */ From 1f237fe6d800aa5ba6e8cf28525c0b3a6c4960a3 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 14:31:24 +0100 Subject: [PATCH 28/37] httpRequest with requestContext --- .../endpoints/introspection/IntrospectionProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java b/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java index c70b009ed3..0bab445bc6 100644 --- a/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java +++ b/security/src/main/java/io/micronaut/security/endpoints/introspection/IntrospectionProcessor.java @@ -31,7 +31,7 @@ public interface IntrospectionProcessor { /** * * @param introspectionRequest A parameter representing the token along with optional parameters representing additional context - * @param httpRequest HTTP Request + * @param requestContext HTTP Request * @return Introspection Response */ @NonNull @@ -41,7 +41,7 @@ Publisher introspect(@NonNull IntrospectionRequest intros /** * * @param authentication The authentication - * @param httpRequest HTTP Request + * @param requestContext HTTP Request * @return Introspection Response */ @NonNull From 07c40af10414f7d9e003a415b92a3683e67fa6af Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 14:35:43 +0100 Subject: [PATCH 29/37] final AuthenticationProviderAdapter --- .../authentication/provider/AuthenticationProviderAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java index 417e4af2a6..44091be707 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java @@ -30,7 +30,7 @@ * @param Request */ @Internal -public class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { +public final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { @NonNull private final AuthenticationProvider authenticationProvider; From 8c8318dcfcd4595046c8816b75046b2d6541eeb6 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 14:35:55 +0100 Subject: [PATCH 30/37] add generic argument type --- .../provider/HttpRequestAuthenticationProvider.java | 3 ++- .../provider/HttpRequestReactiveAuthenticationProvider.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java index ab64db37a0..639f092928 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java @@ -21,6 +21,7 @@ * {@link AuthenticationProvider} for {@link HttpRequest}. * @author Sergio del Amo * @since 4.5.0 + * @param The body type */ -public interface HttpRequestAuthenticationProvider extends AuthenticationProvider { +public interface HttpRequestAuthenticationProvider extends AuthenticationProvider> { } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java index 73c7799ac2..118fef557a 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java @@ -21,6 +21,7 @@ * {@link ReactiveAuthenticationProvider} for {@link HttpRequest}. * @author Sergio del Amo * @since 4.5.0 + * @param The body type */ -public interface HttpRequestReactiveAuthenticationProvider extends ReactiveAuthenticationProvider { +public interface HttpRequestReactiveAuthenticationProvider extends ReactiveAuthenticationProvider> { } From e3a5d1bd8fea5f0a571e9e49f895e664b7ac2683 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 14:58:25 +0100 Subject: [PATCH 31/37] add default methods --- .../provider/AuthenticationProvider.java | 16 ++++++++++++++++ .../ReactiveAuthenticationProvider.java | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index 0113ccc0b1..36ceb141b5 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -46,4 +46,20 @@ public interface AuthenticationProvider extends Ordered { @NonNull @Executable AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest); + + /** + * Authenticates a user with the given request. + * If authenticated successfully return {@link AuthenticationResponse#success(String)}. + * If not authenticated return {@link AuthenticationResponse#failure()}. + * If your implementation is blocking, annotate the overriden method with {@link Blocking} and it will be safely executed on a + * dedicated thread in order to not block the main reactive chain of execution. + * + * @param authRequest The credentials to authenticate + * @return An {@link AuthenticationResponse} indicating either success or failure. + */ + @NonNull + @Executable + default AuthenticationResponse authenticate(@NonNull AuthenticationRequest authRequest) { + return authenticate(null, authRequest); + } } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index ffb7d30aff..5ea3a40364 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -49,4 +49,21 @@ public interface ReactiveAuthenticationProvider extends Ordered { @SingleResult Publisher authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authenticationRequest); + + /** + * Authenticates a user with the given request. If a successful authentication is + * returned, the object must be an instance of {@link Authentication}. + * + * Publishers MUST emit cold observables! This method will be called for + * all authenticators for each authentication request and it is assumed no work + * will be done until the publisher is subscribed to. + * + * @param authenticationRequest The credentials to authenticate + * @return A publisher that emits 0 or 1 responses + */ + @NonNull + @SingleResult + default Publisher authenticate(@NonNull AuthenticationRequest authenticationRequest) { + return authenticate(null, authenticationRequest); + } } From 35d8d0ad736f84110259f67e1364aa5b2b5de557 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 16:25:39 +0100 Subject: [PATCH 32/37] add new line to package-info.java --- .../security/authentication/provider/package-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java b/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java index 5a56348feb..bb1b1c1361 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/package-info.java @@ -18,4 +18,4 @@ * @author Sergio del Amo * @since 4.5.0 */ -package io.micronaut.security.authentication.provider; \ No newline at end of file +package io.micronaut.security.authentication.provider; From c9ce4564259e91de878edf342c6fe82ac099e4b6 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 16:26:00 +0100 Subject: [PATCH 33/37] sonar: Catch Exception instead of Throwable --- .../io/micronaut/security/authentication/Authenticator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index 8705e5ff17..79b00da906 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -250,7 +250,7 @@ private static AuthenticationResponse authenticationResponse(@NonNull Authen @NonNull AuthenticationRequest authenticationRequest) { try { return provider.authenticate(requestContext, authenticationRequest); - } catch (Throwable t) { + } catch (Exception t) { return authenticationResponseForThrowable(t); } } From 76bf47e56da125081af1b4a27483811be6286716 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 16:26:08 +0100 Subject: [PATCH 34/37] sonar: refactor test --- .../AuthenticationProviderTest.java | 5 ++++- .../ReactiveAuthenticationProviderTest.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java index b59c2b1600..0c47277024 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/AuthenticationProviderTest.java @@ -14,6 +14,7 @@ import io.micronaut.security.rules.SecurityRule; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import java.util.Collections; import java.util.Map; @@ -31,7 +32,9 @@ void authProvider(@Client("/") HttpClient httpClient) { String expected = """ {"message":"Hello World"}"""; assertEquals(expected, json); - HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.retrieve(createRequest("user", "wrong"))); + HttpRequest request = createRequest("user", "wrong"); + Executable e = () -> client.retrieve(request); + HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, e); assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatus()); } diff --git a/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java index 1af5d9b44b..55c8e601ee 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/ReactiveAuthenticationProviderTest.java @@ -14,6 +14,7 @@ import io.micronaut.security.rules.SecurityRule; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; import java.util.Collections; import java.util.Map; @@ -31,7 +32,9 @@ void authProvider(@Client("/") HttpClient httpClient) { String expected = """ {"message":"Hello World"}"""; assertEquals(expected, json); - HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.retrieve(createRequest("user", "wrong"))); + HttpRequest request = createRequest("user", "wrong"); + Executable e = () -> client.retrieve(request); + HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, e); assertEquals(HttpStatus.UNAUTHORIZED, ex.getStatus()); } From 69c09f7bae7680b0e2b9ec0d64f7c72d79a0790a Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 16:37:11 +0100 Subject: [PATCH 35/37] AuthenticationProviderUtils and AuthenticationProviderAdapter package private This commits moves AuthenticationProviderUtils and AuthenticationProviderAdapter package private to make io.micronaut.security.authentication. AuthenticationProviderUtils and AuthenticationProviderAdapter package private. --- .../AuthenticationProviderAdapter.java | 14 +++++++------- .../AuthenticationProviderUtils.java | 8 ++++---- .../security/authentication/Authenticator.java | 2 -- ...mperativeAuthenticationProviderUtilsTest.groovy | 1 - 4 files changed, 11 insertions(+), 14 deletions(-) rename security/src/main/java/io/micronaut/security/authentication/{provider => }/AuthenticationProviderAdapter.java (74%) rename security/src/main/java/io/micronaut/security/authentication/{provider => }/AuthenticationProviderUtils.java (88%) diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java similarity index 74% rename from security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java rename to security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java index 44091be707..8e6370dbb0 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderAdapter.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java @@ -13,33 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.security.authentication.provider; +package io.micronaut.security.authentication; import io.micronaut.context.BeanContext; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.security.authentication.AuthenticationRequest; -import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.provider.AuthenticationProvider; +import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; /** - * Adapts between {@link AuthenticationProvider} to {@link ReactiveAuthenticationProvider}. + * Adapts between {@link io.micronaut.security.authentication.provider.AuthenticationProvider} to {@link ReactiveAuthenticationProvider}. * @param Request */ @Internal -public final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { +final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { @NonNull - private final AuthenticationProvider authenticationProvider; + private final io.micronaut.security.authentication.provider.AuthenticationProvider authenticationProvider; private final Scheduler scheduler; public AuthenticationProviderAdapter(BeanContext beanContext, Scheduler scheduler, - @NonNull AuthenticationProvider authenticationProvider) { + @NonNull io.micronaut.security.authentication.provider.AuthenticationProvider authenticationProvider) { this(authenticationProvider, AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, authenticationProvider) ? scheduler : null); } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java similarity index 88% rename from security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java rename to security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java index 09483659ac..b7ebeac0b2 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProviderUtils.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.micronaut.security.authentication.provider; +package io.micronaut.security.authentication; import io.micronaut.context.BeanContext; import io.micronaut.context.BeanRegistration; @@ -22,15 +22,15 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.http.HttpRequest; import io.micronaut.inject.ExecutableMethod; -import io.micronaut.security.authentication.AuthenticationRequest; +import io.micronaut.security.authentication.provider.AuthenticationProvider; import java.lang.annotation.Annotation; /** - * Utility class to check whether {@link AuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}. + * Utility class to check whether {@link io.micronaut.security.authentication.provider.AuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}. */ @Internal -public final class AuthenticationProviderUtils { +final class AuthenticationProviderUtils { private static final String METHOD_AUTHENTICATE = "authenticate"; private AuthenticationProviderUtils() { diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index 79b00da906..ed9eac7aec 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -22,8 +22,6 @@ import io.micronaut.core.util.CollectionUtils; import io.micronaut.scheduling.TaskExecutors; import io.micronaut.security.authentication.provider.AuthenticationProvider; -import io.micronaut.security.authentication.provider.AuthenticationProviderAdapter; -import io.micronaut.security.authentication.provider.AuthenticationProviderUtils; import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import io.micronaut.security.config.AuthenticationStrategy; import io.micronaut.security.config.SecurityConfiguration; diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy index 60550a57ed..237f821ca2 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy @@ -8,7 +8,6 @@ import io.micronaut.core.annotation.NonNull import io.micronaut.core.annotation.Nullable import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.provider.AuthenticationProvider -import io.micronaut.security.authentication.provider.AuthenticationProviderUtils import io.micronaut.test.extensions.spock.annotation.MicronautTest import jakarta.inject.Inject import jakarta.inject.Singleton From dbc26eedf3ac6623257564aca5c1470e3a7df8c4 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 22:12:44 +0100 Subject: [PATCH 36/37] define generic types --- .../CustomAuthenticationProvider.java | 4 +- .../ldap/LdapAuthenticationProvider.java | 9 ++-- .../OauthPasswordAuthenticationProvider.java | 8 ++-- .../OpenIdPasswordAuthenticationProvider.java | 8 ++-- .../security/session/ContextPathSpec.groovy | 4 +- .../AuthenticationProvider.java | 6 ++- .../AuthenticationProviderAdapter.java | 14 +++--- .../AuthenticationProviderUtils.java | 4 +- .../authentication/Authenticator.java | 48 ++++++++++--------- .../BasicAuthAuthenticationFetcher.java | 9 ++-- .../provider/AuthenticationProvider.java | 12 ++--- .../HttpRequestAuthenticationProvider.java | 4 +- ...RequestReactiveAuthenticationProvider.java | 4 +- .../ReactiveAuthenticationProvider.java | 10 ++-- .../security/endpoints/LoginController.java | 8 ++-- .../AuthenticationProviderUserPassword.groovy | 4 +- .../AuthenticationProviderUserPassword.groovy | 4 +- .../AuthenticationProviderUserPassword.groovy | 4 +- ...mperativeAuthenticationProviderSpec.groovy | 8 ++-- ...tiveAuthenticationProviderUtilsTest.groovy | 12 ++--- .../security/MockAuthenticationProvider.java | 4 +- .../CustomAuthenticationProvider.groovy | 9 ++-- .../CustomAuthenticationProvider.groovy | 13 +++-- .../CustomAuthenticationProvider.kt | 10 ++-- .../docs/managementendpoints/LoggersTest.kt | 2 +- .../CustomAuthenticationProvider.kt | 10 ++-- .../SecuredExpressionsTest.kt | 2 +- .../SensitveEndpointRuleReplacementTest.kt | 2 +- .../MockAuthenticationProvider.java | 4 +- .../CustomAuthenticationProvider.java | 15 +++--- .../CustomAuthenticationProvider.java | 7 ++- 31 files changed, 138 insertions(+), 124 deletions(-) diff --git a/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java b/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java index 8f4a562410..875a667dcf 100644 --- a/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java +++ b/security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java @@ -14,10 +14,10 @@ @Requires(property = "spec.name", value = "jwtclaimsoverride") //tag::clazz[] @Singleton -public class CustomAuthenticationProvider implements ReactiveAuthenticationProvider { +public class CustomAuthenticationProvider implements ReactiveAuthenticationProvider { @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create(emitter -> { emitter.next(AuthenticationResponse.success("sherlock", Collections.singletonMap("email", "sherlock@micronaut.example"))); emitter.complete(); diff --git a/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java b/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java index ea0f5109c5..1781962462 100644 --- a/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java +++ b/security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java @@ -47,12 +47,13 @@ /** * Authenticates against an LDAP server using the configuration provided through * {@link LdapConfiguration}. One provider will be created for each configuration. - * @param Request - * + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type * @author James Kleeh * @since 1.0 */ -public class LdapAuthenticationProvider implements AuthenticationProvider, Closeable { +public class LdapAuthenticationProvider implements AuthenticationProvider, Closeable { private static final Logger LOG = LoggerFactory.getLogger(LdapAuthenticationProvider.class); @@ -86,7 +87,7 @@ public LdapAuthenticationProvider(LdapConfiguration configuration, } @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { Flux reactiveSequence = Flux.create(emitter -> { String username = authenticationRequest.getIdentity().toString(); String password = authenticationRequest.getSecret().toString(); diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java index dd6264f1bb..72e9d370b1 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java @@ -37,9 +37,11 @@ * * @author Sergio del Amo * @since 1.2.0 - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type */ -public class OauthPasswordAuthenticationProvider implements AuthenticationProvider { +public class OauthPasswordAuthenticationProvider implements AuthenticationProvider { private final TokenEndpointClient tokenEndpointClient; private final SecureEndpoint secureEndpoint; @@ -61,7 +63,7 @@ public OauthPasswordAuthenticationProvider(TokenEndpointClient tokenEndpointClie } @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { OauthPasswordTokenRequestContext context = new OauthPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration); diff --git a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java index 50e4838f77..7fa3f4fa08 100644 --- a/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java +++ b/security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java @@ -44,9 +44,11 @@ * * @author James Kleeh * @since 1.2.0 - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type */ -public class OpenIdPasswordAuthenticationProvider implements AuthenticationProvider { +public class OpenIdPasswordAuthenticationProvider implements AuthenticationProvider { private final TokenEndpointClient tokenEndpointClient; private final SecureEndpoint secureEndpoint; @@ -77,7 +79,7 @@ public OpenIdPasswordAuthenticationProvider(OauthClientConfiguration clientConfi } @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { OpenIdPasswordTokenRequestContext openIdPasswordTokenRequestContext = new OpenIdPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration); diff --git a/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy b/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy index 2c5505762d..7141e0c43c 100644 --- a/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy +++ b/security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy @@ -71,10 +71,10 @@ class ContextPathSpec extends EmbeddedServerSpecification { @Requires(property = 'spec.name', value = 'ContextPathSpec') @Singleton - static class MockAuthenticationProvider implements ReactiveAuthenticationProvider { + static class MockAuthenticationProvider implements ReactiveAuthenticationProvider { @Override - Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Mono.create(emitter -> { if (authenticationRequest.identity =="user" && authenticationRequest.secret == "password") { emitter.success(AuthenticationResponse.success("user")) diff --git a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java index 95c6d05555..536d7d6632 100644 --- a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java @@ -23,9 +23,11 @@ * @author Sergio del Amo * @author Graeme Rocher * @since 1.0 - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type * @deprecated Use {@link io.micronaut.security.authentication.provider.AuthenticationProvider} for an imperative API or {@link ReactiveAuthenticationProvider} for a reactive API instead. */ @Deprecated(forRemoval = true, since = "4.5.0") -public interface AuthenticationProvider extends ReactiveAuthenticationProvider { +public interface AuthenticationProvider extends ReactiveAuthenticationProvider { } diff --git a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java index 8e6370dbb0..7cc6bd53c2 100644 --- a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderAdapter.java @@ -27,31 +27,33 @@ /** * Adapts between {@link io.micronaut.security.authentication.provider.AuthenticationProvider} to {@link ReactiveAuthenticationProvider}. - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type */ @Internal -final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { +final class AuthenticationProviderAdapter implements ReactiveAuthenticationProvider { @NonNull - private final io.micronaut.security.authentication.provider.AuthenticationProvider authenticationProvider; + private final io.micronaut.security.authentication.provider.AuthenticationProvider authenticationProvider; private final Scheduler scheduler; public AuthenticationProviderAdapter(BeanContext beanContext, Scheduler scheduler, - @NonNull io.micronaut.security.authentication.provider.AuthenticationProvider authenticationProvider) { + @NonNull io.micronaut.security.authentication.provider.AuthenticationProvider authenticationProvider) { this(authenticationProvider, AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, authenticationProvider) ? scheduler : null); } - public AuthenticationProviderAdapter(@NonNull AuthenticationProvider authenticationProvider, + public AuthenticationProviderAdapter(@NonNull AuthenticationProvider authenticationProvider, @Nullable Scheduler scheduler) { this.authenticationProvider = authenticationProvider; this.scheduler = scheduler; } @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { Mono authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(requestContext, authenticationRequest)); return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono; } diff --git a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java index b7ebeac0b2..d29fee4c6e 100644 --- a/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java +++ b/security/src/main/java/io/micronaut/security/authentication/AuthenticationProviderUtils.java @@ -36,8 +36,8 @@ final class AuthenticationProviderUtils { private AuthenticationProviderUtils() { } - public static boolean isAuthenticateBlocking(BeanContext beanContext, - @NonNull AuthenticationProvider authenticationProvider) { + public static boolean isAuthenticateBlocking(BeanContext beanContext, + @NonNull AuthenticationProvider authenticationProvider) { if (isMethodBlocking(beanContext, authenticationProvider, METHOD_AUTHENTICATE, Object.class, AuthenticationRequest.class)) { return true; } diff --git a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java index ed9eac7aec..f3492a1c82 100644 --- a/security/src/main/java/io/micronaut/security/authentication/Authenticator.java +++ b/security/src/main/java/io/micronaut/security/authentication/Authenticator.java @@ -53,10 +53,12 @@ * @author Sergio del Amo * @author Graeme Rocher * @since 1.0 - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type */ @Singleton -public class Authenticator { +public class Authenticator { private static final Logger LOG = LoggerFactory.getLogger(Authenticator.class); @@ -65,12 +67,12 @@ public class Authenticator { * @deprecated Unused. To be removed in the next major version. */ @Deprecated(forRemoval = true, since = "4.5.0") - protected final Collection> authenticationProviders; + protected final Collection> authenticationProviders; - private final List> reactiveAuthenticationProviders; + private final List> reactiveAuthenticationProviders; private final BeanContext beanContext; - private final List> imperativeAuthenticationProviders; + private final List> imperativeAuthenticationProviders; private final SecurityConfiguration securityConfiguration; private final Scheduler scheduler; @@ -84,8 +86,8 @@ public class Authenticator { */ @Inject public Authenticator(BeanContext beanContext, - List> reactiveAuthenticationProviders, - List> authenticationProviders, + List> reactiveAuthenticationProviders, + List> authenticationProviders, @Named(TaskExecutors.BLOCKING) ExecutorService executorService, SecurityConfiguration securityConfiguration) { this.beanContext = beanContext; @@ -102,7 +104,7 @@ public Authenticator(BeanContext beanContext, * @deprecated Use {@link Authenticator#Authenticator(BeanContext, List, List, ExecutorService, SecurityConfiguration)} instead. */ @Deprecated(forRemoval = true, since = "4.5.0") - public Authenticator(Collection> authenticationProviders, + public Authenticator(Collection> authenticationProviders, SecurityConfiguration securityConfiguration) { this.beanContext = null; this.authenticationProviders = authenticationProviders; @@ -119,7 +121,7 @@ public Authenticator(Collection authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { if (CollectionUtils.isEmpty(reactiveAuthenticationProviders) && CollectionUtils.isEmpty(imperativeAuthenticationProviders)) { return Mono.empty(); } @@ -140,10 +142,10 @@ private boolean anyImperativeAuthenticationProviderIsBlocking() { } @NonNull - private static AuthenticationResponse authenticate(@NonNull T requestContext, - @NonNull AuthenticationRequest authenticationRequest, - @NonNull List> authenticationProviders, - @Nullable SecurityConfiguration securityConfiguration) { + private AuthenticationResponse authenticate(@NonNull T requestContext, + @NonNull AuthenticationRequest authenticationRequest, + @NonNull List> authenticationProviders, + @Nullable SecurityConfiguration securityConfiguration) { if (securityConfiguration != null && securityConfiguration.getAuthenticationProviderStrategy() == AuthenticationStrategy.ALL) { return authenticateAll(requestContext, authenticationRequest, authenticationProviders); } @@ -155,9 +157,9 @@ private static AuthenticationResponse authenticate(@NonNull T requestContext } @NonNull - private static AuthenticationResponse authenticateAll(@NonNull T requestContext, - @NonNull AuthenticationRequest authenticationRequest, - @NonNull List> authenticationProviders) { + private AuthenticationResponse authenticateAll(@NonNull T requestContext, + @NonNull AuthenticationRequest authenticationRequest, + @NonNull List> authenticationProviders) { List authenticationResponses = authenticationProviders.stream() .map(provider -> authenticationResponse(provider, requestContext, authenticationRequest)) .toList(); @@ -169,8 +171,8 @@ private static AuthenticationResponse authenticateAll(@NonNull T requestCont : AuthenticationResponse.failure(); } - private List> everyProviderSorted() { - List> providers = new ArrayList<>(reactiveAuthenticationProviders); + private List> everyProviderSorted() { + List> providers = new ArrayList<>(reactiveAuthenticationProviders); if (beanContext != null) { providers.addAll(imperativeAuthenticationProviders.stream() .map(imperativeAuthenticationProvider -> new AuthenticationProviderAdapter<>(beanContext, scheduler, imperativeAuthenticationProvider)).toList()); @@ -180,8 +182,8 @@ private List> everyProviderSorted() { } private Publisher authenticate(T request, - AuthenticationRequest authenticationRequest, - List> providers) { + AuthenticationRequest authenticationRequest, + List> providers) { if (providers == null) { return Flux.empty(); } @@ -243,9 +245,9 @@ private static Mono handleResponse(AuthenticationRespons } @NonNull - private static AuthenticationResponse authenticationResponse(@NonNull AuthenticationProvider provider, - @NonNull T requestContext, - @NonNull AuthenticationRequest authenticationRequest) { + private AuthenticationResponse authenticationResponse(@NonNull AuthenticationProvider provider, + @NonNull T requestContext, + @NonNull AuthenticationRequest authenticationRequest) { try { return provider.authenticate(requestContext, authenticationRequest); } catch (Exception t) { diff --git a/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java b/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java index 16fb7d02cf..b264ec7a6c 100644 --- a/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java +++ b/security/src/main/java/io/micronaut/security/authentication/BasicAuthAuthenticationFetcher.java @@ -31,24 +31,25 @@ * An implementation of {@link AuthenticationFetcher} that decodes a username * and password from the Authorization header and authenticates the credentials * against any {@link io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider}s available. + * @param The HTTP Request Body type */ @Requires(classes = HttpRequest.class) @Requires(property = BasicAuthAuthenticationConfiguration.PREFIX + ".enabled", notEquals = StringUtils.FALSE) @Singleton -public class BasicAuthAuthenticationFetcher implements AuthenticationFetcher> { +public class BasicAuthAuthenticationFetcher implements AuthenticationFetcher> { private static final Logger LOG = LoggerFactory.getLogger(BasicAuthAuthenticationFetcher.class); - private final Authenticator> authenticator; + private final Authenticator, String, String> authenticator; /** * @param authenticator The authenticator to authenticate the credentials */ - public BasicAuthAuthenticationFetcher(Authenticator> authenticator) { + public BasicAuthAuthenticationFetcher(Authenticator, String, String> authenticator) { this.authenticator = authenticator; } @Override - public Publisher fetchAuthentication(HttpRequest request) { + public Publisher fetchAuthentication(HttpRequest request) { Optional credentials = request.getHeaders().getAuthorization().flatMap(BasicAuthUtils::parseCredentials); if (credentials.isPresent()) { diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java index 36ceb141b5..588c4b7d71 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/AuthenticationProvider.java @@ -27,10 +27,12 @@ /** * Defines an API to authenticate a user with the given request. * @since 4.5.0 - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type */ @Indexed(AuthenticationProvider.class) -public interface AuthenticationProvider extends Ordered { +public interface AuthenticationProvider extends Ordered { /** * Authenticates a user with the given request. @@ -38,14 +40,13 @@ public interface AuthenticationProvider extends Ordered { * If not authenticated return {@link AuthenticationResponse#failure()}. * If your implementation is blocking, annotate the overriden method with {@link Blocking} and it will be safely executed on a * dedicated thread in order to not block the main reactive chain of execution. - * * @param requestContext The context request (typically an HTTP Request). * @param authRequest The credentials to authenticate * @return An {@link AuthenticationResponse} indicating either success or failure. */ @NonNull @Executable - AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest); + AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest); /** * Authenticates a user with the given request. @@ -53,13 +54,12 @@ public interface AuthenticationProvider extends Ordered { * If not authenticated return {@link AuthenticationResponse#failure()}. * If your implementation is blocking, annotate the overriden method with {@link Blocking} and it will be safely executed on a * dedicated thread in order to not block the main reactive chain of execution. - * * @param authRequest The credentials to authenticate * @return An {@link AuthenticationResponse} indicating either success or failure. */ @NonNull @Executable - default AuthenticationResponse authenticate(@NonNull AuthenticationRequest authRequest) { + default AuthenticationResponse authenticate(@NonNull AuthenticationRequest authRequest) { return authenticate(null, authRequest); } } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java index 639f092928..299fc3af59 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestAuthenticationProvider.java @@ -21,7 +21,7 @@ * {@link AuthenticationProvider} for {@link HttpRequest}. * @author Sergio del Amo * @since 4.5.0 - * @param The body type + * @param The HTTP Request Body type */ -public interface HttpRequestAuthenticationProvider extends AuthenticationProvider> { +public interface HttpRequestAuthenticationProvider extends AuthenticationProvider, String, String> { } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java index 118fef557a..bd6d9191f7 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/HttpRequestReactiveAuthenticationProvider.java @@ -21,7 +21,7 @@ * {@link ReactiveAuthenticationProvider} for {@link HttpRequest}. * @author Sergio del Amo * @since 4.5.0 - * @param The body type + * @param The HTTP Request Body type */ -public interface HttpRequestReactiveAuthenticationProvider extends ReactiveAuthenticationProvider> { +public interface HttpRequestReactiveAuthenticationProvider extends ReactiveAuthenticationProvider, String, String> { } diff --git a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java index 5ea3a40364..5fe536e7ba 100644 --- a/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java +++ b/security/src/main/java/io/micronaut/security/authentication/provider/ReactiveAuthenticationProvider.java @@ -28,10 +28,12 @@ * Defines a reactive authentication provider. * * @since 4.5.0 - * @param Request + * @param Request Context Type + * @param Authentication Request Identity Type + * @param Authentication Request Secret Type */ @Indexed(ReactiveAuthenticationProvider.class) -public interface ReactiveAuthenticationProvider extends Ordered { +public interface ReactiveAuthenticationProvider extends Ordered { /** * Authenticates a user with the given request. If a successful authentication is @@ -48,7 +50,7 @@ public interface ReactiveAuthenticationProvider extends Ordered { @NonNull @SingleResult Publisher authenticate(@Nullable T requestContext, - @NonNull AuthenticationRequest authenticationRequest); + @NonNull AuthenticationRequest authenticationRequest); /** * Authenticates a user with the given request. If a successful authentication is @@ -63,7 +65,7 @@ Publisher authenticate(@Nullable T requestContext, */ @NonNull @SingleResult - default Publisher authenticate(@NonNull AuthenticationRequest authenticationRequest) { + default Publisher authenticate(@NonNull AuthenticationRequest authenticationRequest) { return authenticate(null, authenticationRequest); } } diff --git a/security/src/main/java/io/micronaut/security/endpoints/LoginController.java b/security/src/main/java/io/micronaut/security/endpoints/LoginController.java index 0b78818792..5b52701917 100644 --- a/security/src/main/java/io/micronaut/security/endpoints/LoginController.java +++ b/security/src/main/java/io/micronaut/security/endpoints/LoginController.java @@ -55,10 +55,10 @@ @Requires(beans = { LoginHandler.class, Authenticator.class }) @Controller("${" + LoginControllerConfigurationProperties.PREFIX + ".path:/login}") @Secured(SecurityRule.IS_ANONYMOUS) -public class LoginController { +public class LoginController { private static final Logger LOG = LoggerFactory.getLogger(LoginController.class); - protected final Authenticator> authenticator; + protected final Authenticator, String, String> authenticator; protected final LoginHandler, MutableHttpResponse> loginHandler; protected final ApplicationEventPublisher loginSuccessfulEventPublisher; protected final ApplicationEventPublisher loginFailedEventPublisher; @@ -69,7 +69,7 @@ public class LoginController { * @param loginSuccessfulEventPublisher Application event publisher for {@link LoginSuccessfulEvent}. * @param loginFailedEventPublisher Application event publisher for {@link LoginFailedEvent}. */ - public LoginController(Authenticator> authenticator, + public LoginController(Authenticator, String, String> authenticator, LoginHandler, MutableHttpResponse> loginHandler, ApplicationEventPublisher loginSuccessfulEventPublisher, ApplicationEventPublisher loginFailedEventPublisher) { @@ -87,7 +87,7 @@ public LoginController(Authenticator> authenticator, @Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON}) @Post @SingleResult - public Publisher> login(@Valid @Body UsernamePasswordCredentials usernamePasswordCredentials, HttpRequest request) { + public Publisher> login(@Valid @Body UsernamePasswordCredentials usernamePasswordCredentials, HttpRequest request) { return Flux.from(authenticator.authenticate(request, usernamePasswordCredentials)) .map(authenticationResponse -> { if (authenticationResponse.isAuthenticated() && authenticationResponse.getAuthentication().isPresent()) { diff --git a/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy index 06397b800e..103660d8e2 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/authentication/AuthenticationProviderUserPassword.groovy @@ -14,11 +14,11 @@ import reactor.core.publisher.FluxSink @Requires(property = "spec.name", value = "authenticationparam") //tag::clazz[] @Singleton -class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { +class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { @Override - Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create({emitter -> if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { emitter.next(AuthenticationResponse.success("user", ["ROLE_USER"])) diff --git a/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy index b60594fb4c..e570127fa3 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/principalparam/AuthenticationProviderUserPassword.groovy @@ -13,10 +13,10 @@ import reactor.core.publisher.FluxSink @Requires(property = "spec.name", value = "principalparam") //tag::clazz[] @Singleton -class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { +class AuthenticationProviderUserPassword implements ReactiveAuthenticationProvider { @Override - Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create({emitter -> if (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) { emitter.next(AuthenticationResponse.success("user")) diff --git a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy index 5ca43b10b2..a2cfb4896b 100644 --- a/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy +++ b/security/src/test/groovy/io/micronaut/docs/security/token/basicauth/AuthenticationProviderUserPassword.groovy @@ -9,10 +9,10 @@ import jakarta.inject.Singleton @Requires(property = "spec.name", value = "docsbasicauth") @Singleton -class AuthenticationProviderUserPassword implements AuthenticationProvider { +class AuthenticationProviderUserPassword implements AuthenticationProvider { @Override AuthenticationResponse authenticate(T requestContext, - AuthenticationRequest authenticationRequest) { + AuthenticationRequest authenticationRequest) { (authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) ? AuthenticationResponse.success("user") : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy index 4f2c21a896..f16bfbf991 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderSpec.groovy @@ -65,12 +65,12 @@ class AuthenticationProviderSpec extends ApplicationContextSpecification { @Requires(property = "spec.name", value = "AuthenticationProviderSpec") @Singleton - static class SimpleAuthenticationProvider implements AuthenticationProvider { + static class SimpleAuthenticationProvider implements AuthenticationProvider { private String executedThreadName @Override @Blocking - AuthenticationResponse authenticate(@Nullable T requestContext, AuthenticationRequest authenticationRequest) { + AuthenticationResponse authenticate(@Nullable T requestContext, AuthenticationRequest authenticationRequest) { executedThreadName = Thread.currentThread().getName() if (authenticationRequest.getIdentity().toString() == 'lebowski' && authenticationRequest.getSecret().toString() == 'thedudeabides') { return AuthenticationResponse.success('lebowski') @@ -82,9 +82,9 @@ class AuthenticationProviderSpec extends ApplicationContextSpecification { @Requires(property = "spec.name", value = "AuthenticationProviderSpec") @Singleton - static class NoOpAuthenticationProvider implements AuthenticationProvider { + static class NoOpAuthenticationProvider implements AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable T requestContext, AuthenticationRequest authenticationRequest) { + AuthenticationResponse authenticate(@Nullable T requestContext, AuthenticationRequest authenticationRequest) { throw AuthenticationResponse.exception() } } diff --git a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy index 237f821ca2..a79cf280f5 100644 --- a/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy +++ b/security/src/test/groovy/io/micronaut/security/authentication/ImperativeAuthenticationProviderUtilsTest.groovy @@ -36,29 +36,29 @@ class AuthenticationProviderUtilsTest extends Specification { @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton - static class BlockingAuthenticationProvider implements AuthenticationProvider { + static class BlockingAuthenticationProvider implements AuthenticationProvider { @Override @Blocking - AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } } @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton - static class BlockingWithGenericAuthenticationProvider implements AuthenticationProvider { + static class BlockingWithGenericAuthenticationProvider implements AuthenticationProvider { @Override @Blocking - AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable T requestContext, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } } @Requires(property = "spec.name", value = "AuthenticationProviderUtilsTest") @Singleton - static class NonBlockingAuthenticationProvider implements AuthenticationProvider { + static class NonBlockingAuthenticationProvider implements AuthenticationProvider { @Override - AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { + AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) { return AuthenticationResponse.failure() } } diff --git a/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java b/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java index 067368f3a5..64c47957f4 100644 --- a/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java +++ b/security/src/test/java/io/micronaut/security/MockAuthenticationProvider.java @@ -28,7 +28,7 @@ /** * Utility class to mock authentication scenarios. */ -public class MockAuthenticationProvider implements ReactiveAuthenticationProvider { +public class MockAuthenticationProvider implements ReactiveAuthenticationProvider { private final List successAuthenticationScenarioList; private final List failedAuthenticationScenarios; @@ -52,7 +52,7 @@ public MockAuthenticationProvider(List successAut } @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create(emitter -> { Optional successAuth = successAuthenticationScenarioList.stream() .filter(scenario -> { diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy index ec3dc5fd0d..d53a875d86 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.groovy @@ -7,18 +7,17 @@ import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse import io.micronaut.security.authentication.provider.AuthenticationProvider +import io.micronaut.security.authentication.provider.HttpRequestAuthenticationProvider import jakarta.inject.Named import jakarta.inject.Singleton @Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] @Singleton -class CustomAuthenticationProvider implements AuthenticationProvider> { - +class CustomAuthenticationProviderCustomAuthenticationProvider implements HttpRequestAuthenticationProvider { @Override - AuthenticationResponse authenticate(HttpRequest httpRequest, - AuthenticationRequest authenticationRequest) { - (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") + AuthenticationResponse authenticate(HttpRequest requestContext, AuthenticationRequest authRequest) { + authRequest.identity == "user" && authRequest.secret == "password" ? AuthenticationResponse.success("user") : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) } diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy index f14edda5e5..c334e638cf 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.groovy @@ -1,11 +1,14 @@ package io.micronaut.security.docs.reactiveauthenticationprovider; -import io.micronaut.context.annotation.Requires; +import io.micronaut.context.annotation.Requires +import io.micronaut.core.annotation.NonNull +import io.micronaut.core.annotation.Nullable; import io.micronaut.core.async.annotation.SingleResult; import io.micronaut.http.HttpRequest; import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; -import io.micronaut.security.authentication.AuthenticationResponse; +import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.provider.HttpRequestReactiveAuthenticationProvider; import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; import jakarta.inject.Singleton; import org.reactivestreams.Publisher; @@ -14,11 +17,11 @@ import reactor.core.publisher.Mono; @Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") //tag::clazz[] @Singleton -class CustomAuthenticationProvider implements ReactiveAuthenticationProvider> { +class CustomAuthenticationProvider implements HttpRequestReactiveAuthenticationProvider { @Override @SingleResult - Publisher authenticate(HttpRequest httpRequest, - AuthenticationRequest authRequest) { + Publisher authenticate(@Nullable HttpRequest httpRequest, + @NonNull AuthenticationRequest authRequest) { AuthenticationResponse rsp = (authRequest.identity == "user" && authRequest.secret == "password") ? AuthenticationResponse.success("user") : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt index 40616e7619..9e076ed4ba 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.kt @@ -6,20 +6,22 @@ import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse import io.micronaut.security.authentication.provider.AuthenticationProvider +import io.micronaut.security.authentication.provider.HttpRequestAuthenticationProvider import jakarta.inject.Singleton @Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] @Singleton class CustomAuthenticationProvider : - AuthenticationProvider> { + HttpRequestAuthenticationProvider { override fun authenticate( - httpRequest: HttpRequest<*>, - authenticationRequest: AuthenticationRequest<*, *> + requestContext: HttpRequest?, + authRequest: AuthenticationRequest ): AuthenticationResponse { - return if (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") + return if (authRequest.identity == "user" && authRequest.secret == "password") AuthenticationResponse.success("user") else AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) } + } //end::clazz[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/managementendpoints/LoggersTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/managementendpoints/LoggersTest.kt index 1e99b041fa..0eebb19328 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/managementendpoints/LoggersTest.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/managementendpoints/LoggersTest.kt @@ -69,7 +69,7 @@ internal class LoggersTest { @Requires(property = "spec.name", value = "LoggersTest") @Singleton internal class AuthenticationProviderUserPassword : - MockAuthenticationProvider>( + MockAuthenticationProvider, Any, Any>( listOf( SuccessAuthenticationScenario("user"), SuccessAuthenticationScenario("system", listOf("ROLE_SYSTEM")) diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt index 33e5e344a1..21126039d9 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.kt @@ -5,6 +5,7 @@ import io.micronaut.http.HttpRequest import io.micronaut.security.authentication.AuthenticationFailureReason import io.micronaut.security.authentication.AuthenticationRequest import io.micronaut.security.authentication.AuthenticationResponse +import io.micronaut.security.authentication.provider.HttpRequestReactiveAuthenticationProvider import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider import jakarta.inject.Singleton import org.reactivestreams.Publisher @@ -13,17 +14,16 @@ import reactor.core.publisher.Mono @Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") //tag::clazz[] @Singleton -class CustomAuthenticationProvider : - ReactiveAuthenticationProvider> { +class CustomAuthenticationProvider : + HttpRequestReactiveAuthenticationProvider { override fun authenticate( - httpRequest: HttpRequest<*>?, - authenticationRequest: AuthenticationRequest<*, *> + requestContext: HttpRequest?, + authenticationRequest: AuthenticationRequest ): Publisher { val rsp = if (authenticationRequest.identity == "user" && authenticationRequest.secret == "password") AuthenticationResponse.success("user") else AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH) return Mono.create { emitter -> emitter.success(rsp) } } - } //end::clazz[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/securedexpressions/SecuredExpressionsTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/securedexpressions/SecuredExpressionsTest.kt index 02cb6a5860..30839e424f 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/securedexpressions/SecuredExpressionsTest.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/securedexpressions/SecuredExpressionsTest.kt @@ -47,7 +47,7 @@ class SecuredExpressionsTest { @Requires(property = "spec.name", value = "docexpressions") @Singleton internal class AuthenticationProviderUserPassword : - MockAuthenticationProvider>( + MockAuthenticationProvider, Any, Any>( Arrays.asList( SuccessAuthenticationScenario("sherlock", listOf("ROLE_ADMIN"), Map.of("email", "sherlock@micronaut.example")), SuccessAuthenticationScenario("moriarty", listOf("ROLE_ADMIN"), Map.of("email", "moriarty@micronaut.example")) diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/sensitiveendpointrule/SensitveEndpointRuleReplacementTest.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/sensitiveendpointrule/SensitveEndpointRuleReplacementTest.kt index 8b16e4a0e3..750876339e 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/sensitiveendpointrule/SensitveEndpointRuleReplacementTest.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/security/docs/sensitiveendpointrule/SensitveEndpointRuleReplacementTest.kt @@ -39,5 +39,5 @@ class SensitiveEndpointRuleReplacementTest { @Singleton @Requires(property = "spec.name", value = "SensitiveEndpointRuleReplacementTest") internal class AuthenticationProviderUserPassword : - MockAuthenticationProvider>(listOf(SuccessAuthenticationScenario("user"))) + MockAuthenticationProvider, Any, Any>(listOf(SuccessAuthenticationScenario("user"))) } diff --git a/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java b/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java index dad24d2c1e..5477b03dbf 100644 --- a/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java +++ b/test-suite-utils-security/src/main/java/io/micronaut/security/testutils/authprovider/MockAuthenticationProvider.java @@ -29,7 +29,7 @@ /** * Utility class to mock authentication scenarios. */ -public class MockAuthenticationProvider implements ReactiveAuthenticationProvider { +public class MockAuthenticationProvider implements ReactiveAuthenticationProvider { private final List successAuthenticationScenarioList; private final List failedAuthenticationScenarios; @@ -53,7 +53,7 @@ public MockAuthenticationProvider(List successAut } @Override - public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { + public Publisher authenticate(T requestContext, AuthenticationRequest authenticationRequest) { return Flux.create(emitter -> { Optional successAuth = successAuthenticationScenarioList.stream() .filter(scenario -> { diff --git a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java index ce8996bede..86ddc3f171 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/blockingauthenticationprovider/CustomAuthenticationProvider.java @@ -5,22 +5,19 @@ import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.provider.AuthenticationProvider; +import io.micronaut.security.authentication.provider.HttpRequestAuthenticationProvider; import jakarta.inject.Singleton; @Requires(property = "spec.name", value = "AuthenticationProviderTest") //tag::clazz[] @Singleton -class CustomAuthenticationProvider implements AuthenticationProvider> { +class CustomAuthenticationProvider implements HttpRequestAuthenticationProvider { @Override - public AuthenticationResponse authenticate(HttpRequest httpRequest, - AuthenticationRequest authenticationRequest) { - return ( - authenticationRequest.getIdentity().equals("user") && - authenticationRequest.getSecret().equals("password") - ) ? AuthenticationResponse.success("user") : - AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); + public AuthenticationResponse authenticate(HttpRequest requestContext, AuthenticationRequest authRequest) { + return (authRequest.getIdentity().equals("user") && authRequest.getSecret().equals("password")) + ? AuthenticationResponse.success("user") + : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); } } //end::clazz[] diff --git a/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java index 98e7f052f5..221605c11e 100644 --- a/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java +++ b/test-suite/src/test/java/io/micronaut/security/docs/reactiveauthenticationprovider/CustomAuthenticationProvider.java @@ -6,7 +6,7 @@ import io.micronaut.security.authentication.AuthenticationFailureReason; import io.micronaut.security.authentication.AuthenticationRequest; import io.micronaut.security.authentication.AuthenticationResponse; -import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider; +import io.micronaut.security.authentication.provider.HttpRequestReactiveAuthenticationProvider; import jakarta.inject.Singleton; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; @@ -14,11 +14,10 @@ @Requires(property = "spec.name", value = "ReactiveAuthenticationProviderTest") //tag::clazz[] @Singleton -class CustomAuthenticationProvider implements ReactiveAuthenticationProvider> { +class CustomAuthenticationProvider implements HttpRequestReactiveAuthenticationProvider { @Override @SingleResult - public Publisher authenticate(HttpRequest httpRequest, - AuthenticationRequest authRequest) { + public Publisher authenticate(HttpRequest requestContext, AuthenticationRequest authRequest) { AuthenticationResponse rsp = authRequest.getIdentity().equals("user") && authRequest.getSecret().equals("password") ? AuthenticationResponse.success("user") : AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH); From df280f7903b89c8019500b3e7ef25721f524f036 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Thu, 14 Dec 2023 22:50:14 +0100 Subject: [PATCH 37/37] fix javadoc --- .../java/io/micronaut/security/endpoints/LoginController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/security/src/main/java/io/micronaut/security/endpoints/LoginController.java b/security/src/main/java/io/micronaut/security/endpoints/LoginController.java index 5b52701917..fc00289c99 100644 --- a/security/src/main/java/io/micronaut/security/endpoints/LoginController.java +++ b/security/src/main/java/io/micronaut/security/endpoints/LoginController.java @@ -49,6 +49,7 @@ * @author Sergio del Amo * @author Graeme Rocher * @since 1.0 + * @param The HTTP Request Body type */ @Requires(property = LoginControllerConfigurationProperties.PREFIX + ".enabled", notEquals = StringUtils.FALSE, defaultValue = StringUtils.TRUE) @Requires(classes = Controller.class)