[KEYCLOAK-5844] - Refreshing PAT instead of obtaining a new one every time

This commit is contained in:
Pedro Igor 2017-11-14 11:24:45 -02:00
parent 0f4b8be2b0
commit fdb618219f
6 changed files with 94 additions and 14 deletions

View file

@ -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;
}
} }

View file

@ -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) {

View file

@ -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);

View file

@ -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.");
} }

View file

@ -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)))

View file

@ -0,0 +1,8 @@
{
"realm": "authz-test-session",
"auth-server-url" : "http://localhost:8180/auth",
"resource" : "resource-server-test",
"credentials": {
"secret": "secret"
}
}