KEYCLOAK-742 Added always-refresh-token option to adapters

This commit is contained in:
mposolda 2014-10-05 22:12:54 +02:00
parent 390ca0930b
commit 84e1ace539
9 changed files with 63 additions and 26 deletions

View file

@ -17,7 +17,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
"connection-pool-size", "connection-pool-size",
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password", "client-keystore", "client-keystore-password", "client-key-password",
"auth-server-url-for-backend-requests" "auth-server-url-for-backend-requests", "always-refresh-token"
}) })
public class AdapterConfig extends BaseAdapterConfig { public class AdapterConfig extends BaseAdapterConfig {
@ -39,6 +39,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected int connectionPoolSize = 20; protected int connectionPoolSize = 20;
@JsonProperty("auth-server-url-for-backend-requests") @JsonProperty("auth-server-url-for-backend-requests")
protected String authServerUrlForBackendRequests; protected String authServerUrlForBackendRequests;
@JsonProperty("always-refresh-token")
protected boolean alwaysRefreshToken = false;
public boolean isAllowAnyHostname() { public boolean isAllowAnyHostname() {
return allowAnyHostname; return allowAnyHostname;
@ -111,4 +113,12 @@ public class AdapterConfig extends BaseAdapterConfig {
public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) { public void setAuthServerUrlForBackendRequests(String authServerUrlForBackendRequests) {
this.authServerUrlForBackendRequests = authServerUrlForBackendRequests; this.authServerUrlForBackendRequests = authServerUrlForBackendRequests;
} }
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
this.alwaysRefreshToken = alwaysRefreshToken;
}
} }

View file

@ -326,6 +326,16 @@ public class AdapterDeploymentContext {
public void setCorsAllowedHeaders(String corsAllowedHeaders) { public void setCorsAllowedHeaders(String corsAllowedHeaders) {
delegate.setCorsAllowedHeaders(corsAllowedHeaders); delegate.setCorsAllowedHeaders(corsAllowedHeaders);
} }
@Override
public boolean isAlwaysRefreshToken() {
return delegate.isAlwaysRefreshToken();
}
@Override
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
delegate.setAlwaysRefreshToken(alwaysRefreshToken);
}
} }
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) { protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {

View file

@ -47,6 +47,7 @@ public class KeycloakDeployment {
protected String corsAllowedHeaders; protected String corsAllowedHeaders;
protected String corsAllowedMethods; protected String corsAllowedMethods;
protected boolean exposeToken; protected boolean exposeToken;
protected boolean alwaysRefreshToken;
protected volatile int notBefore; protected volatile int notBefore;
public KeycloakDeployment() { public KeycloakDeployment() {
@ -281,4 +282,11 @@ public class KeycloakDeployment {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
this.alwaysRefreshToken = alwaysRefreshToken;
}
} }

View file

@ -35,7 +35,7 @@ public class KeycloakDeploymentBuilder {
String realmKeyPem = adapterConfig.getRealmKey(); String realmKeyPem = adapterConfig.getRealmKey();
if (realmKeyPem != null) { if (realmKeyPem != null) {
PublicKey realmKey = null; PublicKey realmKey;
try { try {
realmKey = PemUtils.decodePublicKey(realmKeyPem); realmKey = PemUtils.decodePublicKey(realmKeyPem);
} catch (Exception e) { } catch (Exception e) {
@ -60,9 +60,7 @@ public class KeycloakDeploymentBuilder {
} }
deployment.setBearerOnly(adapterConfig.isBearerOnly()); deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
if (adapterConfig.isBearerOnly()) {
}
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { 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"); throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
@ -82,7 +80,7 @@ public class KeycloakDeploymentBuilder {
public static KeycloakDeployment build(InputStream is) { public static KeycloakDeployment build(InputStream is) {
ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory()); ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT); mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
AdapterConfig adapterConfig = null; AdapterConfig adapterConfig;
try { try {
adapterConfig = mapper.readValue(is, AdapterConfig.class); adapterConfig = mapper.readValue(is, AdapterConfig.class);
} catch (IOException e) { } catch (IOException e) {

View file

@ -32,13 +32,13 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
@Override @Override
public AccessToken getToken() { public AccessToken getToken() {
refreshExpiredToken(); refreshExpiredToken(true);
return super.getToken(); return super.getToken();
} }
@Override @Override
public String getTokenString() { public String getTokenString() {
refreshExpiredToken(); refreshExpiredToken(true);
return super.getTokenString(); return super.getTokenString();
} }
@ -62,12 +62,19 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.deployment = deployment; this.deployment = deployment;
} }
public void refreshExpiredToken() { /**
* @param checkActive if true, then we won't send refresh request if current accessToken is still active.
* @return true if accessToken is active or was successfully refreshed
*/
public boolean refreshExpiredToken(boolean checkActive) {
if (checkActive) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("checking whether to refresh."); log.trace("checking whether to refresh.");
} }
if (isActive()) return; if (isActive()) return true;
if (this.deployment == null || refreshToken == null) return; // Might be serialized in HttpSession? }
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("Doing refresh"); log.trace("Doing refresh");
@ -77,10 +84,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
response = ServerRequest.invokeRefresh(deployment, refreshToken); response = ServerRequest.invokeRefresh(deployment, refreshToken);
} catch (IOException e) { } catch (IOException e) {
log.error("Refresh token failure", e); log.error("Refresh token failure", e);
return; return false;
} catch (ServerRequest.HttpFailure httpFailure) { } catch (ServerRequest.HttpFailure httpFailure) {
log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError()); log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
return; return false;
} }
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("received refresh response"); log.trace("received refresh response");
@ -100,7 +107,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.token = token; this.token = token;
this.refreshToken = response.getRefreshToken(); this.refreshToken = response.getRefreshToken();
this.tokenString = tokenString; this.tokenString = tokenString;
return true;
} }

View file

@ -175,12 +175,12 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (session == null) return; if (session == null) return;
// just in case session got serialized // just in case session got serialized
if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
if (session.isActive()) return; if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated // not be updated
session.refreshExpiredToken(); boolean success = session.refreshExpiredToken(false);
if (session.isActive()) return; if (success && session.isActive()) return;
request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName()); request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName());
request.setUserPrincipal(null); request.setUserPrincipal(null);

View file

@ -177,12 +177,12 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (session == null) return; if (session == null) return;
// just in case session got serialized // just in case session got serialized
if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
if (session.isActive()) return; if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated // not be updated
session.refreshExpiredToken(); boolean success = session.refreshExpiredToken(false);
if (session.isActive()) return; if (success && session.isActive()) return;
request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName()); request.getSessionInternal().removeNote(KeycloakSecurityContext.class.getName());
request.setUserPrincipal(null); request.setUserPrincipal(null);

View file

@ -92,14 +92,14 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
public boolean isActive() { public boolean isActive() {
// this object may have been serialized, so we need to reset realm config/metadata // this object may have been serialized, so we need to reset realm config/metadata
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
if (session.isActive()) { if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
log.debug("session is active"); log.debug("session is active");
return true; return true;
} }
log.debug("session is not active try refresh"); log.debug("session is not active or refresh is enforced. Try refresh");
session.refreshExpiredToken(); boolean success = session.refreshExpiredToken(false);
if (!session.isActive()) { if (!success || !session.isActive()) {
log.debug("session is not active return with failure"); log.debug("session is not active return with failure");
return false; return false;

View file

@ -23,6 +23,10 @@ done;
# Configure admin-access.war # Configure admin-access.war
sed -i -e 's/false/true/' admin-access.war/WEB-INF/web.xml sed -i -e 's/false/true/' admin-access.war/WEB-INF/web.xml
# Enforce refreshing token for product-portal and customer-portal war
sed -i -e 's/\"\/auth\",/&\n \"always-refresh-token\": true,/' customer-portal.war/WEB-INF/keycloak.json;
sed -i -e 's/\"\/auth\",/&\n \"always-refresh-token\": true,/' product-portal.war/WEB-INF/keycloak.json;
# Configure other examples # Configure other examples
for I in *.war/WEB-INF/keycloak.json; do for I in *.war/WEB-INF/keycloak.json; do
sed -i -e 's/\"\/auth\",/&\n \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I; sed -i -e 's/\"\/auth\",/&\n \"auth-server-url-for-backend-requests\": \"http:\/\/\$\{jboss.host.name\}:8080\/auth\",/' $I;