diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java b/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java index 5ed13063f5..cb7389a829 100644 --- a/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java +++ b/authz/client/src/main/java/org/keycloak/authorization/client/AuthzClient.java @@ -22,12 +22,15 @@ import org.keycloak.authorization.client.resource.AuthorizationResource; import org.keycloak.authorization.client.resource.EntitlementResource; import org.keycloak.authorization.client.resource.ProtectionResource; import org.keycloak.authorization.client.util.Http; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.util.function.Supplier; /** *

This is class serves as an entry point for clients looking for access to Keycloak Authorization Services. @@ -37,6 +40,7 @@ import java.net.URI; public class AuthzClient { private final Http http; + private Supplier patSupplier; public static AuthzClient create() { InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("keycloak.json"); @@ -96,7 +100,7 @@ public class AuthzClient { } public ProtectionResource protection() { - return new ProtectionResource(this.http, obtainAccessToken().getToken()); + return new ProtectionResource(this.http, createPatSupplier()); } public AuthorizationResource authorization(String accesstoken) { @@ -136,4 +140,40 @@ public class AuthzClient { public Configuration getConfiguration() { return this.deployment; } + + private Supplier createPatSupplier() { + if (patSupplier == null) { + patSupplier = new Supplier() { + AccessTokenResponse clientToken = obtainAccessToken(); + + @Override + public String get() { + String token = clientToken.getToken(); + + try { + AccessToken accessToken = JsonSerialization.readValue(new JWSInput(token).getContent(), AccessToken.class); + + if (accessToken.isActive()) { + return token; + } + + clientToken = http.post(serverConfiguration.getTokenEndpoint()) + .authentication().client() + .form() + .param("grant_type", "refresh_token") + .param("refresh_token", clientToken.getRefreshToken()) + .response() + .json(AccessTokenResponse.class) + .execute(); + } catch (Exception e) { + patSupplier = null; + throw new RuntimeException(e); + } + + return clientToken.getToken(); + } + }; + } + return patSupplier; + } } diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/PermissionResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/PermissionResource.java index 960955f9ca..5aaccda7a2 100644 --- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/PermissionResource.java +++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/PermissionResource.java @@ -19,6 +19,8 @@ package org.keycloak.authorization.client.resource; import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException; +import java.util.function.Supplier; + import org.keycloak.authorization.client.representation.PermissionRequest; import org.keycloak.authorization.client.representation.PermissionResponse; import org.keycloak.authorization.client.util.Http; @@ -30,9 +32,9 @@ import org.keycloak.util.JsonSerialization; public class PermissionResource { private final Http http; - private final String pat; + private final Supplier pat; - public PermissionResource(Http http, String pat) { + public PermissionResource(Http http, Supplier pat) { this.http = http; this.pat = pat; } @@ -40,7 +42,7 @@ public class PermissionResource { public PermissionResponse forResource(PermissionRequest request) { try { return this.http.post("/authz/protection/permission") - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .json(JsonSerialization.writeValueAsBytes(request)) .response().json(PermissionResponse.class).execute(); } catch (Exception cause) { diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java index d52ccc011a..d774c3d73d 100644 --- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java +++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectedResource.java @@ -20,6 +20,7 @@ package org.keycloak.authorization.client.resource; import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException; import java.util.Set; +import java.util.function.Supplier; import org.keycloak.authorization.client.representation.RegistrationResponse; import org.keycloak.authorization.client.representation.ResourceRepresentation; @@ -32,9 +33,9 @@ import org.keycloak.util.JsonSerialization; public class ProtectedResource { private final Http http; - private final String pat; + private final Supplier pat; - public ProtectedResource(Http http, String pat) { + public ProtectedResource(Http http, Supplier pat) { this.http = http; this.pat = pat; } @@ -42,7 +43,7 @@ public class ProtectedResource { public RegistrationResponse create(ResourceRepresentation resource) { try { return this.http.post("/authz/protection/resource_set") - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .json(JsonSerialization.writeValueAsBytes(resource)) .response().json(RegistrationResponse.class).execute(); } catch (Exception cause) { @@ -53,7 +54,7 @@ public class ProtectedResource { public void update(ResourceRepresentation resource) { try { this.http.put("/authz/protection/resource_set/" + resource.getId()) - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .json(JsonSerialization.writeValueAsBytes(resource)).execute(); } catch (Exception cause) { throw handleAndWrapException("Could not update resource", cause); @@ -63,7 +64,7 @@ public class ProtectedResource { public RegistrationResponse findById(String id) { try { return this.http.get("/authz/protection/resource_set/" + id) - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .response().json(RegistrationResponse.class).execute(); } catch (Exception cause) { throw handleAndWrapException("Could not find resource", cause); @@ -73,7 +74,7 @@ public class ProtectedResource { public Set findByFilter(String filter) { try { return this.http.get("/authz/protection/resource_set") - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .param("filter", filter) .response().json(Set.class).execute(); } catch (Exception cause) { @@ -84,7 +85,7 @@ public class ProtectedResource { public Set findAll() { try { return this.http.get("/authz/protection/resource_set") - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .response().json(Set.class).execute(); } catch (Exception cause) { throw handleAndWrapException("Could not find resource", cause); @@ -94,7 +95,7 @@ public class ProtectedResource { public void delete(String id) { try { this.http.delete("/authz/protection/resource_set/" + id) - .authorizationBearer(this.pat) + .authorizationBearer(this.pat.get()) .execute(); } catch (Exception cause) { throw handleAndWrapException("Could not delete resource", cause); diff --git a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java index fc24f0ebab..fd3bee9ea8 100644 --- a/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java +++ b/authz/client/src/main/java/org/keycloak/authorization/client/resource/ProtectionResource.java @@ -17,6 +17,8 @@ */ package org.keycloak.authorization.client.resource; +import java.util.function.Supplier; + import org.keycloak.authorization.client.representation.TokenIntrospectionResponse; import org.keycloak.authorization.client.util.Http; @@ -25,10 +27,10 @@ import org.keycloak.authorization.client.util.Http; */ public class ProtectionResource { - private final String pat; + private final Supplier pat; private final Http http; - public ProtectionResource(Http http, String pat) { + public ProtectionResource(Http http, Supplier pat) { if (pat == null) { throw new RuntimeException("No access token was provided when creating client for Protection API."); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java index 38a194dd2a..cfeb153490 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthzClientCredentialsTest.java @@ -52,6 +52,7 @@ import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; @@ -73,6 +74,7 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest { .authenticatorType(JWTClientAuthenticator.PROVIDER_ID)) .build()); testRealms.add(configureRealm(RealmBuilder.create().name("authz-test"), ClientBuilder.create().secret("secret")).build()); + testRealms.add(configureRealm(RealmBuilder.create().name("authz-test-session").accessTokenLifespan(1), ClientBuilder.create().secret("secret")).build()); } @Before @@ -168,6 +170,31 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest { assertAccessProtectionAPI(protection); } + @Test + public void testReusingAccessAndRefreshTokens() throws Exception { + ClientsResource clients = getAdminClient().realm("authz-test-session").clients(); + ClientRepresentation clientRepresentation = clients.findByClientId("resource-server-test").get(0); + List userSessions = clients.get(clientRepresentation.getId()).getUserSessions(-1, -1); + + assertEquals(0, userSessions.size()); + + AuthzClient authzClient = getAuthzClient("default-session-keycloak.json"); + ProtectionResource protection = authzClient.protection(); + + protection.resource().findByFilter("name=Default Resource"); + userSessions = clients.get(clientRepresentation.getId()).getUserSessions(null, null); + + assertEquals(1, userSessions.size()); + + Thread.sleep(2000); + protection = authzClient.protection(); + protection.resource().findByFilter("name=Default Resource"); + + userSessions = clients.get(clientRepresentation.getId()).getUserSessions(null, null); + + assertEquals(1, userSessions.size()); + } + private RealmBuilder configureRealm(RealmBuilder builder, ClientBuilder clientBuilder) { return builder .roles(RolesBuilder.create().realmRole(new RoleRepresentation("uma_authorization", "", false))) diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-session-keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-session-keycloak.json new file mode 100644 index 0000000000..3c53b77da2 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/default-session-keycloak.json @@ -0,0 +1,8 @@ +{ + "realm": "authz-test-session", + "auth-server-url" : "http://localhost:8180/auth", + "resource" : "resource-server-test", + "credentials": { + "secret": "secret" + } +} \ No newline at end of file