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