Skip to content

Commit ab6aeec

Browse files
committed
feat: oauth2 client credentials, verify JWT token
1 parent dd64d97 commit ab6aeec

3 files changed

Lines changed: 67 additions & 26 deletions

File tree

.github/CODEOWNERS

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This file provides an overview of responsibilities in this repository.
2+
3+
# Please note that this file does not represent all contributions to the code. What persons and organizations
4+
# actually contributed to each file can be seen on GitHub and is documented in the license headers.
5+
6+
# Linked persons with write permissions are automatically added as reviewers when a pull request is opened.
7+
8+
# Each line is a file pattern followed by one or more contact persons. The last matching pattern has the most precedence.
9+
# For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/.
10+
11+
* @eclipse-dataplane-core/technology-dataplane-core-committers

dataplane-sdk-core/src/main/java/org/eclipse/dataplane/domain/registration/Oauth2ClientCredentialsAuthorization.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
package org.eclipse.dataplane.domain.registration;
1616

1717
import com.fasterxml.jackson.databind.ObjectMapper;
18-
import com.nimbusds.jwt.SignedJWT;
18+
import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
19+
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
1920
import org.eclipse.dataplane.domain.Result;
2021

2122
import java.net.URI;
@@ -25,14 +26,21 @@
2526
import java.net.http.HttpResponse;
2627
import java.nio.charset.StandardCharsets;
2728
import java.util.Map;
29+
import java.util.Objects;
2830
import java.util.stream.Collectors;
2931

32+
import static com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector.fromJWKSource;
3033
import static jakarta.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
3134

3235
public class Oauth2ClientCredentialsAuthorization implements Authorization {
3336

3437
private final HttpClient httpClient = HttpClient.newHttpClient();
3538
private final ObjectMapper objectMapper = new ObjectMapper();
39+
private final String jwksUri;
40+
41+
public Oauth2ClientCredentialsAuthorization(String jwksUri) {
42+
this.jwksUri = Objects.requireNonNull(jwksUri, "jwksUri is required for JWT verification");
43+
}
3644

3745
@Override
3846
public String type() {
@@ -75,12 +83,16 @@ public Result<String> authorizationHeader(AuthorizationProfile profile) {
7583
public Result<String> extractCallerId(String authorizationHeader) {
7684
try {
7785
var token = authorizationHeader.substring("Bearer ".length());
78-
var jwt = SignedJWT.parse(token);
79-
var sub = jwt.getJWTClaimsSet().getClaims().get("sub");
80-
if (sub instanceof String callerId) {
81-
return Result.success(callerId);
86+
87+
var jwtProcessor = new DefaultJWTProcessor<>();
88+
jwtProcessor.setJWSKeySelector(fromJWKSource(JWKSourceBuilder.create(URI.create(jwksUri).toURL()).build()));
89+
90+
var claimsSet = jwtProcessor.process(token, null);
91+
var sub = claimsSet.getSubject();
92+
if (sub == null) {
93+
return Result.failure(new RuntimeException("JWT missing sub claim"));
8294
}
83-
return Result.failure(new RuntimeException("JWT sub claim %s is not a string".formatted(sub)));
95+
return Result.success(sub);
8496
} catch (Exception e) {
8597
return Result.failure(e);
8698
}

e2e-tests/src/test/java/org/eclipse/dataplane/scenario/AuthorizationOauth2Test.java

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
import com.nimbusds.jose.JOSEObjectType;
1919
import com.nimbusds.jose.JWSAlgorithm;
2020
import com.nimbusds.jose.JWSHeader;
21-
import com.nimbusds.jose.crypto.MACSigner;
21+
import com.nimbusds.jose.crypto.RSASSASigner;
22+
import com.nimbusds.jose.jwk.JWKSet;
23+
import com.nimbusds.jose.jwk.RSAKey;
24+
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
2225
import com.nimbusds.jwt.JWTClaimsSet;
2326
import com.nimbusds.jwt.SignedJWT;
2427
import jakarta.ws.rs.Consumes;
2528
import jakarta.ws.rs.FormParam;
29+
import jakarta.ws.rs.GET;
2630
import jakarta.ws.rs.POST;
2731
import jakarta.ws.rs.Path;
2832
import jakarta.ws.rs.Produces;
@@ -53,32 +57,39 @@
5357
public class AuthorizationOauth2Test {
5458

5559
private final HttpServer httpServer = new HttpServer();
56-
private final Oauth2ClientCredentialsAuthorization oauth2ClientCredentialsAuthorization = new Oauth2ClientCredentialsAuthorization();
60+
private Oauth2ClientCredentialsAuthorization oauth2ClientCredentialsAuthorization;
5761
private ControlPlane controlPlane;
58-
private final Dataplane dataPlane = Dataplane.newInstance()
59-
.id("data-plane")
60-
.registerAuthorization(oauth2ClientCredentialsAuthorization)
61-
.onPrepare(dataFlow -> {
62-
dataFlow.transitionToPreparing();
63-
return Result.success(dataFlow);
64-
})
65-
.build();
62+
private Dataplane dataPlane;
63+
private Oauth2TokenController tokenController;
6664

6765
private final String clientId = UUID.randomUUID().toString();
6866
private final String clientSecret = UUID.randomUUID().toString();
6967

7068
@BeforeEach
71-
void setUp() {
69+
void setUp() throws JOSEException {
7270
httpServer.start();
7371

72+
tokenController = new Oauth2TokenController(clientId, clientSecret);
73+
var jwksUri = "http://localhost:" + httpServer.port() + "/oauth2/jwks";
74+
oauth2ClientCredentialsAuthorization = new Oauth2ClientCredentialsAuthorization(jwksUri);
75+
76+
dataPlane = Dataplane.newInstance()
77+
.id("data-plane")
78+
.registerAuthorization(oauth2ClientCredentialsAuthorization)
79+
.onPrepare(dataFlow -> {
80+
dataFlow.transitionToPreparing();
81+
return Result.success(dataFlow);
82+
})
83+
.build();
84+
7485
controlPlane = ControlPlane.newInstance()
7586
.authorizationTokenGenerator(() -> oauth2ClientCredentialsAuthorization.authorizationHeader(oauth2AuthorizationProfile()))
7687
.build();
7788

7889
controlPlane.initialize(httpServer, "/data-plane", "/data-plane");
7990

8091
httpServer.deploy("/data-plane", new DataPlaneSignalingApiController(dataPlane));
81-
httpServer.deploy("/oauth2", new Oauth2TokenController(clientId, clientSecret));
92+
httpServer.deploy("/oauth2", tokenController);
8293
}
8394

8495
@AfterEach
@@ -126,13 +137,14 @@ private AuthorizationProfile oauth2AuthorizationProfile() {
126137
@Path("/")
127138
public static class Oauth2TokenController {
128139

129-
130140
private final String clientId;
131141
private final String clientSecret;
142+
private final RSAKey rsaKey;
132143

133-
public Oauth2TokenController(String clientId, String clientSecret) {
144+
public Oauth2TokenController(String clientId, String clientSecret) throws JOSEException {
134145
this.clientId = clientId;
135146
this.clientSecret = clientSecret;
147+
this.rsaKey = new RSAKeyGenerator(2048).keyID("key-1").generate();
136148
}
137149

138150
@POST
@@ -153,29 +165,35 @@ public Response token(
153165
return Response.ok(responseBody).build();
154166
}
155167

168+
@GET
169+
@Path("/jwks")
170+
@Produces(APPLICATION_JSON)
171+
public Response jwks() {
172+
var jwkSet = new JWKSet(rsaKey.toPublicJWK());
173+
return Response.ok(jwkSet.toJSONObject()).build();
174+
}
175+
156176
public String issueJwt(String sub) {
157177
var now = new Date();
158178

159179
var claimsSet = new JWTClaimsSet.Builder()
160180
.subject(sub)
161181
.issuer("https://your-app.com")
162-
.audience("https://api.your-app.com")
163-
.expirationTime(new Date(now.getTime() + 1000))
182+
.expirationTime(new Date(now.getTime() + 60_000))
164183
.notBeforeTime(now)
165184
.issueTime(now)
166185
.jwtID(UUID.randomUUID().toString())
167186
.build();
168187

169-
var header = new JWSHeader.Builder(JWSAlgorithm.HS256)
188+
var header = new JWSHeader.Builder(JWSAlgorithm.RS256)
189+
.keyID(rsaKey.getKeyID())
170190
.type(JOSEObjectType.JWT)
171191
.build();
172192

173193
var signedJwt = new SignedJWT(header, claimsSet);
174194

175-
var secret = "random-256-bit-secret-" + UUID.randomUUID();
176195
try {
177-
var signer = new MACSigner(secret.getBytes());
178-
signedJwt.sign(signer);
196+
signedJwt.sign(new RSASSASigner(rsaKey));
179197
} catch (JOSEException e) {
180198
throw new RuntimeException(e);
181199
}

0 commit comments

Comments
 (0)