Merge pull request #4303 from jmcshane/master

KEYCLOAK-5173 Spring Boot KeycloakRestTemplate support
This commit is contained in:
Bill Burke 2017-08-13 12:22:45 -04:00 committed by GitHub
commit 61ab6d5b8b
5 changed files with 210 additions and 2 deletions

View file

@ -31,7 +31,9 @@
<description/>
<properties>
<spring-boot.version>1.3.0.RELEASE</spring-boot.version>
<spring-boot.version>1.4.0.RELEASE</spring-boot.version>
<spring.version>4.1.6.RELEASE</spring.version>
<mockito.version>1.9.5</mockito.version>
</properties>
<dependencies>
@ -98,7 +100,19 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>

View file

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

View file

@ -0,0 +1,55 @@
package org.keycloak.adapters.springboot.client;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
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;
/**
* Interceptor for {@link ClientHttpRequestExecution} objects created for server to server secured
* communication using OAuth2 bearer tokens issued by Keycloak.
*
* @author <a href="mailto:jmcshan1@gmail.com">James McShane</a>
* @version $Revision: 1 $
*/
public class KeycloakSecurityContextClientRequestInterceptor implements ClientHttpRequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
/**
* 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 <code>KeycloakSecurityContext</code>
*/
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();
}
@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);
}
}

View file

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

View file

@ -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 KeycloakSecurityContextClientRequestInterceptorTest {
@Spy
private KeycloakSecurityContextClientRequestInterceptor 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;
}
}
}