/* * Copyright 2012-2017 the original author or authors. * * 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.springframework.boot.autoconfigure.security.oauth2.resource; import java.io.IOException; import java.util.List; import java.util.Map; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration; import org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration; import org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.web.servlet.server.MockServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** * Tests for {@link ResourceServerTokenServicesConfiguration}. * * @author Dave Syer * @author Madhura Bhave * @author EddĂș MelĂ©ndez */ public class ResourceServerTokenServicesConfigurationTests { private static String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9" + "AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzE" + "tgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+T" + "Nu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG" + "8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8" + "dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB\n-----END PUBLIC KEY-----"; private ConfigurableApplicationContext context; private ConfigurableEnvironment environment = new StandardEnvironment(); @Rule public ExpectedException thrown = ExpectedException.none(); @After public void close() { if (this.context != null) { this.context.close(); } } @Test public void useRemoteTokenServices() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.tokenInfoUri:http://example.com"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); RemoteTokenServices services = this.context.getBean(RemoteTokenServices.class); assertThat(services).isNotNull(); } @Test public void switchToUserInfo() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); UserInfoTokenServices services = this.context .getBean(UserInfoTokenServices.class); assertThat(services).isNotNull(); } @Test public void userInfoWithAuthorities() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com"); this.context = new SpringApplicationBuilder(AuthoritiesConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); UserInfoTokenServices services = this.context .getBean(UserInfoTokenServices.class); assertThat(services).isNotNull(); assertThat(services).extracting("authoritiesExtractor") .containsExactly(this.context.getBean(AuthoritiesExtractor.class)); } @Test public void userInfoWithPrincipal() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com"); this.context = new SpringApplicationBuilder(PrincipalConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); UserInfoTokenServices services = this.context .getBean(UserInfoTokenServices.class); assertThat(services).isNotNull(); assertThat(services).extracting("principalExtractor") .containsExactly(this.context.getBean(PrincipalExtractor.class)); } @Test public void userInfoWithClient() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.client.client-id=acme", "security.oauth2.resource.userInfoUri:http://example.com", "server.port=-1", "debug=true"); this.context = new SpringApplicationBuilder(ResourceNoClientConfiguration.class) .environment(this.environment).web(WebApplicationType.SERVLET).run(); BeanDefinition bean = ((BeanDefinitionRegistry) this.context) .getBeanDefinition("scopedTarget.oauth2ClientContext"); assertThat(bean.getScope()).isEqualTo("request"); } @Test public void preferUserInfo() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com", "security.oauth2.resource.tokenInfoUri:http://example.com", "security.oauth2.resource.preferTokenInfo:false"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); UserInfoTokenServices services = this.context .getBean(UserInfoTokenServices.class); assertThat(services).isNotNull(); } @Test public void userInfoWithCustomizer() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com", "security.oauth2.resource.tokenInfoUri:http://example.com", "security.oauth2.resource.preferTokenInfo:false"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class, Customizer.class).environment(this.environment) .web(WebApplicationType.NONE).run(); UserInfoTokenServices services = this.context .getBean(UserInfoTokenServices.class); assertThat(services).isNotNull(); } @Test public void switchToJwt() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.jwt.keyValue=FOOBAR"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class); assertThat(services).isNotNull(); this.thrown.expect(NoSuchBeanDefinitionException.class); this.context.getBean(RemoteTokenServices.class); } @Test public void asymmetricJwt() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.jwt.keyValue=" + PUBLIC_KEY); this.context = new SpringApplicationBuilder(ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class); assertThat(services).isNotNull(); } @Test public void jwkConfiguration() throws Exception { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.jwk.key-set-uri=http://my-auth-server/token_keys"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class); assertThat(services).isNotNull(); this.thrown.expect(NoSuchBeanDefinitionException.class); this.context.getBean(RemoteTokenServices.class); } @Test public void springSocialUserInfo() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com", "spring.social.facebook.app-id=foo", "spring.social.facebook.app-secret=bar"); this.context = new SpringApplicationBuilder(SocialResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.SERVLET).run(); ConnectionFactoryLocator connectionFactory = this.context .getBean(ConnectionFactoryLocator.class); assertThat(connectionFactory).isNotNull(); SpringSocialTokenServices services = this.context .getBean(SpringSocialTokenServices.class); assertThat(services).isNotNull(); } @Test public void customUserInfoRestTemplateFactory() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.userInfoUri:http://example.com"); this.context = new SpringApplicationBuilder( CustomUserInfoRestTemplateFactory.class, ResourceConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); assertThat(this.context.getBeansOfType(UserInfoRestTemplateFactory.class)) .hasSize(1); assertThat(this.context.getBean(UserInfoRestTemplateFactory.class)) .isInstanceOf(CustomUserInfoRestTemplateFactory.class); } @Test public void jwtAccessTokenConverterIsConfiguredWhenKeyUriIsProvided() { EnvironmentTestUtils.addEnvironment(this.environment, "security.oauth2.resource.jwt.key-uri=http://localhost:12345/banana"); this.context = new SpringApplicationBuilder(ResourceConfiguration.class, JwtAccessTokenConverterRestTemplateCustomizerConfiguration.class) .environment(this.environment).web(WebApplicationType.NONE).run(); assertThat(this.context.getBeansOfType(JwtAccessTokenConverter.class)).hasSize(1); } @Configuration @Import({ ResourceServerTokenServicesConfiguration.class, ResourceServerPropertiesConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) @EnableConfigurationProperties(OAuth2ClientProperties.class) protected static class ResourceConfiguration { } @Configuration protected static class AuthoritiesConfiguration extends ResourceConfiguration { @Bean AuthoritiesExtractor authoritiesExtractor() { return new AuthoritiesExtractor() { @Override public List<GrantedAuthority> extractAuthorities( Map<String, Object> map) { return AuthorityUtils .commaSeparatedStringToAuthorityList("ROLE_ADMIN"); } }; } } @Configuration protected static class PrincipalConfiguration extends ResourceConfiguration { @Bean PrincipalExtractor principalExtractor() { return new PrincipalExtractor() { @Override public Object extractPrincipal(Map<String, Object> map) { return "boot"; } }; } } @Import({ OAuth2RestOperationsConfiguration.class }) protected static class ResourceNoClientConfiguration extends ResourceConfiguration { @Bean public MockServletWebServerFactory webServerFactory() { return new MockServletWebServerFactory(); } } @Configuration protected static class ResourceServerPropertiesConfiguration { private OAuth2ClientProperties credentials; public ResourceServerPropertiesConfiguration(OAuth2ClientProperties credentials) { this.credentials = credentials; } @Bean public ResourceServerProperties resourceServerProperties() { return new ResourceServerProperties(this.credentials.getClientId(), this.credentials.getClientSecret()); } } @Import({ FacebookAutoConfiguration.class, SocialWebAutoConfiguration.class }) protected static class SocialResourceConfiguration extends ResourceConfiguration { @Bean public ServletWebServerFactory webServerFactory() { return mock(ServletWebServerFactory.class); } } @Component protected static class Customizer implements UserInfoRestTemplateCustomizer { @Override public void customize(OAuth2RestTemplate template) { template.getInterceptors().add(new ClientHttpRequestInterceptor() { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { return execution.execute(request, body); } }); } } @Component protected static class CustomUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory { private final OAuth2RestTemplate restTemplate = new OAuth2RestTemplate( new AuthorizationCodeResourceDetails()); @Override public OAuth2RestTemplate getUserInfoRestTemplate() { return this.restTemplate; } } @Configuration static class JwtAccessTokenConverterRestTemplateCustomizerConfiguration { @Bean public JwtAccessTokenConverterRestTemplateCustomizer restTemplateCustomizer() { return new MockRestCallCustomizer(); } } private static class MockRestCallCustomizer implements JwtAccessTokenConverterRestTemplateCustomizer { @Override public void customize(RestTemplate template) { template.getInterceptors().add(new ClientHttpRequestInterceptor() { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { String payload = "{\"value\":\"FOO\"}"; MockClientHttpResponse response = new MockClientHttpResponse( payload.getBytes(), HttpStatus.OK); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); return response; } }); } } }