Skip to content

Commit

Permalink
KEYCLOAK-3823 KEYCLOAK-3824 Added public-key-cache-ttl for OIDC adapt…
Browse files Browse the repository at this point in the history
…ers. Invalidate cache when notBefore sent
  • Loading branch information
mposolda committed Dec 1, 2016
1 parent c9cf7f6 commit a385447
Show file tree
Hide file tree
Showing 27 changed files with 663 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,16 @@ public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
public int getMinTimeBetweenJwksRequests() {
return delegate.getMinTimeBetweenJwksRequests();
}

@Override
public int getPublicKeyCacheTtl() {
return delegate.getPublicKeyCacheTtl();
}

@Override
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
}
}

protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,7 @@ public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpReque
}
InputStream is = entity.getContent();
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
return JsonSerialization.readValue(json, clazz);
return JsonSerialization.readValue(is, clazz);
} finally {
try {
is.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public class KeycloakDeployment {
protected volatile int notBefore;
protected int tokenMinimumTimeToLive;
protected int minTimeBetweenJwksRequests;
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer;

public KeycloakDeployment() {
Expand Down Expand Up @@ -384,6 +385,14 @@ public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
}

public int getPublicKeyCacheTtl() {
return publicKeyCacheTtl;
}

public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
this.publicKeyCacheTtl = publicKeyCacheTtl;
}

public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ protected KeycloakDeployment internalBuild(AdapterConfig adapterConfig) {
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());

if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.keycloak.common.util.Time;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.util.JWKSUtils;

import java.security.PublicKey;
Expand All @@ -48,15 +47,15 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
@Override
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
int currentTime = Time.currentTime();

// Check if key is in cache.
PublicKey publicKey = currentKeys.get(kid);
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
if (publicKey != null) {
return publicKey;
}

int currentTime = Time.currentTime();

// Check if we are allowed to send request
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
synchronized (this) {
Expand All @@ -70,11 +69,20 @@ public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
}
}

return currentKeys.get(kid);
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);

}


private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
return currentKeys.get(kid);
} else {
return null;
}
}


private void sendRequest(KeycloakDeployment deployment) {
if (log.isTraceEnabled()) {
log.tracef("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public void load() throws Exception {
assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl());
}

@Test
Expand All @@ -78,6 +79,7 @@ public void loadNoClientCredentials() throws Exception {

assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
assertEquals(86400, deployment.getPublicKeyCacheTtl());
}

@Test
Expand Down
3 changes: 2 additions & 1 deletion adapters/oidc/adapter-core/src/test/resources/keycloak.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@
"token-store": "cookie",
"principal-attribute": "email",
"token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20
"min-time-between-jwks-requests": 20,
"public-key-cache-ttl": 120
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"client-keystore", "client-keystore-password", "client-key-password",
"always-refresh-token",
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
"proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
"min-time-between-jwks-requests", "public-key-cache-ttl",
"policy-enforcer"
})
public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClientConfig {
Expand Down Expand Up @@ -73,6 +74,8 @@ public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClien
protected int tokenMinimumTimeToLive = 0;
@JsonProperty("min-time-between-jwks-requests")
protected int minTimeBetweenJwksRequests = 10;
@JsonProperty("public-key-cache-ttl")
protected int publicKeyCacheTtl = 86400; // 1 day
@JsonProperty("policy-enforcer")
protected PolicyEnforcerConfig policyEnforcerConfig;

Expand Down Expand Up @@ -233,4 +236,12 @@ public int getMinTimeBetweenJwksRequests() {
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
}

public int getPublicKeyCacheTtl() {
return publicKeyCacheTtl;
}

public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
this.publicKeyCacheTtl = publicKeyCacheTtl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> {

Expand All @@ -61,7 +61,7 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
private KeycloakOnUndertowConfiguration configuration;
private KeycloakSessionFactory sessionFactory;

Map<String, String> deployedArchivesToContextPath = new HashMap<>();
Map<String, String> deployedArchivesToContextPath = new ConcurrentHashMap<>();

private DeploymentInfo createAuthServerDeploymentInfo() {
ResteasyDeployment deployment = new ResteasyDeployment();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.util.Time;
import org.keycloak.common.util.reflections.Reflections;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;

/**
* Filter to handle "special" requests to perform actions on adapter side (for example setting time offset )
Expand All @@ -38,7 +40,7 @@
public class AdapterActionsFilter implements Filter {

public static final String TIME_OFFSET_PARAM = "timeOffset";
public static final String RESET_PUBLIC_KEY_PARAM = "resetPublicKey";
public static final String RESET_DEPLOYMENT_PARAM = "resetDeployment";

private static final Logger log = Logger.getLogger(AdapterActionsFilter.class);

Expand All @@ -54,19 +56,28 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha

//Accept timeOffset as argument to enforce timeouts
String timeOffsetParam = request.getParameter(TIME_OFFSET_PARAM);
String resetPublicKey = request.getParameter(RESET_PUBLIC_KEY_PARAM);
String resetDeploymentParam = request.getParameter(RESET_DEPLOYMENT_PARAM);

if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) {
int timeOffset = Integer.parseInt(timeOffsetParam);
log.infof("Time offset updated to %d for application %s", timeOffset, servletReq.getRequestURI());
Time.setOffset(timeOffset);
writeResponse(servletResp, "Offset set successfully");
} else if (resetPublicKey != null && !resetPublicKey.isEmpty()) {
} else if (resetDeploymentParam != null && !resetDeploymentParam.isEmpty()) {
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
KeycloakDeployment deployment = deploymentContext.resolveDeployment(null);
deployment.setPublicKeyLocator(new JWKPublicKeyLocator());
log.infof("Restarted publicKey locator for application %s", servletReq.getRequestURI());
writeResponse(servletResp, "PublicKeyLocator restarted successfully");

Field field = Reflections.findDeclaredField(AdapterDeploymentContext.class, "deployment");
Reflections.setAccessible(field);
KeycloakDeployment deployment = (KeycloakDeployment) Reflections.getFieldValue(field, deploymentContext);

Time.setOffset(0);
deployment.setNotBefore(0);
if (deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator) {
deployment.setPublicKeyLocator(new JWKPublicKeyLocator());
}

log.infof("Restarted PublicKeyLocator, notBefore and timeOffset for application %s", servletReq.getRequestURI());
writeResponse(servletResp, "Restarted PublicKeyLocator, notBefore and timeOffset successfully");
} else {
// Continue request
chain.doFilter(request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ protected String renderTokens(HttpServletRequest req) throws ServletException,

return new StringBuilder("<span id=\"accessToken\">" + accessTokenPretty + "</span>")
.append("<span id=\"refreshToken\">" + refreshTokenPretty + "</span>")
.append("<span id=\"accessTokenString\">" + ctx.getTokenString() + "</span>")
.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
@FindBy(id = "refreshToken")
private WebElement refreshToken;

@FindBy(id = "accessTokenString")
private WebElement accessTokenString;


public AccessToken getAccessToken() {
try {
Expand All @@ -51,13 +54,25 @@ public AccessToken getAccessToken() {
return null;
}


public RefreshToken getRefreshToken() {
try {
return JsonSerialization.readValue(refreshToken.getText(), RefreshToken.class);
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchElementException nsee) {
log.warn("No idToken element found on the page");
log.warn("No refreshToken element found on the page");
}

return null;
}


public String getAccessTokenString() {
try {
return accessTokenString.getText();
} catch (NoSuchElementException nsee) {
log.warn("No accessTokenString element found on the page");
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,17 @@ public void setDefaultPageUriParameters() {
testRealmPage.setAuthRealm(DEMO);
}

protected void setAdapterAndServerTimeOffset(int timeOffset, String servletUri) {
protected void setAdapterAndServerTimeOffset(int timeOffset, String... servletUris) {
setTimeOffset(timeOffset);
String timeOffsetUri = UriBuilder.fromUri(servletUri)
.queryParam(AdapterActionsFilter.TIME_OFFSET_PARAM, timeOffset)
.build().toString();

driver.navigate().to(timeOffsetUri);
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
for (String servletUri : servletUris) {
String timeOffsetUri = UriBuilder.fromUri(servletUri)
.queryParam(AdapterActionsFilter.TIME_OFFSET_PARAM, timeOffset)
.build().toString();

driver.navigate().to(timeOffsetUri);
WaitUtils.waitUntilElement(By.tagName("body")).is().visible();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,4 @@ public void testOIDCParamsForwarding() {

}

@Test
@Override
@Ignore
public void testClientWithJwksUri() {

}
}
Loading

0 comments on commit a385447

Please sign in to comment.