From abd7bc0466722b2a6e2b145a630fdb342a7f1656 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 29 Oct 2015 08:40:12 +0000 Subject: [PATCH] Add OAuth2 resource server sample Shows how to use @EnableResourceServer in a pure resource server and configure the secure paths. --- .../AuthorizationServerProperties.java | 57 +++++++++ ...Auth2AuthorizationServerConfiguration.java | 21 +++- .../pom.xml | 56 +++++++++ .../java/sample/secure/oauth2/Flight.java | 110 ++++++++++++++++++ .../secure/oauth2/FlightRepository.java | 38 ++++++ ...SampleSecureOAuth2ResourceApplication.java | 37 ++++++ .../src/main/resources/application.properties | 6 + .../src/main/resources/data-h2.sql | 4 + ...eSecureOAuth2ResourceApplicationTests.java | 65 +++++++++++ .../oauth2/SampleSecureOAuth2Application.java | 10 ++ .../src/main/resources/application.properties | 1 + 11 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/AuthorizationServerProperties.java create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/Flight.java create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/FlightRepository.java create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/data-h2.sql create mode 100644 spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/test/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplicationTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/AuthorizationServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/AuthorizationServerProperties.java new file mode 100644 index 00000000000..e281e2e0730 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/AuthorizationServerProperties.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2015 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.authserver; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * @author Dave Syer + */ +@ConfigurationProperties("security.oauth2.authorization") +public class AuthorizationServerProperties { + + private String checkTokenAccess; + + private String tokenKeyAccess; + + private String realm; + + public String getCheckTokenAccess() { + return this.checkTokenAccess; + } + + public void setCheckTokenAccess(String checkTokenAccess) { + this.checkTokenAccess = checkTokenAccess; + } + + public String getTokenKeyAccess() { + return this.tokenKeyAccess; + } + + public void setTokenKeyAccess(String tokenKeyAccess) { + this.tokenKeyAccess = tokenKeyAccess; + } + + public String getRealm() { + return this.realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/OAuth2AuthorizationServerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/OAuth2AuthorizationServerConfiguration.java index fd6bd10e86e..ad598d435c6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/OAuth2AuthorizationServerConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/authserver/OAuth2AuthorizationServerConfiguration.java @@ -24,7 +24,6 @@ import javax.annotation.PostConstruct; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -44,6 +43,7 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.A import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.token.TokenStore; @@ -60,7 +60,7 @@ import org.springframework.security.oauth2.provider.token.TokenStore; @ConditionalOnClass(EnableAuthorizationServer.class) @ConditionalOnMissingBean(AuthorizationServerConfigurer.class) @ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class) -@EnableConfigurationProperties +@EnableConfigurationProperties(AuthorizationServerProperties.class) public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @@ -76,6 +76,9 @@ public class OAuth2AuthorizationServerConfiguration @Autowired(required = false) private TokenStore tokenStore; + @Autowired + private AuthorizationServerProperties properties; + @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { ClientDetailsServiceBuilder.ClientBuilder builder = clients @@ -105,6 +108,20 @@ public class OAuth2AuthorizationServerConfiguration } } + @Override + public void configure(AuthorizationServerSecurityConfigurer security) + throws Exception { + if (this.properties.getCheckTokenAccess() != null) { + security.checkTokenAccess(this.properties.getCheckTokenAccess()); + } + if (this.properties.getTokenKeyAccess() != null) { + security.tokenKeyAccess(this.properties.getTokenKeyAccess()); + } + if (this.properties.getRealm() != null) { + security.realm(this.properties.getRealm()); + } + } + @Configuration protected static class ClientDetailsLogger { diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/pom.xml b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/pom.xml new file mode 100644 index 00000000000..67e7f0e4092 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.3.0.BUILD-SNAPSHOT + + spring-boot-sample-secure-oauth2-resource + spring-boot-sample-secure-oauth2-resource + Spring Boot Security OAuth2 Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-data-rest + + + com.h2database + h2 + + + org.springframework.security.oauth + spring-security-oauth2 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/Flight.java b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/Flight.java new file mode 100644 index 00000000000..f83eb6f2ef7 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/Flight.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2015 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.secure.oauth2; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Domain object for tracking flights + * + * @author Craig Walls + * @author Greg Turnquist + */ +@Entity +@JsonIgnoreProperties(ignoreUnknown = true) +public class Flight { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String origin; + + private String destination; + + private String airline; + + private String flightNumber; + + private Date date; + + private String traveler; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getOrigin() { + return this.origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getDestination() { + return this.destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getAirline() { + return this.airline; + } + + public void setAirline(String airline) { + this.airline = airline; + } + + public String getFlightNumber() { + return this.flightNumber; + } + + public void setFlightNumber(String flightNumber) { + this.flightNumber = flightNumber; + } + + public Date getDate() { + return this.date; + } + + public void setDate(Date date) { + this.date = date; + } + + public String getTraveler() { + return this.traveler; + } + + public void setTraveler(String traveler) { + this.traveler = traveler; + } + +} diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/FlightRepository.java b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/FlightRepository.java new file mode 100644 index 00000000000..dbc7da5d5c1 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/FlightRepository.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2015 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.secure.oauth2; + +import org.springframework.data.repository.CrudRepository; + +/** + * Spring Data interface with secured methods + * + * @author Craig Walls + * @author Greg Turnquist + */ +public interface FlightRepository extends CrudRepository { + + @Override + Iterable findAll(); + + @Override + Flight findOne(Long aLong); + + @Override + S save(S entity); + +} diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplication.java b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplication.java new file mode 100644 index 00000000000..a1755beac8d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplication.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2015 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.secure.oauth2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; + +@SpringBootApplication +@EnableResourceServer +public class SampleSecureOAuth2ResourceApplication extends ResourceServerConfigurerAdapter { + + @Override + public void configure(HttpSecurity http) throws Exception { + http.antMatcher("/flights/**").authorizeRequests().anyRequest().authenticated(); + } + + public static void main(String[] args) { + SpringApplication.run(SampleSecureOAuth2ResourceApplication.class, args); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/application.properties new file mode 100644 index 00000000000..4da827f8782 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/application.properties @@ -0,0 +1,6 @@ +server.port=8081 +spring.datasource.platform=h2 +security.basic.enabled=false +security.oauth2.resource.id=service +security.oauth2.resource.userInfoUri=http://localhost:8080/user +logging.level.org.springframework.security=DEBUG diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/data-h2.sql b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/data-h2.sql new file mode 100644 index 00000000000..478a2ebf917 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/main/resources/data-h2.sql @@ -0,0 +1,4 @@ +insert into FLIGHT +(id, origin, destination, airline, flight_number, traveler) +values +(1, 'Nashville', 'Dallas', 'Spring Ways', 'OAUTH2', 'Greg Turnquist'); diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/test/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplicationTests.java b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/test/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplicationTests.java new file mode 100644 index 00000000000..92a3de744c8 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-resource/src/test/java/sample/secure/oauth2/SampleSecureOAuth2ResourceApplicationTests.java @@ -0,0 +1,65 @@ +package sample.secure.oauth2; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.hateoas.MediaTypes; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.context.WebApplicationContext; + +/** + * Series of automated integration tests to verify proper behavior of auto-configured, + * OAuth2-secured system + * + * @author Greg Turnquist + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(SampleSecureOAuth2ResourceApplication.class) +@WebIntegrationTest(randomPort = true) +public class SampleSecureOAuth2ResourceApplicationTests { + + @Autowired + WebApplicationContext context; + + @Autowired + FilterChainProxy filterChain; + + private MockMvc mvc; + @Before + public void setUp() { + this.mvc = webAppContextSetup(this.context).addFilters(this.filterChain).build(); + SecurityContextHolder.clearContext(); + } + + @Test + public void homePageAvailable() throws Exception { + this.mvc.perform(get("/").accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()).andDo(print()); + } + + @Test + public void flightsSecuredByDefault() throws Exception { + this.mvc.perform(get("/flights").accept(MediaTypes.HAL_JSON)) + .andExpect(status().isUnauthorized()).andDo(print()); + this.mvc.perform(get("/flights/1").accept(MediaTypes.HAL_JSON)) + .andExpect(status().isUnauthorized()).andDo(print()); + } + + @Test + public void profileAvailable() throws Exception { + this.mvc.perform(get("/profile").accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()).andDo(print()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/java/sample/secure/oauth2/SampleSecureOAuth2Application.java b/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/java/sample/secure/oauth2/SampleSecureOAuth2Application.java index 0f45be81510..c951b7a316c 100644 --- a/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/java/sample/secure/oauth2/SampleSecureOAuth2Application.java +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/java/sample/secure/oauth2/SampleSecureOAuth2Application.java @@ -15,11 +15,15 @@ */ package sample.secure.oauth2; +import java.security.Principal; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; /** * After you launch the app, you can seek a bearer token like this: @@ -92,7 +96,13 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.E @EnableAuthorizationServer @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true) +@RestController public class SampleSecureOAuth2Application { + + @RequestMapping("/user") + public Principal user(Principal user) { + return user; + } public static void main(String[] args) { SpringApplication.run(SampleSecureOAuth2Application.class, args); diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/resources/application.properties index 20f5b4d949f..6ab81af5dbc 100644 --- a/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/resources/application.properties +++ b/spring-boot-samples/spring-boot-sample-secure-oauth2/src/main/resources/application.properties @@ -4,5 +4,6 @@ security.user.name=greg security.user.password=turnquist security.oauth2.client.client-id=foo security.oauth2.client.client-secret=bar +security.oauth2.authorization.checkTokenAccess=isAuthenticated() logging.level.org.springframework.security=DEBUG