diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index ceb8d168dc..6140047e77 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -553,6 +553,11 @@ spring-security-oauth2-client true + + org.springframework.security + spring-security-oauth2-resource-server + true + org.springframework.session spring-session-core diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java new file mode 100644 index 0000000000..3758be17ed --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerProperties.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2018 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 org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * OAuth 2.0 resource server properties. + * + * @author Madhura Bhave + */ +@ConfigurationProperties(prefix = "spring.security.oauth2.resource") +public class OAuth2ResourceServerProperties { + + private Jwt jwt = new Jwt(); + + public Jwt getJwt() { + return this.jwt; + } + + public void setJwt(Jwt jwt) { + this.jwt = jwt; + } + + public static class Jwt { + + private Jwk jwk = new Jwk(); + + public Jwk getJwk() { + return this.jwk; + } + + public void setJwk(Jwk jwk) { + this.jwk = jwk; + } + + } + + public static class Jwk { + + /** + * The URI to get verification keys to verify the JWT token. + */ + private String setUri; + + public String getSetUri() { + return this.setUri; + } + + public void setSetUri(String setUri) { + this.setUri = setUri; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/package-info.java new file mode 100644 index 0000000000..ff8ebd6d02 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2018 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. + */ + +/** + * Support for Spring Security's OAuth 2 resource server. + */ +package org.springframework.boot.autoconfigure.security.oauth2.resource; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java new file mode 100644 index 0000000000..d2685b3965 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfiguration.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2018 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.servlet; + +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OAuth resource server support. + * + * @author Madhura Bhave + * @since 2.1.0 + */ +@Configuration +@AutoConfigureBefore(SecurityAutoConfiguration.class) +@EnableConfigurationProperties(OAuth2ResourceServerProperties.class) +@ConditionalOnClass(JwtAuthenticationToken.class) +@Import({ OAuth2ResourceServerJwkConfiguration.class, + OAuth2ResourceServerWebSecurityConfiguration.class }) +public class OAuth2ResourceServerAutoConfiguration { + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwkConfiguration.java new file mode 100644 index 0000000000..8180ca1405 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwkConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2018 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.servlet; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; + +/** + * Configures a {@link JwtDecoder} when a JWK Set URI is available. + * + * @author Madhura Bhave + */ +@Configuration +class OAuth2ResourceServerJwkConfiguration { + + private final OAuth2ResourceServerProperties properties; + + public OAuth2ResourceServerJwkConfiguration( + OAuth2ResourceServerProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnProperty(name = "spring.security.oauth2.resource.jwt.jwk.set-uri") + @ConditionalOnMissingBean + public JwtDecoder jwtDecoder() { + return new NimbusJwtDecoderJwkSupport( + this.properties.getJwt().getJwk().getSetUri()); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerWebSecurityConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerWebSecurityConfiguration.java new file mode 100644 index 0000000000..7a6e6f31b5 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerWebSecurityConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2018 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.servlet; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.oauth2.jwt.JwtDecoder; + +/** + * {@link WebSecurityConfigurerAdapter} for OAuth2 resource server support. + * + * @author Madhura Bhave + */ +@Configuration +@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +class OAuth2ResourceServerWebSecurityConfiguration { + + @Configuration + @ConditionalOnBean(JwtDecoder.class) + static class OAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { + + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().anyRequest().authenticated().and().oauth2() + .resourceServer().jwt(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/package-info.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/package-info.java new file mode 100644 index 0000000000..bdde811133 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2018 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. + */ + +/** + * Auto-configuration for Spring Security's OAuth 2 resource server. + */ +package org.springframework.boot.autoconfigure.security.oauth2.resource.servlet; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 3b49f16c6f..c8dd4ff1da 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -105,6 +105,7 @@ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\ org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\ +org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskExecutorAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java new file mode 100644 index 0000000000..78ba79f0fe --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2012-2018 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.servlet; + +import java.util.List; + +import javax.servlet.Filter; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OAuth2ResourceServerAutoConfiguration}. + * + * @author Madhura Bhave + */ +public class OAuth2ResourceServerAutoConfigurationTests { + + private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(OAuth2ResourceServerAutoConfiguration.class)) + .withUserConfiguration(TestConfig.class); + + @Test + public void autoConfigurationShouldConfigureResourceServer() { + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com") + .run((context) -> { + assertThat(context.getBean(JwtDecoder.class)) + .isInstanceOf(NimbusJwtDecoderJwkSupport.class); + assertThat(getBearerTokenFilter(context)).isNotNull(); + }); + } + + @Test + public void autoConfigurationWhenJwkSetUriNullShouldNotFail() { + this.contextRunner + .run((context) -> assertThat(getBearerTokenFilter(context)).isNull()); + } + + @Test + public void jwtDecoderBeanIsConditionalOnMissingBean() { + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com") + .withUserConfiguration(JwtDecoderConfig.class) + .run((context) -> assertThat(getBearerTokenFilter(context)).isNotNull()); + } + + @Test + public void autoConfigurationShouldBeConditionalOnJwtAuthenticationTokenClass() { + this.contextRunner.withPropertyValues( + "spring.security.oauth2.resource.jwt.jwk.set-uri=http://jwk-set-uri.com") + .withUserConfiguration(JwtDecoderConfig.class) + .withClassLoader(new FilteredClassLoader(JwtAuthenticationToken.class)) + .run((context) -> assertThat(getBearerTokenFilter(context)).isNull()); + } + + @SuppressWarnings("unchecked") + private Filter getBearerTokenFilter(AssertableWebApplicationContext context) { + FilterChainProxy filterChain = (FilterChainProxy) context + .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); + List filterChains = filterChain.getFilterChains(); + List filters = (List) ReflectionTestUtils + .getField(filterChains.get(0), "filters"); + return filters.stream() + .filter((f) -> f instanceof BearerTokenAuthenticationFilter).findFirst() + .orElse(null); + } + + @Configuration + @EnableWebSecurity + static class TestConfig { + + } + + @Configuration + @EnableWebSecurity + static class JwtDecoderConfig { + + @Bean + public JwtDecoder decoder() { + return mock(JwtDecoder.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 7343cefff1..22c461b96d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -529,6 +529,9 @@ content into your application. Rather, pick only the properties that you need. spring.security.oauth2.client.provider.*= # OAuth provider details. spring.security.oauth2.client.registration.*= # OAuth client registrations. + # SECURITY OAUTH2 RESOURCE SERVER ({sc-spring-boot-autoconfigure}/security/oauth2/resource/OAuth2ResourceServerProperties.{sc-ext}[OAuth2ResourceServerProperties]) + spring.security.oauth2.resource.jwt.jwk.set-uri.*= # JWK Set URI + # ---------------------------------------- # DATA PROPERTIES # ---------------------------------------- diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index fb243687d3..e0284ba337 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3276,12 +3276,26 @@ Provider can be configured with the `issuer-uri`: [[boot-features-security-oauth2-server]] -==== Server +==== Resource Server +If you have `spring-security-oauth2-resource-server` on your classpath, you can take advantage of +some auto-configuration to make it easy to set up an OAuth2 Resource Server. This configuration +makes use of the properties under `OAuth2ResourceServerProperties`. + +For the OAuth2 Resource Server auto-configuration to kick in, you can +provide a bean of type `JwtDecoder` yourself or configure a JWK Set URI as follows: + +[source,properties,indent=0] +---- + spring.security.oauth2.resource.jwt.jwk.set-uri=https://example.com/oauth2/default/v1/keys +---- + + +==== Authorization Server Currently, Spring Security does not provide support for implementing an OAuth 2.0 -Authorization Server or Resource Server. However, this functionality is available from +Authorization Server. However, this functionality is available from the https://projects.spring.io/spring-security-oauth/[Spring Security OAuth] project, which will eventually be superseded by Spring Security completely. Until then, you can -use the `spring-security-oauth2-autoconfigure` module to easily set up an OAuth 2.0 server; +use the `spring-security-oauth2-autoconfigure` module to easily set up an OAuth 2.0 authorization server; see its https://docs.spring.io/spring-security-oauth2-boot[documentation] for instructions. diff --git a/spring-boot-samples/spring-boot-sample-oauth2-resource-server/pom.xml b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/pom.xml new file mode 100644 index 0000000000..649d19b77d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + ${revision} + + spring-boot-sample-oauth2-resource-server + Spring Boot Sample OAuth2 Resource Server + Spring Boot Sample Resource Server + + ${basedir}/../.. + + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-oauth2-resource-server + + + org.springframework.security + spring-security-oauth2-jose + + + + com.squareup.okhttp3 + mockwebserver + 3.9.0 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/java/sample/oauth2/resource/ExampleController.java b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/java/sample/oauth2/resource/ExampleController.java new file mode 100644 index 0000000000..00a7be9743 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/java/sample/oauth2/resource/ExampleController.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2018 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 sample.oauth2.resource; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ExampleController { + + @GetMapping("/") + public String index(@AuthenticationPrincipal Jwt jwt) { + return String.format("Hello, %s!", jwt.getSubject()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/java/sample/oauth2/resource/SampleOauth2ResourceServerApplication.java b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/java/sample/oauth2/resource/SampleOauth2ResourceServerApplication.java new file mode 100644 index 0000000000..f80400458e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/java/sample/oauth2/resource/SampleOauth2ResourceServerApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-2018 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 sample.oauth2.resource; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleOauth2ResourceServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleOauth2ResourceServerApplication.class); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/resources/application.yml b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/resources/application.yml new file mode 100644 index 0000000000..2599bf4091 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/main/resources/application.yml @@ -0,0 +1,8 @@ +spring: + security: + oauth2: + resource: + jwt: + jwk: + # To run the application, replace this with a valid JWK Set URI + set-uri: https://example.com/oauth2/default/v1/keys \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/test/java/sample/oauth2/resource/SampleOauth2ResourceServerApplicationTests.java b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/test/java/sample/oauth2/resource/SampleOauth2ResourceServerApplicationTests.java new file mode 100644 index 0000000000..062bf013f1 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-oauth2-resource-server/src/test/java/sample/oauth2/resource/SampleOauth2ResourceServerApplicationTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2018 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 sample.oauth2.resource; + +import java.io.IOException; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SampleOauth2ResourceServerApplicationTests { + + private static MockWebServer server = new MockWebServer(); + + private static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzdWJqZWN0Iiwic2NvcGUiOiJtZXNzYWdlOnJlYWQi" + + "LCJleHAiOjQ2ODM4MDUxNDF9.h-j6FKRFdnTdmAueTZCdep45e6DPwqM68ZQ8doIJ1exi9YxAlbWzOwId6Bd0L5YmCmp63gGQgsBUBLzwnZQ8kLUgU" + + "OBEC3UzSWGRqMskCY9_k9pX0iomX6IfF3N0PaYs0WPC4hO1s8wfZQ-6hKQ4KigFi13G9LMLdH58PRMK0pKEvs3gCbHJuEPw-K5ORlpdnleUTQIwIN" + + "afU57cmK3KocTeknPAM_L716sCuSYGvDl6xUTXO7oPdrXhS_EhxLP6KxrpI1uD4Ea_5OWTh7S0Wx5LLDfU6wBG1DowN20d374zepOIEkR-Jnmr_Ql" + + "R44vmRqS5ncrF-1R0EGcPX49U6A"; + + @Autowired + private TestRestTemplate restTemplate; + + @BeforeClass + public static void setup() throws Exception { + server.start(); + String url = server.url("/.well-known/jwks.json").toString(); + server.enqueue(mockResponse()); + System.setProperty("spring.security.oauth2.resource.jwt.jwk.set-uri", url); + } + + @AfterClass + public static void shutdown() throws IOException { + server.shutdown(); + System.clearProperty("spring.security.oauth2.resource.jwt.jwk.set-uri"); + } + + @Test + public void withValidBearerTokenShouldAllowAccess() { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(VALID_TOKEN); + HttpEntity request = new HttpEntity(headers); + ResponseEntity entity = this.restTemplate.exchange("/", HttpMethod.GET, + request, String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void withNoBearerTokenShouldNotAllowAccess() { + HttpHeaders headers = new HttpHeaders(); + HttpEntity request = new HttpEntity(headers); + ResponseEntity entity = this.restTemplate.exchange("/", HttpMethod.GET, + request, String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + private static MockResponse mockResponse() { + String body = "{\"keys\":[{\"p\":\"2p-ViY7DE9ZrdWQb544m0Jp7Cv03YCSljqfim9pD4ALhObX0OrAznOiowTjwBky9JGffMw" + + "DBVSfJSD9TSU7aH2sbbfi0bZLMdekKAuimudXwUqPDxrrg0BCyvCYgLmKjbVT3zcdylWSog93CNTxGDPzauu-oc0XPNKCXnaDpNvE\"" + + ",\"kty\":\"RSA\",\"q\":\"sP_QYavrpBvSJ86uoKVGj2AGl78CSsAtpf1ybSY5TwUlorXSdqapRbY69Y271b0aMLzlleUn9ZTBO" + + "1dlKV2_dw_lPADHVia8z3pxL-8sUhIXLsgj4acchMk4c9YX-sFh07xENnyZ-_TXm3llPLuL67HUfBC2eKe800TmCYVWc9U\",\"d\"" + + ":\"bn1nFxCQT4KLTHqo8mo9HvHD0cRNRNdWcKNnnEQkCF6tKbt-ILRyQGP8O40axLd7CoNVG9c9p_-g4-2kwCtLJNv_STLtwfpCY7" + + "VN5o6-ZIpfTjiW6duoPrLWq64Hm_4LOBQTiZfUPcLhsuJRHbWqakj-kV_YbUyC2Ocf_dd8IAQcSrAU2SCcDebhDCWwRUFvaa9V5eq0" + + "851S9goaA-AJz-JXyePH6ZFr8JxmWkWxYZ5kdcMD-sm9ZbxE0CaEk32l4fE4hR-L8x2dDtjWA-ahKCZ091z-gV3HWtR2JOjvxoNRjxUo" + + "3UxaGiFJHWNIl0EYUJZu1Cb-5wIlEI7wPx5mwQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"qS0OK4" + + "8M2CIAA6_4Wdw4EbCaAfcTLf5Oy9t5BOF_PFUKqoSpZ6JsT5H0a_4zkjt-oI969v78OTlvBKbmEyKO-KeytzHBAA5CsLmVcz0THrMSg6o" + + "XZqu66MPnvWoZN9FEN5TklPOvBFm8Bg1QZ3k-YMVaM--DLvhaYR95_mqaz50\",\"dp\":\"Too2NozLGD1XrXyhabZvy1E0EuaVFj0UHQ" + + "PDLSpkZ_2g3BK6Art6T0xmE8RYtmqrKIEIdlI3IliAvyvAx_1D7zWTTRaj-xlZyqJFrnXWL7zj8UxT8PkB-r2E-ILZ3NAi1gxIWezlBTZ8" + + "M6NfObDFmbTc_3tJkN_raISo8z_ziIE\",\"dq\":\"U0yhSkY5yOsa9YcMoigGVBWSJLpNHtbg5NypjHrPv8OhWbkOSq7WvSstBkF" + + "k5AtyFvvfZLMLIkWWxxGzV0t6f1MoxBtttLrYYyCxwihiiGFhLbAdSuZ1wnxcqA9bC7UVECvrQmVTpsMs8UupfHKbQBpZ8OWAqrn" + + "uYNNtG4_4Bt0\",\"n\":\"lygtuZj0lJjqOqIWocF8Bb583QDdq-aaFg8PesOp2-EDda6GqCpL-_NZVOflNGX7XIgjsWHcPsQHs" + + "V9gWuOzSJ0iEuWvtQ6eGBP5M6m7pccLNZfwUse8Cb4Ngx3XiTlyuqM7pv0LPyppZusfEHVEdeelou7Dy9k0OQ_nJTI3b2E1WBoHC5" + + "8CJ453lo4gcBm1efURN3LIVc1V9NQY_ESBKVdwqYyoJPEanURLVGRd6cQKn6YrCbbIRHjqAyqOE-z3KmgDJnPriljfR5XhSGyM9eq" + + "D9Xpy6zu_MAeMJJfSArp857zLPk-Wf5VP9STAcjyfdBIybMKnwBYr2qHMT675hQ\"}]}"; + return new MockResponse() + .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .setResponseCode(200).setBody(body); + } + +} \ No newline at end of file