Skip to content

Commit 0de5040

Browse files
committed
add HttpRequestAuthenticationProvider and HttpRequestReactiveAuthenticationProvider
1 parent c546809 commit 0de5040

File tree

6 files changed

+216
-2
lines changed

6 files changed

+216
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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.provider;
17+
18+
import io.micronaut.http.HttpRequest;
19+
20+
/**
21+
* {@link AuthenticationProvider} for {@link HttpRequest}.
22+
* @author Sergio del Amo
23+
* @since 4.5.0
24+
*/
25+
public interface HttpRequestAuthenticationProvider extends AuthenticationProvider<HttpRequest> {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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.provider;
17+
18+
import io.micronaut.http.HttpRequest;
19+
20+
/**
21+
* {@link ReactiveAuthenticationProvider} for {@link HttpRequest}.
22+
* @author Sergio del Amo
23+
* @since 4.5.0
24+
*/
25+
public interface HttpRequestReactiveAuthenticationProvider extends ReactiveAuthenticationProvider<HttpRequest> {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package io.micronaut.security.authentication.provider
2+
3+
import io.micronaut.context.annotation.Property
4+
import io.micronaut.context.annotation.Requires
5+
import io.micronaut.core.annotation.NonNull
6+
import io.micronaut.core.annotation.Nullable
7+
import io.micronaut.http.HttpRequest
8+
import io.micronaut.http.HttpStatus
9+
import io.micronaut.http.MutableHttpRequest
10+
import io.micronaut.http.annotation.Controller
11+
import io.micronaut.http.annotation.Get
12+
import io.micronaut.http.client.BlockingHttpClient
13+
import io.micronaut.http.client.HttpClient
14+
import io.micronaut.http.client.annotation.Client
15+
import io.micronaut.http.client.exceptions.HttpClientResponseException
16+
import io.micronaut.security.annotation.Secured
17+
import io.micronaut.security.authentication.AuthenticationRequest
18+
import io.micronaut.security.authentication.AuthenticationResponse
19+
import io.micronaut.security.rules.SecurityRule
20+
import io.micronaut.test.extensions.spock.annotation.MicronautTest
21+
import jakarta.inject.Inject
22+
import jakarta.inject.Singleton
23+
import spock.lang.Specification
24+
25+
@Property(name = "spec.name", value = "HttpRequestAuthenticationProviderSpec")
26+
@MicronautTest
27+
class HttpRequestAuthenticationProviderSpec extends Specification {
28+
29+
@Inject
30+
@Client("/")
31+
HttpClient httpClient
32+
33+
void "imperative auth provider"() {
34+
given:
35+
BlockingHttpClient client = httpClient.toBlocking()
36+
String expected = '{"message":"Hello World"}'
37+
38+
when:
39+
String json = client.retrieve(createRequest("sherlock", "password").header("X-API-Version", "v1"))
40+
41+
then:
42+
noExceptionThrown()
43+
expected == json
44+
45+
when:
46+
client.retrieve(createRequest("sherlock", "password"))
47+
48+
then:
49+
HttpClientResponseException ex = thrown()
50+
HttpStatus.UNAUTHORIZED == ex.status
51+
}
52+
53+
private MutableHttpRequest<?> createRequest(String userName, String password) {
54+
HttpRequest.GET("/messages").basicAuth(userName, password)
55+
}
56+
57+
@Requires(property = "spec.name", value = "HttpRequestAuthenticationProviderSpec")
58+
@Singleton
59+
static class SherlockAuthenticationProvider implements HttpRequestAuthenticationProvider {
60+
@Override
61+
AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) {
62+
if (httpRequest.headers.contains("X-API-Version") && authRequest.identity == "sherlock") {
63+
return AuthenticationResponse.success(authRequest.identity.toString())
64+
}
65+
AuthenticationResponse.failure()
66+
}
67+
}
68+
69+
70+
@Requires(property = "spec.name", value = "HttpRequestAuthenticationProviderSpec")
71+
@Controller("/messages")
72+
static class HelloWorldController {
73+
74+
@Secured(SecurityRule.IS_AUTHENTICATED)
75+
@Get
76+
Map<String, Object> index() {
77+
[message: "Hello World"]
78+
}
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.micronaut.security.authentication.provider
2+
3+
import io.micronaut.context.annotation.Property
4+
import io.micronaut.context.annotation.Requires
5+
import io.micronaut.core.annotation.NonNull
6+
import io.micronaut.core.annotation.Nullable
7+
import io.micronaut.core.async.annotation.SingleResult
8+
import io.micronaut.http.HttpRequest
9+
import io.micronaut.http.HttpStatus
10+
import io.micronaut.http.MutableHttpRequest
11+
import io.micronaut.http.annotation.Controller
12+
import io.micronaut.http.annotation.Get
13+
import io.micronaut.http.client.BlockingHttpClient
14+
import io.micronaut.http.client.HttpClient
15+
import io.micronaut.http.client.annotation.Client
16+
import io.micronaut.http.client.exceptions.HttpClientResponseException
17+
import io.micronaut.security.annotation.Secured
18+
import io.micronaut.security.authentication.AuthenticationRequest
19+
import io.micronaut.security.authentication.AuthenticationResponse
20+
import io.micronaut.security.rules.SecurityRule
21+
import io.micronaut.test.extensions.spock.annotation.MicronautTest
22+
import jakarta.inject.Inject
23+
import jakarta.inject.Singleton
24+
import org.reactivestreams.Publisher
25+
import reactor.core.publisher.Mono
26+
import spock.lang.Specification
27+
28+
@Property(name = "spec.name", value = "HttpRequestReactiveAuthenticationProviderSpec")
29+
@MicronautTest
30+
class HttpRequestReactiveAuthenticationProviderSpec extends Specification {
31+
32+
@Inject
33+
@Client("/")
34+
HttpClient httpClient
35+
36+
void "imperative auth provider"() {
37+
given:
38+
BlockingHttpClient client = httpClient.toBlocking()
39+
String expected = '{"message":"Hello World"}'
40+
41+
when:
42+
String json = client.retrieve(createRequest("sherlock", "password").header("X-API-Version", "v1"))
43+
44+
then:
45+
noExceptionThrown()
46+
expected == json
47+
48+
when:
49+
client.retrieve(createRequest("sherlock", "password"))
50+
51+
then:
52+
HttpClientResponseException ex = thrown()
53+
HttpStatus.UNAUTHORIZED == ex.status
54+
}
55+
56+
private MutableHttpRequest<?> createRequest(String userName, String password) {
57+
HttpRequest.GET("/messages").basicAuth(userName, password)
58+
}
59+
60+
@Requires(property = "spec.name", value = "HttpRequestReactiveAuthenticationProviderSpec")
61+
@Singleton
62+
static class SherlockAuthenticationProvider implements HttpRequestReactiveAuthenticationProvider {
63+
@Override
64+
@SingleResult
65+
Publisher<AuthenticationResponse> authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) {
66+
Mono.just((httpRequest.headers.contains("X-API-Version") && authRequest.identity == "sherlock")
67+
? AuthenticationResponse.success(authRequest.identity.toString())
68+
: AuthenticationResponse.failure())
69+
}
70+
}
71+
72+
@Requires(property = "spec.name", value = "HttpRequestReactiveAuthenticationProviderSpec")
73+
@Controller("/messages")
74+
static class HelloWorldController {
75+
76+
@Secured(SecurityRule.IS_AUTHENTICATED)
77+
@Get
78+
Map<String, Object> index() {
79+
[message: "Hello World"]
80+
}
81+
}
82+
}

src/main/docs/guide/authenticationProviders.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
To authenticate users you must provide implementations of api:security.authentication.provider.ReactiveAuthenticationProvider[].
1+
To authenticate users you must provide implementations of api:security.authentication.provider.ReactiveAuthenticationProvider[] or api:security.authentication.provider.HttpRequestReactiveAuthenticationProvider[].
22

33
The following code snippet illustrates a naive implementation:
44

src/main/docs/guide/authenticationProviders/imperativeAuthenticationProviders.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
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:
1+
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:
22

33
snippet::io.micronaut.security.docs.blockingauthenticationprovider.CustomAuthenticationProvider[tags="clazz"]
44

0 commit comments

Comments
 (0)