Merge pull request #4689 from pedroigor/KEYCLOAK-5844
[KEYCLOAK-5844] - Refreshing PAT instead of obtaining a new one every time
This commit is contained in:
commit
63a01b1e1f
6 changed files with 94 additions and 14 deletions
|
@ -22,12 +22,15 @@ import org.keycloak.authorization.client.resource.AuthorizationResource;
|
||||||
import org.keycloak.authorization.client.resource.EntitlementResource;
|
import org.keycloak.authorization.client.resource.EntitlementResource;
|
||||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
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.representations.AccessTokenResponse;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>This is class serves as an entry point for clients looking for access to Keycloak Authorization Services.
|
* <p>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 {
|
public class AuthzClient {
|
||||||
|
|
||||||
private final Http http;
|
private final Http http;
|
||||||
|
private Supplier<String> patSupplier;
|
||||||
|
|
||||||
public static AuthzClient create() {
|
public static AuthzClient create() {
|
||||||
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("keycloak.json");
|
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("keycloak.json");
|
||||||
|
@ -96,7 +100,7 @@ public class AuthzClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtectionResource protection() {
|
public ProtectionResource protection() {
|
||||||
return new ProtectionResource(this.http, obtainAccessToken().getToken());
|
return new ProtectionResource(this.http, createPatSupplier());
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthorizationResource authorization(String accesstoken) {
|
public AuthorizationResource authorization(String accesstoken) {
|
||||||
|
@ -136,4 +140,40 @@ public class AuthzClient {
|
||||||
public Configuration getConfiguration() {
|
public Configuration getConfiguration() {
|
||||||
return this.deployment;
|
return this.deployment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Supplier<String> createPatSupplier() {
|
||||||
|
if (patSupplier == null) {
|
||||||
|
patSupplier = new Supplier<String>() {
|
||||||
|
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.<AccessTokenResponse>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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.keycloak.authorization.client.resource;
|
||||||
|
|
||||||
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
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.PermissionRequest;
|
||||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
import org.keycloak.authorization.client.representation.PermissionResponse;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
import org.keycloak.authorization.client.util.Http;
|
||||||
|
@ -30,9 +32,9 @@ import org.keycloak.util.JsonSerialization;
|
||||||
public class PermissionResource {
|
public class PermissionResource {
|
||||||
|
|
||||||
private final Http http;
|
private final Http http;
|
||||||
private final String pat;
|
private final Supplier<String> pat;
|
||||||
|
|
||||||
public PermissionResource(Http http, String pat) {
|
public PermissionResource(Http http, Supplier<String> pat) {
|
||||||
this.http = http;
|
this.http = http;
|
||||||
this.pat = pat;
|
this.pat = pat;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +42,7 @@ public class PermissionResource {
|
||||||
public PermissionResponse forResource(PermissionRequest request) {
|
public PermissionResponse forResource(PermissionRequest request) {
|
||||||
try {
|
try {
|
||||||
return this.http.<PermissionResponse>post("/authz/protection/permission")
|
return this.http.<PermissionResponse>post("/authz/protection/permission")
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.json(JsonSerialization.writeValueAsBytes(request))
|
.json(JsonSerialization.writeValueAsBytes(request))
|
||||||
.response().json(PermissionResponse.class).execute();
|
.response().json(PermissionResponse.class).execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.authorization.client.resource;
|
||||||
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
import static org.keycloak.authorization.client.util.Throwables.handleAndWrapException;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||||
|
@ -32,9 +33,9 @@ import org.keycloak.util.JsonSerialization;
|
||||||
public class ProtectedResource {
|
public class ProtectedResource {
|
||||||
|
|
||||||
private final Http http;
|
private final Http http;
|
||||||
private final String pat;
|
private final Supplier<String> pat;
|
||||||
|
|
||||||
public ProtectedResource(Http http, String pat) {
|
public ProtectedResource(Http http, Supplier<String> pat) {
|
||||||
this.http = http;
|
this.http = http;
|
||||||
this.pat = pat;
|
this.pat = pat;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,7 @@ public class ProtectedResource {
|
||||||
public RegistrationResponse create(ResourceRepresentation resource) {
|
public RegistrationResponse create(ResourceRepresentation resource) {
|
||||||
try {
|
try {
|
||||||
return this.http.<RegistrationResponse>post("/authz/protection/resource_set")
|
return this.http.<RegistrationResponse>post("/authz/protection/resource_set")
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.json(JsonSerialization.writeValueAsBytes(resource))
|
.json(JsonSerialization.writeValueAsBytes(resource))
|
||||||
.response().json(RegistrationResponse.class).execute();
|
.response().json(RegistrationResponse.class).execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
|
@ -53,7 +54,7 @@ public class ProtectedResource {
|
||||||
public void update(ResourceRepresentation resource) {
|
public void update(ResourceRepresentation resource) {
|
||||||
try {
|
try {
|
||||||
this.http.<RegistrationResponse>put("/authz/protection/resource_set/" + resource.getId())
|
this.http.<RegistrationResponse>put("/authz/protection/resource_set/" + resource.getId())
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.json(JsonSerialization.writeValueAsBytes(resource)).execute();
|
.json(JsonSerialization.writeValueAsBytes(resource)).execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
throw handleAndWrapException("Could not update resource", cause);
|
throw handleAndWrapException("Could not update resource", cause);
|
||||||
|
@ -63,7 +64,7 @@ public class ProtectedResource {
|
||||||
public RegistrationResponse findById(String id) {
|
public RegistrationResponse findById(String id) {
|
||||||
try {
|
try {
|
||||||
return this.http.<RegistrationResponse>get("/authz/protection/resource_set/" + id)
|
return this.http.<RegistrationResponse>get("/authz/protection/resource_set/" + id)
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.response().json(RegistrationResponse.class).execute();
|
.response().json(RegistrationResponse.class).execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
throw handleAndWrapException("Could not find resource", cause);
|
throw handleAndWrapException("Could not find resource", cause);
|
||||||
|
@ -73,7 +74,7 @@ public class ProtectedResource {
|
||||||
public Set<String> findByFilter(String filter) {
|
public Set<String> findByFilter(String filter) {
|
||||||
try {
|
try {
|
||||||
return this.http.<Set>get("/authz/protection/resource_set")
|
return this.http.<Set>get("/authz/protection/resource_set")
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.param("filter", filter)
|
.param("filter", filter)
|
||||||
.response().json(Set.class).execute();
|
.response().json(Set.class).execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
|
@ -84,7 +85,7 @@ public class ProtectedResource {
|
||||||
public Set<String> findAll() {
|
public Set<String> findAll() {
|
||||||
try {
|
try {
|
||||||
return this.http.<Set>get("/authz/protection/resource_set")
|
return this.http.<Set>get("/authz/protection/resource_set")
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.response().json(Set.class).execute();
|
.response().json(Set.class).execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
throw handleAndWrapException("Could not find resource", cause);
|
throw handleAndWrapException("Could not find resource", cause);
|
||||||
|
@ -94,7 +95,7 @@ public class ProtectedResource {
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
try {
|
try {
|
||||||
this.http.delete("/authz/protection/resource_set/" + id)
|
this.http.delete("/authz/protection/resource_set/" + id)
|
||||||
.authorizationBearer(this.pat)
|
.authorizationBearer(this.pat.get())
|
||||||
.execute();
|
.execute();
|
||||||
} catch (Exception cause) {
|
} catch (Exception cause) {
|
||||||
throw handleAndWrapException("Could not delete resource", cause);
|
throw handleAndWrapException("Could not delete resource", cause);
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.authorization.client.resource;
|
package org.keycloak.authorization.client.resource;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
import org.keycloak.authorization.client.representation.TokenIntrospectionResponse;
|
||||||
import org.keycloak.authorization.client.util.Http;
|
import org.keycloak.authorization.client.util.Http;
|
||||||
|
|
||||||
|
@ -25,10 +27,10 @@ import org.keycloak.authorization.client.util.Http;
|
||||||
*/
|
*/
|
||||||
public class ProtectionResource {
|
public class ProtectionResource {
|
||||||
|
|
||||||
private final String pat;
|
private final Supplier<String> pat;
|
||||||
private final Http http;
|
private final Http http;
|
||||||
|
|
||||||
public ProtectionResource(Http http, String pat) {
|
public ProtectionResource(Http http, Supplier<String> pat) {
|
||||||
if (pat == null) {
|
if (pat == null) {
|
||||||
throw new RuntimeException("No access token was provided when creating client for Protection API.");
|
throw new RuntimeException("No access token was provided when creating client for Protection API.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
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.Permission;
|
||||||
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
|
||||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||||
|
@ -73,6 +74,7 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
|
||||||
.authenticatorType(JWTClientAuthenticator.PROVIDER_ID))
|
.authenticatorType(JWTClientAuthenticator.PROVIDER_ID))
|
||||||
.build());
|
.build());
|
||||||
testRealms.add(configureRealm(RealmBuilder.create().name("authz-test"), ClientBuilder.create().secret("secret")).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
|
@Before
|
||||||
|
@ -168,6 +170,31 @@ public class AuthzClientCredentialsTest extends AbstractAuthzTest {
|
||||||
assertAccessProtectionAPI(protection);
|
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<UserSessionRepresentation> 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) {
|
private RealmBuilder configureRealm(RealmBuilder builder, ClientBuilder clientBuilder) {
|
||||||
return builder
|
return builder
|
||||||
.roles(RolesBuilder.create().realmRole(new RoleRepresentation("uma_authorization", "", false)))
|
.roles(RolesBuilder.create().realmRole(new RoleRepresentation("uma_authorization", "", false)))
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"realm": "authz-test-session",
|
||||||
|
"auth-server-url" : "http://localhost:8180/auth",
|
||||||
|
"resource" : "resource-server-test",
|
||||||
|
"credentials": {
|
||||||
|
"secret": "secret"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue