Skip to content

Commit f6e283f

Browse files
committed
MODULE: oauth2
1 parent b29d1b0 commit f6e283f

Some content is hidden

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

48 files changed

+3335
-11
lines changed

core/src/main/java/feign/BaseBuilder.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,13 @@ B enrich() {
262262
}
263263
});
264264

265-
return clone;
265+
B enrichedBuilder = (B) clone.clone();
266+
267+
for (final Capability capability : capabilities) {
268+
enrichedBuilder = capability.beforeBuild(enrichedBuilder);
269+
}
270+
271+
return enrichedBuilder;
266272
} catch (CloneNotSupportedException e) {
267273
throw new AssertionError(e);
268274
}

core/src/main/java/feign/Capability.java

+14
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,18 @@ default <C> AsyncContextSupplier<C> enrich(AsyncContextSupplier<C> asyncContextS
144144
default MethodInfoResolver enrich(MethodInfoResolver methodInfoResolver) {
145145
return methodInfoResolver;
146146
}
147+
148+
/**
149+
* Hook executed before the build of Feign client is done. Any interceptors or retryers added by
150+
* this method are not enriched by this or any other Capability.
151+
*
152+
* @param baseBuilder feign client builder
153+
* @return enriched builder
154+
* @param <B> builder class
155+
* @param <T> target class
156+
* @see OAuth2Authentication
157+
*/
158+
default <B extends BaseBuilder<B, T>, T> B beforeBuild(B baseBuilder) {
159+
return baseBuilder;
160+
}
147161
}

core/src/test/java/feign/BaseBuilderTest.java

+108-10
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.junit.jupiter.api.Assertions.assertNotSame;
20-
import static org.mockito.Mockito.RETURNS_MOCKS;
2120

21+
import feign.codec.Decoder;
22+
import feign.codec.Encoder;
23+
import feign.codec.ErrorDecoder;
2224
import java.lang.reflect.Field;
2325
import java.util.List;
2426
import org.junit.jupiter.api.Test;
@@ -36,9 +38,20 @@ void checkEnrichTouchesAllAsyncBuilderFields()
3638
14);
3739
}
3840

41+
@Test
42+
void checkEnrichTouchesAllBuilderFields()
43+
throws IllegalArgumentException, IllegalAccessException {
44+
test(
45+
Feign.builder()
46+
.requestInterceptor(template -> {})
47+
.responseInterceptor((ic, c) -> c.next(ic)),
48+
12);
49+
}
50+
3951
private void test(BaseBuilder<?, ?> builder, int expectedFieldsCount)
4052
throws IllegalArgumentException, IllegalAccessException {
41-
Capability mockingCapability = Mockito.mock(Capability.class, RETURNS_MOCKS);
53+
Capability mockingCapability = new MockingCapability();
54+
4255
BaseBuilder<?, ?> enriched = builder.addCapability(mockingCapability).enrich();
4356

4457
List<Field> fields = enriched.getFieldsToEnrich();
@@ -58,13 +71,98 @@ private void test(BaseBuilder<?, ?> builder, int expectedFieldsCount)
5871
}
5972
}
6073

61-
@Test
62-
void checkEnrichTouchesAllBuilderFields()
63-
throws IllegalArgumentException, IllegalAccessException {
64-
test(
65-
Feign.builder()
66-
.requestInterceptor(template -> {})
67-
.responseInterceptor((ic, c) -> c.next(ic)),
68-
12);
74+
private final class MockingCapability implements Capability {
75+
76+
@Override
77+
@SuppressWarnings("unchecked")
78+
public <C> AsyncContextSupplier<C> enrich(AsyncContextSupplier<C> asyncContextSupplier) {
79+
return (AsyncContextSupplier<C>) Mockito.mock(AsyncContextSupplier.class);
80+
}
81+
82+
@Override
83+
public AsyncResponseHandler enrich(AsyncResponseHandler asyncResponseHandler) {
84+
return Mockito.mock(AsyncResponseHandler.class);
85+
}
86+
87+
@Override
88+
public ResponseInterceptor.Chain enrich(ResponseInterceptor.Chain chain) {
89+
return Mockito.mock(ResponseInterceptor.Chain.class);
90+
}
91+
92+
@Override
93+
@SuppressWarnings("unchecked")
94+
public AsyncClient<Object> enrich(AsyncClient<Object> client) {
95+
return (AsyncClient<Object>) Mockito.mock(AsyncClient.class);
96+
}
97+
98+
@Override
99+
public Client enrich(Client client) {
100+
return Mockito.mock(Client.class);
101+
}
102+
103+
@Override
104+
public Contract enrich(Contract contract) {
105+
return Mockito.mock(Contract.class);
106+
}
107+
108+
@Override
109+
public Decoder enrich(Decoder decoder) {
110+
return Mockito.mock(Decoder.class);
111+
}
112+
113+
@Override
114+
public ErrorDecoder enrich(ErrorDecoder decoder) {
115+
return Mockito.mock(ErrorDecoder.class);
116+
}
117+
118+
@Override
119+
public Encoder enrich(Encoder encoder) {
120+
return Mockito.mock(Encoder.class);
121+
}
122+
123+
@Override
124+
public InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {
125+
return Mockito.mock(InvocationHandlerFactory.class);
126+
}
127+
128+
@Override
129+
public Logger.Level enrich(Logger.Level level) {
130+
return Mockito.mock(Logger.Level.class);
131+
}
132+
133+
@Override
134+
public Logger enrich(Logger logger) {
135+
return Mockito.mock(Logger.class);
136+
}
137+
138+
@Override
139+
public MethodInfoResolver enrich(MethodInfoResolver methodInfoResolver) {
140+
return Mockito.mock(MethodInfoResolver.class);
141+
}
142+
143+
@Override
144+
public Request.Options enrich(Request.Options options) {
145+
return Mockito.mock(Request.Options.class);
146+
}
147+
148+
@Override
149+
public QueryMapEncoder enrich(QueryMapEncoder queryMapEncoder) {
150+
return Mockito.mock(QueryMapEncoder.class);
151+
}
152+
153+
@Override
154+
public RequestInterceptor enrich(RequestInterceptor requestInterceptor) {
155+
return Mockito.mock(RequestInterceptor.class);
156+
}
157+
158+
@Override
159+
public ResponseInterceptor enrich(ResponseInterceptor responseInterceptor) {
160+
return Mockito.mock(ResponseInterceptor.class);
161+
}
162+
163+
@Override
164+
public Retryer enrich(Retryer retryer) {
165+
return Mockito.mock(Retryer.class);
166+
}
69167
}
70168
}

oauth2/README.md

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Feign OAuth2
2+
3+
This module extends **Feign** to enable client authentication using
4+
[OAuth2](https://datatracker.ietf.org/doc/html/rfc6749)
5+
and
6+
[OIDC](https://openid.net/specs/openid-connect-core-1_0.html)
7+
frameworks.
8+
9+
It automatically authenticates the client against an **OAuth2/OpenID Connect (OIDC) Authorization Server** using
10+
the `client_credentials` grant type.
11+
Additionally, it manages **access token renewal** seamlessly.
12+
13+
### Supported Authentication Methods
14+
15+
-`client_secret_basic` (OAuth2)
16+
-`client_secret_post` (OAuth2)
17+
-`client_secret_jwt` (OIDC)
18+
-`private_key_jwt` (OIDC)
19+
20+
### 🛠️ Upcoming Features (Planned Support)
21+
22+
- 🚀 `tls_client_auth` (RFC 8705)
23+
- 🚀 `self_signed_tls_client_auth` (RFC 8705)
24+
25+
### Compatibility
26+
27+
Designed to work with most **OAuth2/OpenID Connect** providers.
28+
Out-of-the-box support for:
29+
- **AWS Cognito**
30+
- **Okta Auth0**
31+
- **Keycloak**
32+
33+
## Installation
34+
35+
### With Maven
36+
37+
```xml
38+
<dependencies>
39+
...
40+
<dependency>
41+
<groupId>io.github.openfeign</groupId>
42+
<artifactId>feign-oauth2</artifactId>
43+
</dependency>
44+
...
45+
</dependencies>
46+
```
47+
48+
### With Gradle
49+
50+
```groovy
51+
compile group: 'io.github.openfeign', name: 'feign-oauth2'
52+
```
53+
54+
## Usage
55+
56+
Module provides `OAuth2Authentication` and `OpenIdAuthentication` generic capabilities, but also more specialized factory
57+
classes: `AWSCognitoAuthentication`, `Auth0Authentication` and `KeycloakAuthentication`.
58+
59+
Here an example how to create an authenticated REST client by using **OIDC Discovery** of **Keycloak**:
60+
61+
```java
62+
String issuer = String.format("http://keycloak:8080/realms/%s", "<keycloak realm>");
63+
64+
// Create an authentication
65+
OpenIdAuthentication openIdAuthentication = OpenIdAuthentication.discover(ClientRegistration
66+
.builder()
67+
.credentials(Credentials
68+
.builder()
69+
.clientId("<client ID>")
70+
.clientSecret("<client secret>")
71+
.build())
72+
.providerDetails(ProviderDetails
73+
.builder()
74+
.issuerUri(issuer)
75+
.build())
76+
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
77+
.build());
78+
79+
IcecreamClient client = Feign
80+
.builder()
81+
.encoder(new JacksonEncoder())
82+
.decoder(new JacksonDecoder())
83+
.addCapability(openIdAuthentication) // <-- add authentication to the Feign client
84+
.target(IcecreamClient.class, "http://localhost:5555");
85+
86+
// This call to the service will be authenticated
87+
Collection<Mixin> mixins = client.getAvailableMixins();
88+
```

0 commit comments

Comments
 (0)