Add support for OAuth relay in zuul proxy
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -72,6 +72,11 @@
|
||||
<artifactId>eureka-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.zuul</groupId>
|
||||
<artifactId>zuul-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.platform.cloudfoundry.proxy;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass({ ZuulFilter.class, EnableOAuth2Client.class, SecurityProperties.class })
|
||||
@ConditionalOnWebApplication
|
||||
public class CloudfoundryProxyConfiguration {
|
||||
|
||||
@Bean
|
||||
public CloudfoundryTokenFilter cloudfoundryTokenFilter() {
|
||||
return new CloudfoundryTokenFilter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.springframework.platform.cloudfoundry.proxy;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.provider.OAuth2Authentication;
|
||||
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
public class CloudfoundryTokenFilter extends ZuulFilter {
|
||||
|
||||
private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth instanceof OAuth2Authentication) {
|
||||
Object details = auth.getDetails();
|
||||
if (details instanceof OAuth2AuthenticationDetails) {
|
||||
OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
ctx.addZuulRequestHeader("authorization", "Bearer " + ctx.get(ACCESS_TOKEN));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.platform.cloudfoundry.resource;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.ResourceServerConfiguration;
|
||||
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.security.oauth2.provider.token.RemoteTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnExpression("'${cloudfoundry.resource.clientId:${vcap.services.resource.credentials.clientId:}}'!=''")
|
||||
@ConditionalOnClass({ EnableResourceServer.class, SecurityProperties.class })
|
||||
@ConditionalOnWebApplication
|
||||
@EnableResourceServer
|
||||
@EnableConfigurationProperties(CloudfoundryResourceProperties.class)
|
||||
public class CloudfoundryResourceConfiguration {
|
||||
|
||||
@Autowired
|
||||
private CloudfoundryResourceProperties resource;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerConfigurer.class)
|
||||
public ResourceServerConfigurer resourceServer() {
|
||||
return new ResourceSecurityConfigurer(resource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public ResourceServerTokenServices tokenServices() {
|
||||
RemoteTokenServices services = new RemoteTokenServices();
|
||||
services.setCheckTokenEndpointUrl(resource.getTokenInfoUri());
|
||||
services.setClientId(resource.getClientId());
|
||||
services.setClientSecret(resource.getClientSecret());
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected static class ResourceSecurityConfigurer extends ResourceServerConfigurerAdapter {
|
||||
|
||||
private CloudfoundryResourceProperties resource;
|
||||
|
||||
@Autowired
|
||||
public ResourceSecurityConfigurer(CloudfoundryResourceProperties resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ResourceServerSecurityConfigurer resources)
|
||||
throws Exception {
|
||||
resources.resourceId(resource.getClientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().anyRequest().authenticated();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class ResourceServerOrderProcessor implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
if (bean instanceof ResourceServerConfiguration) {
|
||||
ResourceServerConfiguration configuration = (ResourceServerConfiguration) bean;
|
||||
configuration.setOrder(getOrder());
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
private int getOrder() {
|
||||
if (ClassUtils
|
||||
.isPresent(
|
||||
"org.springframework.boot.actuate.autoconfigure.ManagementServerProperties",
|
||||
null)) {
|
||||
return ManagementServerProperties.ACCESS_OVERRIDE_ORDER - 10;
|
||||
}
|
||||
return SecurityProperties.ACCESS_OVERRIDE_ORDER - 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.platform.cloudfoundry.resource;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties("cloudfoundry.resource")
|
||||
@Data
|
||||
public class CloudfoundryResourceProperties implements Validator {
|
||||
|
||||
private String serviceId = "resource";
|
||||
|
||||
@Value("${vcap.services.${cloudfoundry.resource.serviceId:resource}.credentials.clientId:}")
|
||||
private String clientId;
|
||||
|
||||
@Value("${vcap.services.${cloudfoundry.resource.serviceId:resource}.credentials.clientSecret:}")
|
||||
private String clientSecret;
|
||||
|
||||
@Value("${vcap.services.${cloudfoundry.resource.serviceId:resource}.credentials.tokenInfoUri:}")
|
||||
private String tokenInfoUri;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return CloudfoundryResourceProperties.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
CloudfoundryResourceProperties resource = (CloudfoundryResourceProperties) target;
|
||||
if (StringUtils.hasText(resource.getClientId())) {
|
||||
if (!StringUtils.hasText(resource.getTokenInfoUri())) {
|
||||
errors.rejectValue("tokenInfoUri", "missing.tokenInfoUri", "Missing tokenInfoUri");
|
||||
}
|
||||
if (!StringUtils.hasText(resource.getClientSecret())) {
|
||||
errors.rejectValue("clientSecret", "missing.clientSecret", "Missing clientSecret");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.platform.cloudfoundry.resource;
|
||||
|
||||
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 org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import(CloudfoundryResourceConfiguration.class)
|
||||
public @interface EnableCloudfoundryResource {
|
||||
|
||||
}
|
||||
@@ -63,7 +63,7 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnExpression("'${cloudfoundry.sso.tokenUri:${vcap.services.sso.credentials.tokenUri:}}'!=''")
|
||||
@ConditionalOnExpression("'${cloudfoundry.sso.clientId:${vcap.services.sso.credentials.clientId:}}'!=''")
|
||||
@ConditionalOnClass({ EnableOAuth2Client.class, SecurityProperties.class })
|
||||
@ConditionalOnWebApplication
|
||||
@EnableOAuth2Client
|
||||
|
||||
@@ -71,12 +71,12 @@ public class CloudfoundrySsoProperties implements Validator {
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
CloudfoundrySsoProperties sso = (CloudfoundrySsoProperties) target;
|
||||
if (StringUtils.hasText(sso.getTokenUri())) {
|
||||
if (StringUtils.hasText(sso.getClientId())) {
|
||||
if (!StringUtils.hasText(sso.getAuthorizationUri())) {
|
||||
errors.rejectValue("authorizeUri", "missing.authorizeUri", "Missing authorizeUri");
|
||||
}
|
||||
if (!StringUtils.hasText(sso.getClientId())) {
|
||||
errors.rejectValue("clientId", "missing.clientId", "Missing clientId");
|
||||
if (!StringUtils.hasText(sso.getTokenUri())) {
|
||||
errors.rejectValue("tokenUri", "missing.tokenUri", "Missing tokenUri");
|
||||
}
|
||||
if (!StringUtils.hasText(sso.getClientSecret())) {
|
||||
errors.rejectValue("clientSecret", "missing.clientSecret", "Missing clientSecret");
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.platform.cloudfoundry.broker.configuration.ServiceBrokerAutoConfiguration
|
||||
org.springframework.platform.cloudfoundry.broker.configuration.ServiceBrokerAutoConfiguration,\
|
||||
org.springframework.platform.cloudfoundry.proxy.CloudfoundryProxyConfiguration
|
||||
@@ -33,16 +33,18 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = Application.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest("server.port=0")
|
||||
@IntegrationTest({ "server.port=0", "security.basic.enabled=false",
|
||||
"cloudfoundry.sso.clientId=", "cloudfoundry.resource.clientId=" })
|
||||
public class ApplicationTests {
|
||||
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Test
|
||||
public void catalogLoads() {
|
||||
@SuppressWarnings("rawtypes")
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity("http://localhost:" + port + "/v2/catalog", Map.class);
|
||||
ResponseEntity<Map> entity = new TestRestTemplate().getForEntity(
|
||||
"http://localhost:" + port + "/v2/catalog", Map.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user