Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for OIDC public-key property #7750

Merged
merged 1 commit into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion extensions/oidc/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
<systemPropertyVariables>
<keycloak.not.required/>
</systemPropertyVariables>
<includes>
<include>ServicePublicKeyTestCase.java</include>
</includes>
</configuration>
</plugin>
</plugins>
Expand All @@ -121,6 +126,9 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
<excludes>
<exclude>ServicePublicKeyTestCase.java</exclude>
</excludes>
<systemPropertyVariables>
<keycloak.url>${keycloak.url}</keycloak.url>
</systemPropertyVariables>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,25 @@ public class KeycloakDevModeRealmResourceManager implements QuarkusTestResourceL

@Override
public Map<String, String> start() {

try {

RealmRepresentation realm = createRealm(KEYCLOAK_REALM);

realm.getClients().add(createClient("client-dev-mode"));
realm.getUsers().add(createUser("alice-dev-mode", "user"));

RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
.contentType("application/json")
.body(JsonSerialization.writeValueAsBytes(realm))
.when()
.post(KEYCLOAK_SERVER_URL + "/admin/realms").then()
.statusCode(201);
} catch (IOException e) {
throw new RuntimeException(e);
if (System.getProperty("keycloak.not.required") == null) {
try {

RealmRepresentation realm = createRealm(KEYCLOAK_REALM);

realm.getClients().add(createClient("client-dev-mode"));
realm.getUsers().add(createUser("alice-dev-mode", "user"));

RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
.contentType("application/json")
.body(JsonSerialization.writeValueAsBytes(realm))
.when()
.post(KEYCLOAK_SERVER_URL + "/admin/realms").then()
.statusCode(201);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return Collections.emptyMap();
}
Expand Down Expand Up @@ -113,11 +114,12 @@ private static UserRepresentation createUser(String username, String... realmRol

@Override
public void stop() {

RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
.when()
.delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).thenReturn().prettyPrint();
if (System.getProperty("keycloak.not.required") == null) {
RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
.when()
.delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).thenReturn().prettyPrint();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.oidc.test;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.quarkus.security.Authenticated;

@Path("/service")
@Authenticated
public class ServiceProtectedResource {

@Inject
JsonWebToken accessToken;

@GET
public String getName() {
return accessToken.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.quarkus.oidc.test;

import java.io.IOException;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.smallrye.jwt.build.Jwt;

public class ServicePublicKeyTestCase {

private static Class<?>[] testClasses = {
ServiceProtectedResource.class
};

@RegisterExtension
static final QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(testClasses)
.addAsResource("privateKey.pem")
.addAsResource("application-service-public-key.properties", "application.properties"));

@Test
public void testAccessTokenInjection() throws IOException, InterruptedException {
String jwt = Jwt.claims().preferredUserName("alice").sign();
Assertions.assertEquals("alice", RestAssured.given().auth()
.oauth2(jwt)
.get("/service").getBody().asString());
}

@Test
public void testModifiedSignature() throws IOException, InterruptedException {
String jwt = Jwt.claims().preferredUserName("alice").sign();
// the last section of the jwt token is a signature
Response r = RestAssured.given().auth()
.oauth2(jwt + "1")
.get("/service");
Assertions.assertEquals(403, r.getStatusCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
quarkus.oidc.client-id=test
quarkus.oidc.public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEqFyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwRTYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5eUF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYnsIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9xnQIDAQAB
smallrye.jwt.sign.key-location=/privateKey.pem
28 changes: 28 additions & 0 deletions extensions/oidc/deployment/src/test/resources/privateKey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa
PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H
OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN
qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh
nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM
uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6
oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv
6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY
URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6
96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB
Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3
zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF
KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP
iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B
m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS
34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG
5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2
tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL
WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y
b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09
nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB
MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d
Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe
Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt
FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8
f3cg+fr8aou7pr9SHhJlZCU=
-----END PRIVATE KEY-----
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.oauth2.AccessToken;
import io.vertx.ext.auth.oauth2.impl.OAuth2AuthProviderImpl;
import io.vertx.ext.jwt.JWT;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
Expand Down Expand Up @@ -53,13 +56,22 @@ public SecurityIdentity get() {
return authenticate(request, vertxContext);
}

@SuppressWarnings("deprecation")
private CompletableFuture<SecurityIdentity> authenticate(TokenAuthenticationRequest request,
RoutingContext vertxContext) {
CompletableFuture<SecurityIdentity> result = new CompletableFuture<>();
TenantConfigContext resolvedContext = tenantResolver.resolve(vertxContext, true);
OidcTenantConfig config = resolvedContext.oidcConfig;

if (resolvedContext.oidcConfig.publicKey.isPresent()) {
return validateTokenWithoutOidcServer(request, resolvedContext);
} else {
return validateTokenWithOidcServer(request, resolvedContext);
}
}

@SuppressWarnings("deprecation")
private CompletableFuture<SecurityIdentity> validateTokenWithOidcServer(TokenAuthenticationRequest request,
TenantConfigContext resolvedContext) {

CompletableFuture<SecurityIdentity> result = new CompletableFuture<>();
resolvedContext.auth.decodeToken(request.getToken().getToken(),
new Handler<AsyncResult<AccessToken>>() {
@Override
Expand All @@ -68,42 +80,73 @@ public void handle(AsyncResult<AccessToken> event) {
result.completeExceptionally(new AuthenticationFailedException(event.cause()));
return;
}
AccessToken token = event.result();
try {
OidcUtils.validateClaims(config.getToken(), token.accessToken());
} catch (OIDCException e) {
result.completeExceptionally(new AuthenticationFailedException(e));
return;
}

QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
builder.addCredential(request.getToken());

JsonWebToken jwtPrincipal;
try {
JwtClaims jwtClaims = JwtClaims.parse(token.accessToken().encode());
jwtClaims.setClaim(Claims.raw_token.name(), request.getToken().getToken());
jwtPrincipal = new OidcJwtCallerPrincipal(jwtClaims, request.getToken(),
config.token.principalClaim.isPresent() ? config.token.principalClaim.get() : null);
} catch (InvalidJwtException e) {
result.completeExceptionally(new AuthenticationFailedException(e));
return;
}
builder.setPrincipal(jwtPrincipal);
JsonObject tokenJson = event.result().accessToken();
try {
String clientId = config.getClientId().isPresent() ? config.getClientId().get() : null;
for (String role : OidcUtils.findRoles(clientId, config.getRoles(), token.accessToken())) {
builder.addRole(role);
}
} catch (Exception e) {
result.completeExceptionally(new ForbiddenException(e));
return;
result.complete(validateAndCreateIdentity(request, resolvedContext.oidcConfig, tokenJson));
} catch (Throwable ex) {
result.completeExceptionally(ex);
}

result.complete(builder.build());
}
});

return result;
}

private CompletableFuture<SecurityIdentity> validateTokenWithoutOidcServer(TokenAuthenticationRequest request,
TenantConfigContext resolvedContext) {
CompletableFuture<SecurityIdentity> result = new CompletableFuture<>();

OAuth2AuthProviderImpl auth = ((OAuth2AuthProviderImpl) resolvedContext.auth);
JWT jwt = auth.getJWT();
JsonObject tokenJson = null;
try {
tokenJson = jwt.decode(request.getToken().getToken());
} catch (Throwable ex) {
result.completeExceptionally(new AuthenticationFailedException(ex));
return result;
}
if (jwt.isExpired(tokenJson, auth.getConfig().getJWTOptions())) {
result.completeExceptionally(new AuthenticationFailedException());
} else {
try {
result.complete(validateAndCreateIdentity(request, resolvedContext.oidcConfig, tokenJson));
} catch (Throwable ex) {
result.completeExceptionally(ex);
}
}
return result;
}

private QuarkusSecurityIdentity validateAndCreateIdentity(TokenAuthenticationRequest request,
OidcTenantConfig config, JsonObject tokenJson)
throws Exception {
try {
OidcUtils.validateClaims(config.getToken(), tokenJson);
} catch (OIDCException e) {
throw new AuthenticationFailedException(e);
}

QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
builder.addCredential(request.getToken());

JsonWebToken jwtPrincipal;
try {
JwtClaims jwtClaims = JwtClaims.parse(tokenJson.encode());
jwtClaims.setClaim(Claims.raw_token.name(), request.getToken().getToken());
jwtPrincipal = new OidcJwtCallerPrincipal(jwtClaims, request.getToken(),
config.token.principalClaim.isPresent() ? config.token.principalClaim.get() : null);
} catch (InvalidJwtException e) {
throw new AuthenticationFailedException(e);
}
builder.setPrincipal(jwtPrincipal);
try {
String clientId = config.getClientId().isPresent() ? config.getClientId().get() : null;
for (String role : OidcUtils.findRoles(clientId, config.getRoles(), tokenJson)) {
builder.addRole(role);
}
} catch (Exception e) {
throw new ForbiddenException(e);
}
return builder.build();
}
}
Loading