From e99b08c6dad25e69f2b340e065e27bb46ef25993 Mon Sep 17 00:00:00 2001 From: jmcshane Date: Fri, 7 Jul 2017 23:31:52 -0500 Subject: [PATCH 1/2] Adding a instance of HttpComponentsClientHttpRequestFactory that supports the embedded servlet container auth pattern --- adapters/oidc/spring-boot/pom.xml | 16 +++- .../EmbeddedServletClientRequestFactory.java | 47 ++++++++++ ...beddedServletClientRequestFactoryTest.java | 87 +++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java create mode 100644 adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml index 6a720f65a5..3ff64a7e6f 100755 --- a/adapters/oidc/spring-boot/pom.xml +++ b/adapters/oidc/spring-boot/pom.xml @@ -32,6 +32,8 @@ 1.3.0.RELEASE + 4.1.6.RELEASE + 1.9.5 @@ -98,7 +100,19 @@ junit test - + + org.springframework + spring-test + ${spring.version} + test + + + org.mockito + mockito-all + ${mockito.version} + test + + org.springframework.boot spring-boot-configuration-processor true diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java new file mode 100644 index 0000000000..046d71c8c1 --- /dev/null +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java @@ -0,0 +1,47 @@ +package org.keycloak.adapters.springboot.client; + +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.security.Principal; + +/** + * Factory for {@link ClientHttpRequest} objects created for server to server secured + * communication using OAuth2 bearer tokens issued by Keycloak. + * + * @author James McShane + * @version $Revision: 1 $ + */ +public class EmbeddedServletClientRequestFactory extends KeycloakClientRequestFactory { + + public EmbeddedServletClientRequestFactory() { + super(); + } + + /** + * Returns the {@link KeycloakSecurityContext} from the Spring {@link ServletRequestAttributes}'s {@link Principal}. + * + * The principal must support retrieval of the KeycloakSecurityContext, so at this point, only {@link KeycloakPrincipal} + * values are supported + * + * @return the current KeycloakSecurityContext + */ + protected KeycloakSecurityContext getKeycloakSecurityContext() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + Principal principal = attributes.getRequest().getUserPrincipal(); + if (principal == null) { + throw new IllegalStateException("Cannot set authorization header because there is no authenticated principal"); + } + if (!(principal instanceof KeycloakPrincipal)) { + throw new IllegalStateException( + String.format( + "Cannot set authorization header because the principal type %s does not provide the KeycloakSecurityContext", + principal.getClass())); + } + return ((KeycloakPrincipal) principal).getKeycloakSecurityContext(); + } +} diff --git a/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java b/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java new file mode 100644 index 0000000000..1e1bd174f7 --- /dev/null +++ b/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java @@ -0,0 +1,87 @@ +/* + * 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.adapters.springboot.client; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.security.Principal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +/** + * Keycloak spring boot client request factory tests. + */ +public class EmbeddedServletClientRequestFactoryTest { + + @Spy + private EmbeddedServletClientRequestFactory factory; + + private MockHttpServletRequest servletRequest; + + @Mock + private KeycloakSecurityContext keycloakSecurityContext; + + @Mock + private KeycloakPrincipal keycloakPrincipal; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + servletRequest = new MockHttpServletRequest(); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest)); + servletRequest.setUserPrincipal(keycloakPrincipal); + when(keycloakPrincipal.getKeycloakSecurityContext()).thenReturn(keycloakSecurityContext); + } + + @Test + public void testGetKeycloakSecurityContext() throws Exception { + KeycloakSecurityContext context = factory.getKeycloakSecurityContext(); + assertNotNull(context); + assertEquals(keycloakSecurityContext, context); + } + + @Test(expected = IllegalStateException.class) + public void testGetKeycloakSecurityContextInvalidPrincipal() throws Exception { + servletRequest.setUserPrincipal(new MarkerPrincipal()); + factory.getKeycloakSecurityContext(); + } + + @Test(expected = IllegalStateException.class) + public void testGetKeycloakSecurityContextNullAuthentication() throws Exception { + servletRequest.setUserPrincipal(null); + factory.getKeycloakSecurityContext(); + } + + private static class MarkerPrincipal implements Principal { + @Override + public String getName() { + return null; + } + } +} From ec89aab8fbde0b00040c8782d556c28ca3236bf6 Mon Sep 17 00:00:00 2001 From: c5403 Date: Mon, 24 Jul 2017 11:15:01 -0500 Subject: [PATCH 2/2] Refactoring the spring-boot adapter to use the rest template customizer --- adapters/oidc/spring-boot/pom.xml | 2 +- .../KeycloakRestTemplateCustomizer.java | 24 ++++++++++++++++ ...urityContextClientRequestInterceptor.java} | 22 ++++++++++----- .../KeycloakRestTemplateCustomizerTest.java | 28 +++++++++++++++++++ ...yContextClientRequestInterceptorTest.java} | 4 +-- 5 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizer.java rename adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/{EmbeddedServletClientRequestFactory.java => KeycloakSecurityContextClientRequestInterceptor.java} (63%) create mode 100644 adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizerTest.java rename adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/{EmbeddedServletClientRequestFactoryTest.java => KeycloakSecurityContextClientRequestInterceptorTest.java} (95%) diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml index 3ff64a7e6f..901a5fa4c5 100755 --- a/adapters/oidc/spring-boot/pom.xml +++ b/adapters/oidc/spring-boot/pom.xml @@ -31,7 +31,7 @@ - 1.3.0.RELEASE + 1.4.0.RELEASE 4.1.6.RELEASE 1.9.5 diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizer.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizer.java new file mode 100644 index 0000000000..ae4836c713 --- /dev/null +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizer.java @@ -0,0 +1,24 @@ +package org.keycloak.adapters.springboot.client; + +import org.springframework.boot.web.client.RestTemplateCustomizer; +import org.springframework.web.client.RestTemplate; + +public class KeycloakRestTemplateCustomizer implements RestTemplateCustomizer { + + private final KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor; + + public KeycloakRestTemplateCustomizer() { + this(new KeycloakSecurityContextClientRequestInterceptor()); + } + + protected KeycloakRestTemplateCustomizer( + KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor + ) { + this.keycloakInterceptor = keycloakInterceptor; + } + + @Override + public void customize(RestTemplate restTemplate) { + restTemplate.getInterceptors().add(keycloakInterceptor); + } +} diff --git a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakSecurityContextClientRequestInterceptor.java similarity index 63% rename from adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java rename to adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakSecurityContextClientRequestInterceptor.java index 046d71c8c1..200a9035f1 100644 --- a/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactory.java +++ b/adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/client/KeycloakSecurityContextClientRequestInterceptor.java @@ -2,25 +2,26 @@ package org.keycloak.adapters.springboot.client; import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; -import org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory; -import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import java.io.IOException; import java.security.Principal; /** - * Factory for {@link ClientHttpRequest} objects created for server to server secured + * Interceptor for {@link ClientHttpRequestExecution} objects created for server to server secured * communication using OAuth2 bearer tokens issued by Keycloak. * * @author James McShane * @version $Revision: 1 $ */ -public class EmbeddedServletClientRequestFactory extends KeycloakClientRequestFactory { +public class KeycloakSecurityContextClientRequestInterceptor implements ClientHttpRequestInterceptor { - public EmbeddedServletClientRequestFactory() { - super(); - } + private static final String AUTHORIZATION_HEADER = "Authorization"; /** * Returns the {@link KeycloakSecurityContext} from the Spring {@link ServletRequestAttributes}'s {@link Principal}. @@ -44,4 +45,11 @@ public class EmbeddedServletClientRequestFactory extends KeycloakClientRequestFa } return ((KeycloakPrincipal) principal).getKeycloakSecurityContext(); } + + @Override + public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { + KeycloakSecurityContext context = this.getKeycloakSecurityContext(); + httpRequest.getHeaders().set(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString()); + return clientHttpRequestExecution.execute(httpRequest, bytes); + } } diff --git a/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizerTest.java b/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizerTest.java new file mode 100644 index 0000000000..e8e599e40d --- /dev/null +++ b/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakRestTemplateCustomizerTest.java @@ -0,0 +1,28 @@ +package org.keycloak.adapters.springboot.client; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.web.client.RestTemplate; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class KeycloakRestTemplateCustomizerTest { + + private KeycloakRestTemplateCustomizer customizer; + private KeycloakSecurityContextClientRequestInterceptor interceptor = + mock(KeycloakSecurityContextClientRequestInterceptor.class); + + @Before + public void setup() { + customizer = new KeycloakRestTemplateCustomizer(interceptor); + } + + @Test + public void interceptorIsAddedToRequest() { + RestTemplate restTemplate = new RestTemplate(); + customizer.customize(restTemplate); + assertTrue(restTemplate.getInterceptors().contains(interceptor)); + } + +} diff --git a/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java b/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakSecurityContextClientRequestInterceptorTest.java similarity index 95% rename from adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java rename to adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakSecurityContextClientRequestInterceptorTest.java index 1e1bd174f7..689cc65274 100644 --- a/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/EmbeddedServletClientRequestFactoryTest.java +++ b/adapters/oidc/spring-boot/src/test/java/org/keycloak/adapters/springboot/client/KeycloakSecurityContextClientRequestInterceptorTest.java @@ -37,10 +37,10 @@ import static org.mockito.Mockito.when; /** * Keycloak spring boot client request factory tests. */ -public class EmbeddedServletClientRequestFactoryTest { +public class KeycloakSecurityContextClientRequestInterceptorTest { @Spy - private EmbeddedServletClientRequestFactory factory; + private KeycloakSecurityContextClientRequestInterceptor factory; private MockHttpServletRequest servletRequest;