Skip to content

Commit 14f234a

Browse files
authored
Add Imperative AuthenticationProvider and Reactive AuthenticationProvider (#1526)
1 parent 60ec78e commit 14f234a

File tree

52 files changed

+1618
-151
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1618
-151
lines changed

security-jwt/src/test/groovy/io/micronaut/docs/jwtclaimsoverride/CustomAuthenticationProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
package io.micronaut.docs.jwtclaimsoverride;
33

44
import io.micronaut.context.annotation.Requires;
5-
import io.micronaut.security.authentication.AuthenticationProvider;
5+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider;
66
import io.micronaut.security.authentication.AuthenticationRequest;
77
import io.micronaut.security.authentication.AuthenticationResponse;
88
import jakarta.inject.Singleton;
@@ -14,10 +14,10 @@
1414
@Requires(property = "spec.name", value = "jwtclaimsoverride")
1515
//tag::clazz[]
1616
@Singleton
17-
public class CustomAuthenticationProvider<T> implements AuthenticationProvider<T> {
17+
public class CustomAuthenticationProvider<T, I, S> implements ReactiveAuthenticationProvider<T, I, S> {
1818

1919
@Override
20-
public Publisher<AuthenticationResponse> authenticate(T httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
20+
public Publisher<AuthenticationResponse> authenticate(T requestContext, AuthenticationRequest<I, S> authenticationRequest) {
2121
return Flux.create(emitter -> {
2222
emitter.next(AuthenticationResponse.success("sherlock", Collections.singletonMap("email", "[email protected]")));
2323
emitter.complete();

security-jwt/src/test/groovy/io/micronaut/security/token/jwt/signature/jwks/JwksSpec.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import io.micronaut.http.annotation.Produces
1818
import io.micronaut.http.client.HttpClient
1919
import io.micronaut.runtime.server.EmbeddedServer
2020
import io.micronaut.security.annotation.Secured
21-
import io.micronaut.security.authentication.AuthenticationProvider
21+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider
2222
import io.micronaut.security.authentication.UsernamePasswordCredentials
2323
import io.micronaut.security.rules.SecurityRule
2424
import io.micronaut.security.testutils.authprovider.MockAuthenticationProvider
@@ -77,7 +77,7 @@ class JwksSpec extends Specification {
7777
RSAJwkProvider,
7878
JwkProvider,
7979
RSASignatureGeneratorConfiguration,
80-
AuthenticationProvider,
80+
ReactiveAuthenticationProvider,
8181
]) {
8282
gatewayEmbeddedServer.applicationContext.getBean(beanClazz)
8383
}

security-ldap/src/main/java/io/micronaut/security/ldap/LdapAuthenticationProvider.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@
4747
/**
4848
* Authenticates against an LDAP server using the configuration provided through
4949
* {@link LdapConfiguration}. One provider will be created for each configuration.
50-
* @param <T> Request
51-
*
50+
* @param <T> Request Context Type
51+
* @param <I> Authentication Request Identity Type
52+
* @param <S> Authentication Request Secret Type
5253
* @author James Kleeh
5354
* @since 1.0
5455
*/
55-
public class LdapAuthenticationProvider<T> implements AuthenticationProvider<T>, Closeable {
56+
public class LdapAuthenticationProvider<T, I, S> implements AuthenticationProvider<T, I, S>, Closeable {
5657

5758
private static final Logger LOG = LoggerFactory.getLogger(LdapAuthenticationProvider.class);
5859

@@ -86,7 +87,7 @@ public LdapAuthenticationProvider(LdapConfiguration configuration,
8687
}
8788

8889
@Override
89-
public Publisher<AuthenticationResponse> authenticate(T httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
90+
public Publisher<AuthenticationResponse> authenticate(T requestContext, AuthenticationRequest<I, S> authenticationRequest) {
9091
Flux<AuthenticationResponse> reactiveSequence = Flux.create(emitter -> {
9192
String username = authenticationRequest.getIdentity().toString();
9293
String password = authenticationRequest.getSecret().toString();

security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OauthPasswordAuthenticationProvider.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616
package io.micronaut.security.oauth2.endpoint.token.request.password;
1717

18-
import io.micronaut.security.authentication.AuthenticationProvider;
1918
import io.micronaut.security.authentication.AuthenticationRequest;
2019
import io.micronaut.security.authentication.AuthenticationResponse;
20+
import io.micronaut.security.authentication.AuthenticationProvider;
2121
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration;
2222
import io.micronaut.security.oauth2.configuration.endpoints.SecureEndpointConfiguration;
2323
import io.micronaut.security.oauth2.endpoint.AuthenticationMethod;
@@ -37,9 +37,11 @@
3737
*
3838
* @author Sergio del Amo
3939
* @since 1.2.0
40-
* @param <T> Request
40+
* @param <T> Request Context Type
41+
* @param <I> Authentication Request Identity Type
42+
* @param <S> Authentication Request Secret Type
4143
*/
42-
public class OauthPasswordAuthenticationProvider<T> implements AuthenticationProvider<T> {
44+
public class OauthPasswordAuthenticationProvider<T, I, S> implements AuthenticationProvider<T, I, S> {
4345

4446
private final TokenEndpointClient tokenEndpointClient;
4547
private final SecureEndpoint secureEndpoint;
@@ -61,7 +63,7 @@ public OauthPasswordAuthenticationProvider(TokenEndpointClient tokenEndpointClie
6163
}
6264

6365
@Override
64-
public Publisher<AuthenticationResponse> authenticate(T httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
66+
public Publisher<AuthenticationResponse> authenticate(T requestContext, AuthenticationRequest<I, S> authenticationRequest) {
6567

6668
OauthPasswordTokenRequestContext context = new OauthPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration);
6769

security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/OpenIdPasswordAuthenticationProvider.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@
4444
*
4545
* @author James Kleeh
4646
* @since 1.2.0
47-
* @param <T> Request
47+
* @param <T> Request Context Type
48+
* @param <I> Authentication Request Identity Type
49+
* @param <S> Authentication Request Secret Type
4850
*/
49-
public class OpenIdPasswordAuthenticationProvider<T> implements AuthenticationProvider<T> {
51+
public class OpenIdPasswordAuthenticationProvider<T, I, S> implements AuthenticationProvider<T, I, S> {
5052

5153
private final TokenEndpointClient tokenEndpointClient;
5254
private final SecureEndpoint secureEndpoint;
@@ -77,12 +79,12 @@ public OpenIdPasswordAuthenticationProvider(OauthClientConfiguration clientConfi
7779
}
7880

7981
@Override
80-
public Publisher<AuthenticationResponse> authenticate(T httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
82+
public Publisher<AuthenticationResponse> authenticate(T requestContext, AuthenticationRequest<I, S> authenticationRequest) {
8183

82-
OpenIdPasswordTokenRequestContext requestContext = new OpenIdPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration);
84+
OpenIdPasswordTokenRequestContext openIdPasswordTokenRequestContext = new OpenIdPasswordTokenRequestContext(authenticationRequest, secureEndpoint, clientConfiguration);
8385

8486
return Flux.from(
85-
tokenEndpointClient.sendRequest(requestContext))
87+
tokenEndpointClient.sendRequest(openIdPasswordTokenRequestContext))
8688
.switchMap(response -> {
8789
Optional<JWT> jwt = tokenResponseValidator.validate(clientConfiguration, openIdProviderMetadata, response, null);
8890
if (jwt.isPresent()) {

security-oauth2/src/main/java/io/micronaut/security/oauth2/endpoint/token/request/password/PasswordGrantFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import io.micronaut.context.annotation.Requires;
2222
import io.micronaut.core.annotation.Internal;
2323
import io.micronaut.core.annotation.Nullable;
24-
import io.micronaut.security.authentication.AuthenticationProvider;
24+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider;
2525
import io.micronaut.security.oauth2.client.OpenIdProviderMetadata;
2626
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration;
2727
import io.micronaut.security.oauth2.endpoint.token.request.TokenEndpointClient;
@@ -31,7 +31,7 @@
3131
import io.micronaut.security.oauth2.endpoint.token.response.validation.OpenIdTokenResponseValidator;
3232

3333
/**
34-
* Factory creating {@link AuthenticationProvider} beans that delegate
34+
* Factory creating {@link ReactiveAuthenticationProvider} beans that delegate
3535
* to the password grant flow of an OAuth 2.0 or OpenID provider.
3636
*
3737
* @author James Kleeh
@@ -61,7 +61,7 @@ class PasswordGrantFactory {
6161
*/
6262
@EachBean(OauthClientConfiguration.class)
6363
@Requires(condition = PasswordGrantCondition.class)
64-
AuthenticationProvider passwordGrantProvider(
64+
ReactiveAuthenticationProvider passwordGrantProvider(
6565
@Parameter OauthClientConfiguration clientConfiguration,
6666
@Parameter @Nullable OauthAuthenticationMapper authenticationMapper,
6767
@Parameter @Nullable OpenIdAuthenticationMapper openIdAuthenticationMapper,

security-oauth2/src/test/groovy/io/micronaut/security/oauth2/client/condition/PasswordGrantConditionSpec.groovy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import io.micronaut.context.annotation.Requires
66
import io.micronaut.core.annotation.Nullable
77
import io.micronaut.json.JsonMapper
88
import io.micronaut.json.tree.JsonNode
9-
import io.micronaut.security.authentication.AuthenticationProvider
9+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider
1010
import io.micronaut.security.authentication.AuthenticationRequest
1111
import io.micronaut.security.authentication.AuthenticationResponse
1212
import io.micronaut.security.oauth2.configuration.OauthClientConfiguration
@@ -64,10 +64,10 @@ class PasswordGrantConditionSpec extends Specification {
6464
ApplicationContext ctx = ApplicationContext.run(PROPS + properties)
6565

6666
expect:
67-
ctx.containsBean(AuthenticationProvider)
67+
ctx.containsBean(ReactiveAuthenticationProvider)
6868

6969
when:
70-
ctx.getBean(AuthenticationProvider)
70+
ctx.getBean(ReactiveAuthenticationProvider)
7171

7272
then:
7373
noExceptionThrown()

security-session/src/test/groovy/io/micronaut/security/session/ContextPathSpec.groovy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import io.micronaut.http.annotation.Get
99
import io.micronaut.http.annotation.Produces
1010
import io.micronaut.http.client.exceptions.HttpClientResponseException
1111
import io.micronaut.security.annotation.Secured
12-
import io.micronaut.security.authentication.AuthenticationProvider
12+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider
1313
import io.micronaut.security.authentication.AuthenticationRequest
1414
import io.micronaut.security.authentication.AuthenticationResponse
1515
import io.micronaut.security.rules.SecurityRule
@@ -71,10 +71,10 @@ class ContextPathSpec extends EmbeddedServerSpecification {
7171

7272
@Requires(property = 'spec.name', value = 'ContextPathSpec')
7373
@Singleton
74-
static class MockAuthenticationProvider<T> implements AuthenticationProvider<T> {
74+
static class MockAuthenticationProvider<T, I, S> implements ReactiveAuthenticationProvider<T, I, S> {
7575

7676
@Override
77-
Publisher<AuthenticationResponse> authenticate(T httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
77+
Publisher<AuthenticationResponse> authenticate(T requestContext, AuthenticationRequest<I, S> authenticationRequest) {
7878
return Mono.<AuthenticationResponse>create(emitter -> {
7979
if (authenticationRequest.identity =="user" && authenticationRequest.secret == "password") {
8080
emitter.success(AuthenticationResponse.success("user"))

security/src/main/java/io/micronaut/security/authentication/AuthenticationProvider.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,19 @@
1515
*/
1616
package io.micronaut.security.authentication;
1717

18-
import io.micronaut.core.annotation.Nullable;
19-
import org.reactivestreams.Publisher;
18+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider;
2019

2120
/**
2221
* Defines an authentication provider.
2322
*
2423
* @author Sergio del Amo
2524
* @author Graeme Rocher
2625
* @since 1.0
27-
* @param <T> Request
26+
* @param <T> Request Context Type
27+
* @param <I> Authentication Request Identity Type
28+
* @param <S> Authentication Request Secret Type
29+
* @deprecated Use {@link io.micronaut.security.authentication.provider.AuthenticationProvider} for an imperative API or {@link ReactiveAuthenticationProvider} for a reactive API instead.
2830
*/
29-
public interface AuthenticationProvider<T> {
30-
31-
/**
32-
* Authenticates a user with the given request. If a successful authentication is
33-
* returned, the object must be an instance of {@link Authentication}.
34-
*
35-
* Publishers <b>MUST emit cold observables</b>! This method will be called for
36-
* all authenticators for each authentication request and it is assumed no work
37-
* will be done until the publisher is subscribed to.
38-
*
39-
* @param httpRequest The http request
40-
* @param authenticationRequest The credentials to authenticate
41-
* @return A publisher that emits 0 or 1 responses
42-
*/
43-
Publisher<AuthenticationResponse> authenticate(@Nullable T httpRequest, AuthenticationRequest<?, ?> authenticationRequest);
31+
@Deprecated(forRemoval = true, since = "4.5.0")
32+
public interface AuthenticationProvider<T, I, S> extends ReactiveAuthenticationProvider<T, I, S> {
4433
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2017-2023 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.security.authentication;
17+
18+
import io.micronaut.context.BeanContext;
19+
import io.micronaut.core.annotation.Internal;
20+
import io.micronaut.core.annotation.NonNull;
21+
import io.micronaut.core.annotation.Nullable;
22+
import io.micronaut.security.authentication.provider.AuthenticationProvider;
23+
import io.micronaut.security.authentication.provider.ReactiveAuthenticationProvider;
24+
import org.reactivestreams.Publisher;
25+
import reactor.core.publisher.Mono;
26+
import reactor.core.scheduler.Scheduler;
27+
28+
/**
29+
* Adapts between {@link io.micronaut.security.authentication.provider.AuthenticationProvider} to {@link ReactiveAuthenticationProvider}.
30+
* @param <T> Request Context Type
31+
* @param <I> Authentication Request Identity Type
32+
* @param <S> Authentication Request Secret Type
33+
*/
34+
@Internal
35+
final class AuthenticationProviderAdapter<T, I, S> implements ReactiveAuthenticationProvider<T, I, S> {
36+
37+
@NonNull
38+
private final io.micronaut.security.authentication.provider.AuthenticationProvider<T, I, S> authenticationProvider;
39+
40+
private final Scheduler scheduler;
41+
42+
public AuthenticationProviderAdapter(BeanContext beanContext,
43+
Scheduler scheduler,
44+
@NonNull io.micronaut.security.authentication.provider.AuthenticationProvider<T, I, S> authenticationProvider) {
45+
this(authenticationProvider,
46+
AuthenticationProviderUtils.isAuthenticateBlocking(beanContext, authenticationProvider) ? scheduler : null);
47+
}
48+
49+
public AuthenticationProviderAdapter(@NonNull AuthenticationProvider<T, I, S> authenticationProvider,
50+
@Nullable Scheduler scheduler) {
51+
this.authenticationProvider = authenticationProvider;
52+
this.scheduler = scheduler;
53+
}
54+
55+
@Override
56+
public Publisher<AuthenticationResponse> authenticate(T requestContext, AuthenticationRequest<I, S> authenticationRequest) {
57+
Mono<AuthenticationResponse> authenticationResponseMono = Mono.fromCallable(() -> authenticationProvider.authenticate(requestContext, authenticationRequest));
58+
return scheduler != null ? authenticationResponseMono.subscribeOn(scheduler) : authenticationResponseMono;
59+
}
60+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2017-2023 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.security.authentication;
17+
18+
import io.micronaut.context.BeanContext;
19+
import io.micronaut.context.BeanRegistration;
20+
import io.micronaut.core.annotation.Blocking;
21+
import io.micronaut.core.annotation.Internal;
22+
import io.micronaut.core.annotation.NonNull;
23+
import io.micronaut.http.HttpRequest;
24+
import io.micronaut.inject.ExecutableMethod;
25+
import io.micronaut.security.authentication.provider.AuthenticationProvider;
26+
27+
import java.lang.annotation.Annotation;
28+
29+
/**
30+
* Utility class to check whether {@link io.micronaut.security.authentication.provider.AuthenticationProvider#authenticate(Object, AuthenticationRequest)} is annotated with {@link Blocking}.
31+
*/
32+
@Internal
33+
final class AuthenticationProviderUtils {
34+
private static final String METHOD_AUTHENTICATE = "authenticate";
35+
36+
private AuthenticationProviderUtils() {
37+
}
38+
39+
public static <B, I, S> boolean isAuthenticateBlocking(BeanContext beanContext,
40+
@NonNull AuthenticationProvider<B, I, S> authenticationProvider) {
41+
if (isMethodBlocking(beanContext, authenticationProvider, METHOD_AUTHENTICATE, Object.class, AuthenticationRequest.class)) {
42+
return true;
43+
}
44+
return isMethodBlocking(beanContext, authenticationProvider, METHOD_AUTHENTICATE, HttpRequest.class, AuthenticationRequest.class);
45+
}
46+
47+
private static boolean isMethodBlocking(BeanContext beanContext,
48+
@NonNull Object bean,
49+
String methodName,
50+
Class<?>... argumentTypes) {
51+
return beanContext.findBeanRegistration(bean)
52+
.map(BeanRegistration::getBeanDefinition)
53+
.map(beanDefinition -> beanDefinition
54+
.findMethod(methodName, argumentTypes)
55+
.filter(AuthenticationProviderUtils::isBlockingMethod)
56+
.isPresent())
57+
.orElse(false);
58+
}
59+
60+
private static boolean isBlockingMethod(ExecutableMethod<?, ?> executableMethod) {
61+
return isMethodAnnotatedWith(executableMethod, Blocking.class);
62+
}
63+
64+
private static boolean isMethodAnnotatedWith(ExecutableMethod<?, ?> executableMethod, Class<? extends Annotation> annotationClass) {
65+
return executableMethod.getAnnotationMetadata().hasAnnotation(annotationClass);
66+
}
67+
}

0 commit comments

Comments
 (0)