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.KeycloakAuthenticationProvider;
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
|
import org.keycloak.adapters.springsecurity.authentication.KeycloakLogoutHandler;
|
||||||
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
|
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.filter.KeycloakPreAuthActionsFilter;
|
||||||
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
|
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -59,6 +60,10 @@ public abstract class KeycloakWebSecurityConfigurerAdapter extends WebSecurityCo
|
||||||
return new KeycloakPreAuthActionsFilter(httpSessionManager());
|
return new KeycloakPreAuthActionsFilter(httpSessionManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected KeycloakCsrfRequestMatcher keycloakCsrfRequestMatcher() {
|
||||||
|
return new KeycloakCsrfRequestMatcher();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
protected HttpSessionManager httpSessionManager() {
|
protected HttpSessionManager httpSessionManager() {
|
||||||
return new 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