Moves classes from spring-cloud-security to spring-cloud-commons
This commit is contained in:
@@ -1146,6 +1146,95 @@ myrandom=${cachedrandom.appname.value}
|
||||
----
|
||||
====
|
||||
|
||||
[[spring-cloud-security]]
|
||||
== Security
|
||||
|
||||
[[spring-cloud-security-single-sign-on]]
|
||||
=== Single Sign On
|
||||
|
||||
NOTE: All of the OAuth2 SSO and resource server features moved to Spring Boot
|
||||
in version 1.3. You can find documentation in the
|
||||
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/[Spring Boot user guide].
|
||||
|
||||
[[spring-cloud-security-client-token-relay]]
|
||||
==== Client Token Relay
|
||||
|
||||
If your app is a user facing OAuth2 client (i.e. has declared
|
||||
`@EnableOAuth2Sso` or `@EnableOAuth2Client`) then it has an
|
||||
`OAuth2ClientContext` in request scope from Spring Boot. You can
|
||||
create your own `OAuth2RestTemplate` from this context and an
|
||||
autowired `OAuth2ProtectedResourceDetails`, and then the context will
|
||||
always forward the access token downstream, also refreshing the access
|
||||
token automatically if it expires. (These are features of Spring
|
||||
Security and Spring Boot.)
|
||||
|
||||
[[spring-cloud-security-resource-server-token-relay]]
|
||||
==== Resource Server Token Relay
|
||||
|
||||
If your app has `@EnableResourceServer` you might want to relay the
|
||||
incoming token downstream to other services. If you use a
|
||||
`RestTemplate` to contact the downstream services then this is just a
|
||||
matter of how to create the template with the right context.
|
||||
|
||||
If your service uses `UserInfoTokenServices` to authenticate incoming
|
||||
tokens (i.e. it is using the `security.oauth2.user-info-uri`
|
||||
configuration), then you can simply create an `OAuth2RestTemplate`
|
||||
using an autowired `OAuth2ClientContext` (it will be populated by the
|
||||
authentication process before it hits the backend code). Equivalently
|
||||
(with Spring Boot 1.4), you could inject a
|
||||
`UserInfoRestTemplateFactory` and grab its `OAuth2RestTemplate` in
|
||||
your configuration. For example:
|
||||
|
||||
.MyConfiguration.java
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2RestTemplate restTemplate(UserInfoRestTemplateFactory factory) {
|
||||
return factory.getUserInfoRestTemplate();
|
||||
}
|
||||
----
|
||||
|
||||
This rest template will then have the same `OAuth2ClientContext`
|
||||
(request-scoped) that is used by the authentication filter, so you can
|
||||
use it to send requests with the same access token.
|
||||
|
||||
If your app is not using `UserInfoTokenServices` but is still a client
|
||||
(i.e. it declares `@EnableOAuth2Client` or `@EnableOAuth2Sso`), then
|
||||
with Spring Security Cloud any `OAuth2RestOperations` that the user
|
||||
creates from an `@Autowired` `OAuth2Context` will also forward
|
||||
tokens. This feature is implemented by default as an MVC handler
|
||||
interceptor, so it only works in Spring MVC. If you are not using MVC
|
||||
you could use a custom filter or AOP interceptor wrapping an
|
||||
`AccessTokenContextRelay` to provide the same feature.
|
||||
|
||||
Here's a basic
|
||||
example showing the use of an autowired rest template created
|
||||
elsewhere ("foo.com" is a Resource Server accepting the same tokens as
|
||||
the surrounding app):
|
||||
|
||||
.MyController.java
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
private OAuth2RestOperations restTemplate;
|
||||
|
||||
@RequestMapping("/relay")
|
||||
public String relay() {
|
||||
ResponseEntity<String> response =
|
||||
restTemplate.getForEntity("https://foo.com/bar", String.class);
|
||||
return "Success! (" + response.getBody() + ")";
|
||||
}
|
||||
----
|
||||
|
||||
If you don't want to forward tokens (and that is a valid
|
||||
choice, since you might want to act as yourself, rather than the
|
||||
client that sent you the token), then you only need to create your own
|
||||
`OAuth2Context` instead of autowiring the default one.
|
||||
|
||||
Feign clients will also pick up an interceptor that uses the
|
||||
`OAuth2ClientContext` if it is available, so they should also do a
|
||||
token relay anywhere where a `RestTemplate` would.
|
||||
|
||||
== Configuration Properties
|
||||
|
||||
To see the list of all Spring Cloud Commons related configuration properties please check link:appendix.html[the Appendix page].
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -28,6 +28,7 @@
|
||||
<properties>
|
||||
<bintray.package>commons</bintray.package>
|
||||
<evictor.version>1.0.0</evictor.version>
|
||||
<spring-security-oauth2-autoconfigure.version>2.1.2.RELEASE</spring-security-oauth2-autoconfigure.version>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -150,6 +151,11 @@
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth.boot</groupId>
|
||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||
<version>${spring-security-oauth2-autoconfigure.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<modules>
|
||||
|
||||
@@ -108,6 +108,11 @@
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth.boot</groupId>
|
||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- REMOVE after boot build updates -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.hateoas</groupId>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.cloud.commons.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
|
||||
|
||||
/**
|
||||
* Convenience class for relaying an access token from the {@link SecurityContext} to the
|
||||
* {@link OAuth2ClientContext}. If successful then subsequent calls to an
|
||||
* {@link OAuth2RestTemplate} using the context contained here will use the same access
|
||||
* token. This is mostly useful for relaying calls to a resource server downstream to
|
||||
* other resource servers. If the access token expires there is no way to refresh it, so
|
||||
* expect an exception from downstream (propagating it to the caller is the best strategy,
|
||||
* so they can refresh it and try again).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AccessTokenContextRelay {
|
||||
|
||||
private OAuth2ClientContext context;
|
||||
|
||||
public AccessTokenContextRelay(OAuth2ClientContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to copy an access token from the security context into the oauth2 context.
|
||||
* @return true if the token was copied
|
||||
*/
|
||||
public boolean copyToken() {
|
||||
if (context.getAccessToken() == null) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null) {
|
||||
Object details = authentication.getDetails();
|
||||
if (details instanceof OAuth2AuthenticationDetails) {
|
||||
OAuth2AuthenticationDetails holder = (OAuth2AuthenticationDetails) details;
|
||||
String token = holder.getTokenValue();
|
||||
DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(token);
|
||||
String tokenType = holder.getTokenType();
|
||||
if (tokenType != null) {
|
||||
accessToken.setTokenType(tokenType);
|
||||
}
|
||||
context.setAccessToken(accessToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.cloud.commons.security;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
/**
|
||||
* Adds an MVC interceptor for relaying OAuth2 access tokens into the client context (if
|
||||
* there is one). In this way an incoming request to a resource server can be relayed
|
||||
* downstream just be using <code>@EnableOAuth2Client</code> and an
|
||||
* <code>OAuth2RestTemplate</code>. An MVC interceptor is used so as to have a minimal
|
||||
* impact on the call stack. If you are not using MVC you could use a custom filter or AOP
|
||||
* interceptor wrapping the same call to an {@link AccessTokenContextRelay}.
|
||||
*
|
||||
* <br/>
|
||||
*
|
||||
* N.B. an app that is using {@link UserInfoTokenServices} generally doesn't need this
|
||||
* interceptor, but it doesn't hurt to include it.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureAfter(OAuth2AutoConfiguration.class)
|
||||
@ResourceServerTokenRelayAutoConfiguration.ConditionalOnOAuth2ClientInResourceServer
|
||||
@ConditionalOnClass(ResourceServerConfiguration.class)
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnProperty(value = "spring.cloud.mvc.token-relay.enabled=true", matchIfMissing = true)
|
||||
public class ResourceServerTokenRelayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public AccessTokenContextRelay accessTokenContextRelay(OAuth2ClientContext context) {
|
||||
return new AccessTokenContextRelay(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link WebMvcConfigurer} for the access token interceptor.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public static class ResourceServerTokenRelayRegistrationAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
AccessTokenContextRelay accessTokenContextRelay;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(
|
||||
|
||||
new HandlerInterceptorAdapter() {
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler) throws Exception {
|
||||
accessTokenContextRelay.copyToken();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Conditional(OAuth2OnClientInResourceServerCondition.class)
|
||||
@interface ConditionalOnOAuth2ClientInResourceServer {
|
||||
|
||||
}
|
||||
|
||||
private static class OAuth2OnClientInResourceServerCondition extends AllNestedConditions {
|
||||
|
||||
OAuth2OnClientInResourceServerCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnBean(ResourceServerConfiguration.class)
|
||||
static class Server {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnBean(OAuth2ClientConfiguration.class)
|
||||
static class Client {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,8 @@ org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguratio
|
||||
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
|
||||
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
|
||||
org.springframework.cloud.configuration.CompatibilityVerifierAutoConfiguration,\
|
||||
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration
|
||||
org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration,\
|
||||
org.springframework.cloud.commons.security.ResourceServerTokenRelayAutoConfiguration
|
||||
# Environment Post Processors
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2015-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
|
||||
*
|
||||
* https://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.cloud.commons.security;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ResourceServerTokenRelayAutoConfigurationTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientNotConfigured() {
|
||||
this.context = new SpringApplicationBuilder(NoClientConfiguration.class)
|
||||
.properties("spring.config.name=test", "server.port=0", "spring.cloud.mvc.token-relay.enabled=true",
|
||||
"security.oauth2.resource.userInfoUri:https://example.com")
|
||||
.run();
|
||||
assertThat(this.context.containsBean("loadBalancedOauth2RestTemplate")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clientConfigured() throws Exception {
|
||||
this.context = new SpringApplicationBuilder(ClientConfiguration.class).properties("spring.config.name=test",
|
||||
"server.port=0", "security.oauth2.resource.userInfoUri:https://example.com",
|
||||
"security.oauth2.client.clientId=foo").run();
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest()));
|
||||
OAuth2ClientContext client = this.context.getBean(OAuth2ClientContext.class);
|
||||
assertThat(client.getAccessToken()).isNull();
|
||||
UserInfoTokenServices services = context.getBean(UserInfoTokenServices.class);
|
||||
OAuth2RestTemplate template = (OAuth2RestTemplate) ReflectionTestUtils.getField(services, "restTemplate");
|
||||
MockRestServiceServer server = MockRestServiceServer.createServer(template);
|
||||
server.expect(requestTo("https://example.com"))
|
||||
.andRespond(withSuccess("{\"id\":\"user\"}", MediaType.APPLICATION_JSON));
|
||||
services.loadAuthentication("FOO");
|
||||
assertThat(client.getAccessToken().getValue()).isEqualTo("FOO");
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableResourceServer
|
||||
protected static class NoClientConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableResourceServer
|
||||
@EnableOAuth2Sso
|
||||
protected static class ClientConfiguration {
|
||||
|
||||
@Bean
|
||||
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ProtectedResourceDetails resource,
|
||||
OAuth2ClientContext oauth2Context) {
|
||||
return new OAuth2RestTemplate(resource, oauth2Context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2015-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
|
||||
*
|
||||
* https://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.cloud.commons.security.tokenrelay;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.context.TestComponent;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.cloud.commons.security.AccessTokenContextRelay;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
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.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
/**
|
||||
* @author Peter Szanto (spring@szantocsalad.hu)
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
properties = { "security.oauth2.resource.jwt.keyValue=secret", "spring.cloud.mvc.token-relay.enabled=true",
|
||||
"spring.autoconfigure.exclude=" })
|
||||
public class ResourceServerTokenRelayTests {
|
||||
|
||||
protected static final String TOKEN_VALID_UNTIL_2085 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
|
||||
+ "eyJleHAiOjM2NDA2ODU4ODIsInVzZXJfbmFtZSI6InJlYWRlciIsImF1dGhvcml0aWVzIjpbIlJPTEVfUkVBREVSIl0s"
|
||||
+ "Imp0aSI6ImRkOTAzZGM2LTI0NDctNDViMi04MDZjLTIzZjU3ODVhNGQ4MCIsImNsaWVudF9pZCI6IndlYi1hcHAiLCJzY29wZSI6WyJyZWFkIl19."
|
||||
+ "6hoNtxmN1_o5Ki0D0ae4amSOTRmit3pmaqv-z1-Qk4Y";
|
||||
|
||||
protected static final String AUTH_HEADER_TO_BE_RELAYED = "Bearer " + TOKEN_VALID_UNTIL_2085;
|
||||
|
||||
protected static final String TEST_RESPONSE = "[\"test response\"]";
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate testRestTemplate;
|
||||
|
||||
@Autowired
|
||||
private MockRestServiceServer mockServerToReceiveRelay;
|
||||
|
||||
@SpyBean
|
||||
AccessTokenContextRelay accessTokenContextRelay;
|
||||
|
||||
@Test
|
||||
public void tokenRelayJWT() throws Exception {
|
||||
|
||||
mockServerToReceiveRelay.expect(requestTo("https://example.com/test"))
|
||||
.andExpect(header("authorization", AUTH_HEADER_TO_BE_RELAYED))
|
||||
.andRespond(withSuccess(TEST_RESPONSE, MediaType.APPLICATION_JSON));
|
||||
|
||||
HttpEntity<String> authorizationHeader = createAuthorizationHeader();
|
||||
ResponseEntity<String> exchange = testRestTemplate.exchange("/token-relay", HttpMethod.GET, authorizationHeader,
|
||||
String.class);
|
||||
|
||||
assertThat(exchange.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
|
||||
assertThat(exchange.getBody()).isEqualTo(TEST_RESPONSE);
|
||||
|
||||
mockServerToReceiveRelay.verify();
|
||||
verify(accessTokenContextRelay).copyToken();
|
||||
}
|
||||
|
||||
private HttpEntity<String> createAuthorizationHeader() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Authorization", AUTH_HEADER_TO_BE_RELAYED);
|
||||
return new HttpEntity<String>("parameters", headers);
|
||||
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
@TestConfiguration
|
||||
@EnableResourceServer
|
||||
@ComponentScan(basePackageClasses = TokenRelayTestController.class)
|
||||
@EnableOAuth2Client
|
||||
protected static class ClientConfiguration {
|
||||
|
||||
@Bean
|
||||
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ProtectedResourceDetails resource,
|
||||
OAuth2ClientContext oauth2Context) {
|
||||
return new OAuth2RestTemplate(resource, oauth2Context);
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MockRestServiceServer mockRestServiceServer(OAuth2RestTemplate template) {
|
||||
return MockRestServiceServer.createServer(template);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
@TestComponent
|
||||
protected static class TokenRelayTestController {
|
||||
|
||||
@Autowired
|
||||
OAuth2RestTemplate oAuth2RestTemplate;
|
||||
|
||||
@GetMapping("/token-relay")
|
||||
public String callAnotherService() {
|
||||
|
||||
return oAuth2RestTemplate.getForEntity("https://example.com/test", String.class).getBody();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,4 +6,7 @@ debug:true
|
||||
spring.cloud.hypermedia.refresh.initial-delay=50000
|
||||
spring.cloud.hypermedia.refresh.fixed-delay=10000
|
||||
management.security.enabled=false
|
||||
|
||||
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
|
||||
security.oauth2.resource.tokenRelay=false
|
||||
security.oauth2.client.clientId: acme
|
||||
security.oauth2.client.clientSecret: acmesecret
|
||||
|
||||
@@ -82,11 +82,21 @@
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth.boot</groupId>
|
||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2015-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
|
||||
*
|
||||
* https://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.cloud.loadbalancer.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateCustomizer;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OAuth2RestTemplate.class)
|
||||
@ConditionalOnProperty("spring.cloud.oauth2.load-balanced.enabled=true")
|
||||
@AutoConfigureAfter(OAuth2AutoConfiguration.class)
|
||||
public class OAuth2LoadBalancerClientAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(LoadBalancerInterceptor.class)
|
||||
protected static class UserInfoLoadBalancerConfig {
|
||||
|
||||
@Bean
|
||||
public UserInfoRestTemplateCustomizer loadBalancedUserInfoRestTemplateCustomizer(
|
||||
final LoadBalancerInterceptor loadBalancerInterceptor) {
|
||||
return new UserInfoRestTemplateCustomizer() {
|
||||
@Override
|
||||
public void customize(OAuth2RestTemplate restTemplate) {
|
||||
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
|
||||
interceptors.add(loadBalancerInterceptor);
|
||||
restTemplate.setInterceptors(interceptors);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(RetryLoadBalancerInterceptor.class)
|
||||
protected static class UserInfoRetryLoadBalancerConfig {
|
||||
|
||||
@Bean
|
||||
public UserInfoRestTemplateCustomizer retryLoadBalancedUserInfoRestTemplateCustomizer(
|
||||
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
|
||||
return new UserInfoRestTemplateCustomizer() {
|
||||
@Override
|
||||
public void customize(OAuth2RestTemplate restTemplate) {
|
||||
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(restTemplate.getInterceptors());
|
||||
interceptors.add(loadBalancerInterceptor);
|
||||
restTemplate.setInterceptors(interceptors);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,4 +2,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2015-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
|
||||
*
|
||||
* https://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.cloud.loadbalancer.security;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions("spring-retry-*.jar")
|
||||
public class OAuth2LoadBalancerClientAutoConfigurationTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoNotLoadBalanced() {
|
||||
this.context = new SpringApplicationBuilder(ClientConfiguration.class).properties("spring.config.name=test",
|
||||
"server.port=0", "security.oauth2.resource.userInfoUri:https://example.com").run();
|
||||
|
||||
assertThat(this.context.containsBean("loadBalancedUserInfoRestTemplateCustomizer")).isFalse();
|
||||
assertThat(this.context.containsBean("retryLoadBalancedUserInfoRestTemplateCustomizer")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoLoadBalancedNoRetry() throws Exception {
|
||||
this.context = new SpringApplicationBuilder(ClientConfiguration.class).properties("spring.config.name=test",
|
||||
"server.port=0", "security.oauth2.resource.userInfoUri:https://nosuchservice",
|
||||
"spring.cloud.oauth2.load-balanced.enabled=true").run();
|
||||
|
||||
assertThat(this.context.containsBean("loadBalancedUserInfoRestTemplateCustomizer")).isTrue();
|
||||
assertThat(this.context.containsBean("retryLoadBalancedUserInfoRestTemplateCustomizer")).isFalse();
|
||||
|
||||
OAuth2RestTemplate template = this.context.getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
|
||||
ClientHttpRequest request = template.getRequestFactory().createRequest(new URI("https://nosuchservice"),
|
||||
HttpMethod.GET);
|
||||
expected.expectMessage("No instances available for nosuchservice");
|
||||
request.execute();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableOAuth2Sso
|
||||
protected static class ClientConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,3 +25,7 @@ spring:
|
||||
uri: http://hhost
|
||||
- service-id: myservice
|
||||
uri: http://ihost
|
||||
autoconfigure.exclude:
|
||||
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
|
||||
- org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration
|
||||
- org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
|
||||
Reference in New Issue
Block a user