diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java
index 523906ab2a4..c98c46c3eab 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/OAuth2ResourceServerConfiguration.java
@@ -17,6 +17,9 @@
package org.springframework.boot.autoconfigure.security.oauth2.resource;
import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -45,6 +48,7 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.R
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
+import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -98,6 +102,36 @@ public class OAuth2ResourceServerConfiguration {
}
+ @Component
+ public static class ResourceServerFilterChainOrderProcessor
+ implements BeanPostProcessor {
+
+ private ResourceServerProperties resource;
+
+ @Autowired
+ public ResourceServerFilterChainOrderProcessor(
+ ResourceServerProperties resource) {
+ this.resource = resource;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName)
+ throws BeansException {
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName)
+ throws BeansException {
+ if (bean instanceof ResourceServerConfiguration) {
+ ResourceServerConfiguration config = (ResourceServerConfiguration) bean;
+ config.setOrder(this.resource.getFilterOrder());
+ }
+ return bean;
+ }
+
+ }
+
protected static class ResourceServerCondition extends SpringBootCondition
implements ConfigurationCondition {
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerProperties.java
index 056f73a3f3f..85fb6512beb 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerProperties.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerProperties.java
@@ -23,6 +23,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
@@ -77,6 +78,12 @@ public class ResourceServerProperties implements Validator, BeanFactoryAware {
private Jwt jwt = new Jwt();
+ /**
+ * The order of the filter chain used to authenticate tokens. Default puts it after
+ * the actuator endpoints and before the default HTTP basic filter chain (catchall).
+ */
+ private int filterOrder = SecurityProperties.ACCESS_OVERRIDE_ORDER - 1;
+
public ResourceServerProperties() {
this(null, null);
}
@@ -159,6 +166,14 @@ public class ResourceServerProperties implements Validator, BeanFactoryAware {
return this.clientSecret;
}
+ public int getFilterOrder() {
+ return this.filterOrder;
+ }
+
+ public void setFilterOrder(int filterOrder) {
+ this.filterOrder = filterOrder;
+ }
+
@Override
public boolean supports(Class> clazz) {
return ResourceServerProperties.class.isAssignableFrom(clazz);
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java
index 22bad3fbccf..eda0126ad14 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/OAuth2AutoConfigurationTests.java
@@ -21,11 +21,13 @@ import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
+
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
+import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.method.OAuth2MethodSecurityConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration;
@@ -43,6 +45,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.Order;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -437,6 +440,7 @@ public class OAuth2AutoConfigurationTests {
}
@Configuration
+ @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class TestSecurityConfiguration
extends WebSecurityConfigurerAdapter {
diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index 3a0217bcf61..75dc0f272fe 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -1,6 +1,4 @@
-
-
+
4.0.0
org.springframework.boot
spring-boot-dependencies
@@ -2563,4 +2561,4 @@
integration-test
-
+
\ No newline at end of file
diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
index 1fc9851b7c1..cc011c54f63 100644
--- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
+++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
@@ -2528,6 +2528,10 @@ WARNING: If you use the `security.oauth2.resource.jwt.key-uri` the authorization
needs to be running when your application starts up. It will log a warning if it can't
find the key, and tell you what to do to fix it.
+OAuth2 resources are protected by a filter chain with order
+`security.oauth2.resource.filter-order` and the default is after the
+filter protecting the actuator endpoints by default (so actuator
+endpoints will stay on HTTP Basic unless you change the order).
[[boot-features-security-oauth2-token-type]]
diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index 9f10e354c6a..6a07c185339 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -75,6 +75,7 @@
spring-boot-sample-property-validation
spring-boot-sample-secure
spring-boot-sample-secure-oauth2
+ spring-boot-sample-secure-oauth2-actuator
spring-boot-sample-secure-oauth2-resource
spring-boot-sample-servlet
spring-boot-sample-session-redis
diff --git a/spring-boot-samples/spring-boot-sample-ant/pom.xml b/spring-boot-samples/spring-boot-sample-ant/pom.xml
index 5c474c48fa1..3fa53b05d46 100644
--- a/spring-boot-samples/spring-boot-sample-ant/pom.xml
+++ b/spring-boot-samples/spring-boot-sample-ant/pom.xml
@@ -102,4 +102,4 @@
-
+
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/pom.xml b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/pom.xml
new file mode 100644
index 00000000000..8574cd3e629
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.5.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-secure-oauth2-actuator
+ spring-boot-sample-secure-oauth2-actuator
+ Spring Boot Security OAuth2 Actuator 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-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ 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-actuator/src/main/java/sample/secure/oauth2/resource/SampleSecureOAuth2ResourceApplication.java b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/main/java/sample/secure/oauth2/resource/SampleSecureOAuth2ResourceApplication.java
new file mode 100644
index 00000000000..761d5546b8c
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/main/java/sample/secure/oauth2/resource/SampleSecureOAuth2ResourceApplication.java
@@ -0,0 +1,61 @@
+/*
+ * 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.resource;
+
+import java.util.UUID;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@SpringBootApplication
+@EnableResourceServer
+@RestController
+public class SampleSecureOAuth2ResourceApplication {
+
+ @GetMapping("/")
+ public Message home() {
+ return new Message("Hello World");
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleSecureOAuth2ResourceApplication.class, args);
+ }
+
+}
+
+class Message {
+
+ private String id = UUID.randomUUID().toString();
+
+ private String value;
+
+ public Message(String value) {
+ this.value = value;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+}
\ No newline at end of file
diff --git a/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/main/resources/application.properties
new file mode 100644
index 00000000000..ac60cbc595d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+server.port=8081
+security.basic.enabled=true
+security.user.password=password
+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-actuator/src/test/java/sample/secure/oauth2/resource/SampleSecureOAuth2ResourceApplicationTests.java b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/test/java/sample/secure/oauth2/resource/SampleSecureOAuth2ResourceApplicationTests.java
new file mode 100644
index 00000000000..74bf07a735e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-secure-oauth2-actuator/src/test/java/sample/secure/oauth2/resource/SampleSecureOAuth2ResourceApplicationTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012-2016 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.resource;
+
+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.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.util.Base64Utils;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.hamcrest.CoreMatchers.containsString;
+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.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
+
+/**
+ * Series of automated integration tests to verify proper behavior of auto-configured,
+ * OAuth2-secured system
+ *
+ * @author Dave Syer
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+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 homePageSecuredByDefault() throws Exception {
+ this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
+ .andExpect(header().string("WWW-Authenticate", containsString("Bearer")))
+ .andDo(print());
+ }
+
+ @Test
+ public void healthAvailable() throws Exception {
+ this.mvc.perform(get("/health")).andExpect(status().isOk()).andDo(print());
+ }
+
+ @Test
+ public void envSecuredWithBasic() throws Exception {
+ this.mvc.perform(get("/env")).andExpect(status().isUnauthorized())
+ .andExpect(header().string("WWW-Authenticate", containsString("Basic")))
+ .andDo(print());
+ }
+
+ @Test
+ public void envWithPassword() throws Exception {
+ this.mvc.perform(get("/env").header("Authorization",
+ "Basic " + Base64Utils.encodeToString("user:password".getBytes())))
+ .andExpect(status().isOk()).andDo(print());
+ }
+
+}