keycloak-developers

Permit Spring Security adapter to process admin tasks with

5/7/2015 8:58:27 PM

Details

diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
index 7a3d772..34dd14d 100644
--- a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java
@@ -5,6 +5,7 @@ import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticatio
 import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
 import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
 import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
+import org.keycloak.adapters.springsecurity.filter.KeycloakCsrfRequestMatcher;
 import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
 import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
 import org.springframework.context.annotation.Bean;
@@ -59,6 +60,10 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
         return new KeycloakPreAuthActionsFilter(httpSessionManager());
     }
 
+    protected KeycloakCsrfRequestMatcher keycloakCsrfRequestMatcher() {
+        return new KeycloakCsrfRequestMatcher();
+    }
+
     @Bean
     protected HttpSessionManager httpSessionManager() {
         return  new HttpSessionManager();
diff --git a/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcher.java b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcher.java
new file mode 100644
index 0000000..77c8e7d
--- /dev/null
+++ b/integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcher.java
@@ -0,0 +1,37 @@
+package org.keycloak.adapters.springsecurity.filter;
+
+import org.keycloak.constants.AdapterConstants;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * CSRF protection matcher that allows administrative POST requests from the Keycloak server.
+ *
+ * @author <a href="mailto:srossillo@smartling.com">Scott Rossillo</a>
+ */
+public class KeycloakCsrfRequestMatcher implements RequestMatcher {
+
+    private static final List<String> ALLOWED_ENDPOINTS = Arrays.asList(
+            AdapterConstants.K_LOGOUT,
+            AdapterConstants.K_PUSH_NOT_BEFORE,
+            AdapterConstants.K_QUERY_BEARER_TOKEN,
+            AdapterConstants.K_TEST_AVAILABLE,
+            AdapterConstants.K_VERSION
+    );
+
+    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
+    private Pattern allowedEndpoints = Pattern.compile(String.format("^\\/(%s)$", StringUtils.arrayToDelimitedString(ALLOWED_ENDPOINTS.toArray(), "|")));
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.servlet.http.HttpServletRequest)
+     */
+    public boolean matches(HttpServletRequest request) {
+        String uri = request.getRequestURI().replaceFirst(request.getContextPath(), "");
+        return !allowedEndpoints.matcher(uri).matches() && !allowedMethods.matcher(request.getMethod()).matches();
+    }
+}
diff --git a/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java
new file mode 100644
index 0000000..e729e53
--- /dev/null
+++ b/integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java
@@ -0,0 +1,98 @@
+package org.keycloak.adapters.springsecurity.filter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.constants.AdapterConstants;
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import static org.junit.Assert.*;
+
+/**
+ * Keycloak CSRF request matcher tests.
+ */
+public class KeycloakCsrfRequestMatcherTest {
+
+    private static final String ROOT_CONTEXT_PATH = "";
+    private static final String SUB_CONTEXT_PATH = "/foo";
+
+    private KeycloakCsrfRequestMatcher matcher = new KeycloakCsrfRequestMatcher();
+
+    private MockHttpServletRequest request;
+
+    @Before
+    public void setUp() throws Exception {
+        request = new MockHttpServletRequest();
+    }
+
+    @Test
+    public void testMatchesMethodGet() throws Exception {
+        request.setMethod(HttpMethod.GET.name());
+        assertFalse(matcher.matches(request));
+    }
+
+    @Test
+    public void testMatchesMethodPost() throws Exception {
+        prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, "some/random/uri");
+        assertTrue(matcher.matches(request));
+
+        prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, "some/random/uri");
+        assertTrue(matcher.matches(request));
+    }
+
+    @Test
+    public void testMatchesKeycloakLogout() throws Exception {
+
+        prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_LOGOUT);
+        assertFalse(matcher.matches(request));
+
+        prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_LOGOUT);
+        assertFalse(matcher.matches(request));
+    }
+
+    @Test
+    public void testMatchesKeycloakPushNotBefore() throws Exception {
+
+        prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_PUSH_NOT_BEFORE);
+        assertFalse(matcher.matches(request));
+
+        prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_PUSH_NOT_BEFORE);
+        assertFalse(matcher.matches(request));
+    }
+
+    @Test
+    public void testMatchesKeycloakQueryBearerToken() throws Exception {
+
+        prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_QUERY_BEARER_TOKEN);
+        assertFalse(matcher.matches(request));
+
+        prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_QUERY_BEARER_TOKEN);
+        assertFalse(matcher.matches(request));
+    }
+
+    @Test
+    public void testMatchesKeycloakTestAvailable() throws Exception {
+
+        prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_TEST_AVAILABLE);
+        assertFalse(matcher.matches(request));
+
+        prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_TEST_AVAILABLE);
+        assertFalse(matcher.matches(request));
+    }
+
+    @Test
+    public void testMatchesKeycloakVersion() throws Exception {
+
+        prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_VERSION);
+        assertFalse(matcher.matches(request));
+
+        prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_VERSION);
+        assertFalse(matcher.matches(request));
+    }
+
+    private void prepareRequest(HttpMethod method, String contextPath, String uri) {
+        request.setMethod(method.name());
+        request.setContextPath(contextPath);
+        request.setRequestURI(contextPath + "/" + uri);
+    }
+}