KEYCLOAK-742 Added always-refresh-token option to adapters
This commit is contained in:
parent
390ca0930b
commit
84e1ace539
9 changed files with 63 additions and 26 deletions
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue