Merge pull request #1231 from Smartling/KEYCLOAK-1286

Permit Spring Security adapter to process admin tasks with CSRF enabled
This commit is contained in:
Stian Thorgersen 2015-05-08 07:00:15 +02:00
commit 96668daefa
3 changed files with 140 additions and 0 deletions

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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);
}
}