keycloak-aplcache

KEYCLOAK-5082 : Add new redirect-rewrite-rule parameters

6/29/2017 7:50:42 AM

Changes

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 45c4557..d5761bc 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -91,6 +91,8 @@ public class KeycloakDeployment {
     // https://tools.ietf.org/html/rfc7636
     protected boolean pkce = false;
     protected boolean ignoreOAuthQueryParameter;
+    
+    protected Map<String, String> redirectRewriteRules;
 
     public KeycloakDeployment() {
     }
@@ -446,4 +448,14 @@ public class KeycloakDeployment {
     public boolean isOAuthQueryParameterEnabled() {
         return !this.ignoreOAuthQueryParameter;
     }
+
+    public Map<String, String> getRedirectRewriteRules() {
+        return redirectRewriteRules;
+    }
+
+    public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
+        this.redirectRewriteRules = redirectRewriteRules;
+    }
+    
+    
 }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index eca6849..7fca1f1 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -116,6 +116,7 @@ public class KeycloakDeploymentBuilder {
         deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
         deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
         deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
+        deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
 
         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");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index 2757b36..ee3f214 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -25,7 +25,6 @@ import org.keycloak.adapters.spi.AuthChallenge;
 import org.keycloak.adapters.spi.AuthOutcome;
 import org.keycloak.adapters.spi.HttpFacade;
 import org.keycloak.common.VerificationException;
-import org.keycloak.common.util.Encode;
 import org.keycloak.common.util.KeycloakUriBuilder;
 import org.keycloak.common.util.UriUtils;
 import org.keycloak.constants.AdapterConstants;
@@ -38,7 +37,10 @@ import org.keycloak.representations.IDToken;
 import org.keycloak.util.TokenUtil;
 
 import java.io.IOException;
-import java.util.concurrent.atomic.AtomicLong;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+import java.util.logging.Level;
 
 
 /**
@@ -141,6 +143,7 @@ public class OAuthRequestAuthenticator {
     protected String getRedirectUri(String state) {
         String url = getRequestUrl();
         log.debugf("callback uri: %s", url);
+      
         if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
             int port = sslRedirectPort();
             if (port < 0) {
@@ -170,7 +173,7 @@ public class OAuthRequestAuthenticator {
         KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
                 .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
                 .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
-                .queryParam(OAuth2Constants.REDIRECT_URI, url)
+                .queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
                 .queryParam(OAuth2Constants.STATE, state)
                 .queryParam("login", "true");
         if(loginHint != null && loginHint.length() > 0){
@@ -320,10 +323,11 @@ public class OAuthRequestAuthenticator {
 
         AccessTokenResponse tokenResponse = null;
         strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
+    
         try {
             // For COOKIE store we don't have httpSessionId and single sign-out won't be available
             String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
-            tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
+            tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
         } catch (ServerRequest.HttpFailure failure) {
             log.error("failed to turn code into token");
             log.error("status from server: " + failure.getStatus());
@@ -375,6 +379,23 @@ public class OAuthRequestAuthenticator {
                 .replaceQueryParam(OAuth2Constants.STATE, null);
         return builder.build().toString();
     }
-
+    
+    private String rewrittenRedirectUri(String originalUri) {
+        Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
+            if(rewriteRules != null && !rewriteRules.isEmpty()) {
+            try {
+                URL url = new URL(originalUri);
+                Map.Entry<String, String> rule =  rewriteRules.entrySet().iterator().next();
+                StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
+                redirectUriBuilder.append("://"+ url.getAuthority());
+                redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
+                return redirectUriBuilder.toString();
+            } catch (MalformedURLException ex) {
+                log.error("Not a valid request url");
+                throw new RuntimeException(ex);
+            }
+            }
+        return originalUri;
+    }
 
 }
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index cd191e2..af58b33 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -75,6 +75,7 @@ public class KeycloakDeploymentBuilderTest {
         assertEquals(10, deployment.getTokenMinimumTimeToLive());
         assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
         assertEquals(120, deployment.getPublicKeyCacheTtl());
+        assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
     }
 
     @Test
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
index 521b8a9..e1b8881 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
@@ -33,5 +33,8 @@
     "token-minimum-time-to-live": 10,
     "min-time-between-jwks-requests": 20,
     "public-key-cache-ttl": 120,
-    "ignore-oauth-query-parameter": true
+    "ignore-oauth-query-parameter": true,
+    "redirect-rewrite-rules" : {
+  	"^/wsmaster/api/(.*)$" : "/api/$1"
+     }
 }
\ No newline at end of file
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
index e96a5e5..5a71e61 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
@@ -37,6 +37,8 @@ import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD
 public final class KeycloakAdapterConfigService {
 
     private static final String CREDENTIALS_JSON_NAME = "credentials";
+    
+    private static final String REDIRECT_REWRITE_RULE_JSON_NAME = "redirect-rewrite-rule";
 
     private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
 
@@ -129,6 +131,56 @@ public final class KeycloakAdapterConfigService {
         ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
         return deployment.get(CREDENTIALS_JSON_NAME);
     }
+    
+     public void addRedirectRewriteRule(ModelNode operation, ModelNode model) {
+        ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
+        if (!redirectRewritesRules.isDefined()) {
+            redirectRewritesRules = new ModelNode();
+        }
+
+        String redirectRewriteRuleName = redirectRewriteRule(operation);
+        if (!redirectRewriteRuleName.contains(".")) {
+            redirectRewritesRules.get(redirectRewriteRuleName).set(model.get("value").asString());
+        } else {
+            String[] parts = redirectRewriteRuleName.split("\\.");
+            String provider = parts[0];
+            String property = parts[1];
+            ModelNode redirectRewriteRule = redirectRewritesRules.get(provider);
+            if (!redirectRewriteRule.isDefined()) {
+                redirectRewriteRule = new ModelNode();
+            }
+            redirectRewriteRule.get(property).set(model.get("value").asString());
+            redirectRewritesRules.set(provider, redirectRewriteRule);
+        }
+
+        ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+        deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME).set(redirectRewritesRules);
+    }
+
+    public void removeRedirectRewriteRule(ModelNode operation) {
+        ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
+        if (!redirectRewritesRules.isDefined()) {
+            throw new RuntimeException("Can not remove redirect rewrite rule.  No rules defined for deployment in op " + operation.toString());
+        }
+
+        String ruleName = credentialNameFromOp(operation);
+        redirectRewritesRules.remove(ruleName);
+    }
+
+    public void updateRedirectRewriteRule(ModelNode operation, String attrName, ModelNode resolvedValue) {
+        ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
+        if (!redirectRewritesRules.isDefined()) {
+            throw new RuntimeException("Can not update redirect rewrite rule.  No rules defined for deployment in op " + operation.toString());
+        }
+
+        String ruleName = credentialNameFromOp(operation);
+        redirectRewritesRules.get(ruleName).set(resolvedValue);
+    }
+
+    private ModelNode redirectRewriteRuleFromOp(ModelNode operation) {
+        ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+        return deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME);
+    }
 
     private String realmNameFromOp(ModelNode operation) {
         return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
@@ -141,6 +193,10 @@ public final class KeycloakAdapterConfigService {
     private String credentialNameFromOp(ModelNode operation) {
         return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
     }
+    
+    private String redirectRewriteRule(ModelNode operation) {
+        return valueFromOpAddress(RedirecRewritetRuleDefinition.TAG_NAME, operation);
+    }
 
     private String valueFromOpAddress(String addrElement, ModelNode operation) {
         String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java
index 541454a..d04e72d 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java
@@ -48,6 +48,7 @@ public class KeycloakExtension implements Extension {
     static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
     static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
     static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
+     static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
 
     public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
         StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
@@ -77,6 +78,7 @@ public class KeycloakExtension implements Extension {
         registration.registerSubModel(REALM_DEFINITION);
         ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
         secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
+        secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
 
         subsystem.registerXMLElementWriter(PARSER);
     }
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
index d4ddc02..79555e3 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
@@ -96,12 +96,17 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
                 PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
         addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
         List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
+        List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
         while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
             String tagName = reader.getLocalName();
             if (tagName.equals(CredentialDefinition.TAG_NAME)) {
                 readCredential(reader, addr, credentialsToAdd);
                 continue;
             }
+            if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
+                readRewriteRule(reader, addr, redirectRulesToAdd);
+                continue;
+            }
 
             SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
             if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
@@ -111,6 +116,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
         // Must add credentials after the deployment is added.
         resourcesToAdd.add(addSecureDeployment);
         resourcesToAdd.addAll(credentialsToAdd);
+        resourcesToAdd.addAll(redirectRulesToAdd);
     }
 
     public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
@@ -149,6 +155,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
             }
         }
     }
+    
+       public void readRewriteRule(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> rewriteRuleToToAdd) throws XMLStreamException {
+        String name = readNameAttribute(reader);
+
+        Map<String, String> values = new HashMap<>();
+        String textValue = null;
+        while (reader.hasNext()) {
+            int next = reader.next();
+            if (next == CHARACTERS) {
+                // text value of redirect rule element
+                String text = reader.getText();
+                if (text == null || text.trim().isEmpty()) {
+                    continue;
+                }
+                textValue = text;
+            } else if (next == START_ELEMENT) {
+                String key = reader.getLocalName();
+                reader.next();
+                String value = reader.getText();
+                reader.next();
+
+                values.put(key, value);
+            } else if (next == END_ELEMENT) {
+                break;
+            }
+        }
+
+        if (textValue != null) {
+            ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name, textValue);
+            rewriteRuleToToAdd.add(addRedirectRule);
+        } else {
+            for (Map.Entry<String, String> entry : values.entrySet()) {
+                ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+                rewriteRuleToToAdd.add(addRedirectRule);
+            }
+        }
+    }
 
     private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
         ModelNode addCredential = new ModelNode();
@@ -158,6 +201,15 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
         addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
         return addCredential;
     }
+    
+    private ModelNode getRedirectRuleToAdd(PathAddress parent, String name, String value) {
+        ModelNode addRedirectRule = new ModelNode();
+        addRedirectRule.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+        PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(RedirecRewritetRuleDefinition.TAG_NAME, name));
+        addRedirectRule.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+        addRedirectRule.get(RedirecRewritetRuleDefinition.VALUE.getName()).set(value);
+        return addRedirectRule;
+    }
 
     // expects that the current tag will have one single attribute called "name"
     private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
@@ -219,6 +271,11 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
             if (credentials.isDefined()) {
                 writeCredentials(writer, credentials);
             }
+            
+            ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
+            if (redirectRewriteRule.isDefined()) {
+                writeRedirectRules(writer, redirectRewriteRule);
+            }
 
             writer.writeEndElement();
         }
@@ -265,6 +322,34 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
             writer.writeEndElement();
         }
     }
+    
+      private void writeRedirectRules(XMLExtendedStreamWriter writer, ModelNode redirectRules) throws XMLStreamException {
+        Map<String, Object> parsed = new LinkedHashMap<>();
+        for (Property redirectRule : redirectRules.asPropertyList()) {
+            String ruleName = redirectRule.getName();
+            String ruleValue = redirectRule.getValue().get(RedirecRewritetRuleDefinition.VALUE.getName()).asString();
+            parsed.put(ruleName, ruleValue);
+        }
+
+        for (Map.Entry<String, Object> entry : parsed.entrySet()) {
+            writer.writeStartElement(RedirecRewritetRuleDefinition.TAG_NAME);
+            writer.writeAttribute("name", entry.getKey());
+
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                writeCharacters(writer, (String) value);
+            } else {
+                Map<String, String> redirectRulesProps = (Map<String, String>) value;
+                for (Map.Entry<String, String> prop : redirectRulesProps.entrySet()) {
+                    writer.writeStartElement(prop.getKey());
+                    writeCharacters(writer, prop.getValue());
+                    writer.writeEndElement();
+                }
+            }
+
+            writer.writeEndElement();
+        }
+    }
 
     // code taken from org.jboss.as.controller.AttributeMarshaller
     private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirecRewritetRuleDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirecRewritetRuleDefinition.java
new file mode 100644
index 0000000..a9095c7
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirecRewritetRuleDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+/**
+ *
+ * @author sblanc
+ */
+public class RedirecRewritetRuleDefinition extends SimpleResourceDefinition {
+
+    public static final String TAG_NAME = "redirect-rewrite-rule";
+
+    protected static final AttributeDefinition VALUE =
+            new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
+            .setAllowExpression(true)
+            .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
+            .build();
+
+    public RedirecRewritetRuleDefinition() {
+        super(PathElement.pathElement(TAG_NAME),
+                KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+                new RedirectRewriteRuleAddHandler(VALUE),
+                RedirectRewriteRuleRemoveHandler.INSTANCE);
+    }
+
+    @Override
+    public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+        super.registerOperations(resourceRegistration);
+        resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+    }
+
+    @Override
+    public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+        super.registerAttributes(resourceRegistration);
+        resourceRegistration.registerReadWriteAttribute(VALUE, null, new RedirectRewriteRuleReadWriteAttributeHandler());
+    }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleAddHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleAddHandler.java
new file mode 100644
index 0000000..2fc25f7
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleAddHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+public class RedirectRewriteRuleAddHandler extends AbstractAddStepHandler {
+
+    public RedirectRewriteRuleAddHandler(AttributeDefinition... attributes) {
+        super(attributes);
+    }
+
+    @Override
+    protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+        KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+        ckService.addRedirectRewriteRule(operation, context.resolveExpressions(model));
+    }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleReadWriteAttributeHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleReadWriteAttributeHandler.java
new file mode 100644
index 0000000..171e755
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleReadWriteAttributeHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+public class RedirectRewriteRuleReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+    @Override
+    protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+                                           ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+
+        KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+        ckService.updateRedirectRewriteRule(operation, attributeName, resolvedValue);
+
+        hh.setHandback(ckService);
+
+        return false;
+    }
+
+    @Override
+    protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+                                         ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+        ckService.updateRedirectRewriteRule(operation, attributeName, valueToRestore);
+    }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleRemoveHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleRemoveHandler.java
new file mode 100644
index 0000000..de17c96
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RedirectRewriteRuleRemoveHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+public class RedirectRewriteRuleRemoveHandler extends AbstractRemoveStepHandler {
+
+    public static RedirectRewriteRuleRemoveHandler INSTANCE = new RedirectRewriteRuleRemoveHandler();
+
+    private RedirectRewriteRuleRemoveHandler() {}
+
+    @Override
+    protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+        KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+        ckService.removeRedirectRewriteRule(operation);
+    }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
index 1df5979..c9cea77 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
@@ -65,6 +65,7 @@ keycloak.secure-deployment.connection-pool-size=Connection pool size for the cli
 keycloak.secure-deployment.resource=Application name
 keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
 keycloak.secure-deployment.credentials=Adapter credentials
+keycloak.secure-deployment.redirect-rewrite-rule=Apply a rewrite rule for the redirect URI
 keycloak.secure-deployment.bearer-only=Bearer Token Auth only
 keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
 keycloak.secure-deployment.public-client=Public client
@@ -94,4 +95,9 @@ keycloak.secure-deployment.credential=Credential value
 keycloak.credential=Credential
 keycloak.credential.value=Credential value
 keycloak.credential.add=Credential add
-keycloak.credential.remove=Credential remove
\ No newline at end of file
+keycloak.credential.remove=Credential remove
+
+keycloak.redirect-rewrite-rule=redirect-rewrite-rule
+keycloak.redirect-rewrite-rule.value=redirect-rewrite-rule value
+keycloak.redirect-rewrite-rule.add=redirect-rewrite-rule add
+keycloak.redirect-rewrite-rule.remove=redirect-rewrite-rule remove
\ No newline at end of file
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
index 604e6ac..d8f5bc3 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -101,6 +101,7 @@
             <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
             <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
+            <xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="1" maxOccurs="1"/>
             <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
             <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
             <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@@ -127,4 +128,10 @@
         </xs:sequence>
         <xs:attribute name="name" type="xs:string" use="required" />
     </xs:complexType>
+     <xs:complexType name="redirect-rewrite-rule-type" mixed="true">
+        <xs:sequence maxOccurs="unbounded" minOccurs="0">
+            <xs:any processContents="lax"></xs:any>
+        </xs:sequence>
+        <xs:attribute name="name" type="xs:string" use="required" />
+    </xs:complexType>
 </xs:schema>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
index 3dcb61d..246d768 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
@@ -53,6 +53,7 @@
         <auth-server-url>http://localhost:8080/auth</auth-server-url>
         <ssl-required>EXTERNAL</ssl-required>
         <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
+        <redirect-rewrite-rule name="^/wsmaster/api/(.*)$">api/$1/</redirect-rewrite-rule>
     </secure-deployment>
     <secure-deployment name="http-endpoint">
         <realm>master</realm>
@@ -66,5 +67,6 @@
         <credential name="jwt">
             <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
         </credential>
+        <redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
     </secure-deployment>
 </subsystem>
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
index 4a2b7e2..ebd49ab 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
@@ -62,7 +62,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
     protected boolean publicClient;
     @JsonProperty("credentials")
     protected Map<String, Object> credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
+     @JsonProperty("redirect-rewrite-rules")
+    protected Map<String, String> redirectRewriteRules;
 
     public boolean isUseResourceRoleMappings() {
         return useResourceRoleMappings;
@@ -167,4 +168,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
     public void setPublicClient(boolean publicClient) {
         this.publicClient = publicClient;
     }
+
+    public Map<String, String> getRedirectRewriteRules() {
+        return redirectRewriteRules;
+    }
+
+    public void setRedirectRewriteRules(Map<String, String> redirectRewriteRules) {
+        this.redirectRewriteRules = redirectRewriteRules;
+    }
+    
+    
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 4870415..14d5570 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -258,6 +258,7 @@ public class TokenEndpoint {
         }
 
         event.user(userSession.getUser());
+
         event.session(userSession.getId());
 
         String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM);