Merge pull request #1231 from Smartling/KEYCLOAK-1286
Permit Spring Security adapter to process admin tasks with CSRF enabled
This commit is contained in:
commit
96668daefa
3 changed files with 140 additions and 0 deletions
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue