diff --git a/pom.xml b/pom.xml index 79fe022..16fdbde 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ org.springframework.social spring-social-core + true org.projectlombok diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ClientConfiguration.java b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ClientConfiguration.java new file mode 100644 index 0000000..c3f6e45 --- /dev/null +++ b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ClientConfiguration.java @@ -0,0 +1,120 @@ +/* + * 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.cloud.cloudfoundry.oauth2; + +import java.io.IOException; +import java.util.Arrays; + +import javax.annotation.Resource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.embedded.FilterRegistrationBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.RequestEnhancer; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; +import org.springframework.util.MultiValueMap; + +/** + * @author Dave Syer + * + */ +@Configuration +@EnableOAuth2Client +@EnableConfigurationProperties(OAuth2ClientProperties.class) +public class ClientConfiguration { + + @Autowired + private OAuth2ClientProperties sso; + + @Resource + @Qualifier("accessTokenRequest") + private AccessTokenRequest accessTokenRequest; + + @Bean + public FilterRegistrationBean oauth2ClientFilterRegistration( + OAuth2ClientContextFilter filter) { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(filter); + registration.setOrder(0); + return registration; + } + + @Bean + public OAuth2ProtectedResourceDetails oauth2RemoteResource() { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + // set up resource details, OAuth2 URLs etc. + details.setClientId(sso.getClientId()); + details.setClientSecret(sso.getClientSecret()); + details.setAccessTokenUri(sso.getTokenUri()); + details.setUserAuthorizationUri(sso.getAuthorizationUri()); + details.setClientAuthenticationScheme(sso.getAuthenticationScheme()); + return details; + } + + @Bean + public OAuth2RestOperations oauth2RestTemplate() { + OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2RemoteResource(), + oauth2ClientContext()); + template.setInterceptors(Arrays + . asList(new ClientHttpRequestInterceptor() { + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().setAccept( + Arrays.asList(MediaType.APPLICATION_JSON)); + return execution.execute(request, body); + } + })); + AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); + accessTokenProvider.setTokenRequestEnhancer(new RequestEnhancer() { + @Override + public void enhance(AccessTokenRequest request, + OAuth2ProtectedResourceDetails resource, + MultiValueMap form, HttpHeaders headers) { + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + } + }); + template.setAccessTokenProvider(accessTokenProvider); + return template; + } + + @Bean + @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) + public OAuth2ClientContext oauth2ClientContext() { + return new DefaultOAuth2ClientContext(accessTokenRequest); + } + +} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/OAuth2ClientProperties.java b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/OAuth2ClientProperties.java new file mode 100644 index 0000000..5bf5651 --- /dev/null +++ b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/OAuth2ClientProperties.java @@ -0,0 +1,72 @@ +/* + * 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.cloud.cloudfoundry.oauth2; + +import lombok.Data; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.util.StringUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +/** + * @author Dave Syer + * + */ +@ConfigurationProperties("oauth2.client") +@Data +public class OAuth2ClientProperties implements Validator { + + @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:${vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenUri:}}") + private String tokenUri; + + @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.authorizationUri:${vcap.services.${oauth2.resource.serviceId:resource}.credentials.authorizationUri:}}") + private String authorizationUri; + + @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientId:${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientId:}}") + private String clientId; + + @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientSecret:${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientSecret:}}") + private String clientSecret; + + private AuthenticationScheme authenticationScheme = AuthenticationScheme.header; + + @Override + public boolean supports(Class clazz) { + return OAuth2ClientProperties.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + OAuth2ClientProperties sso = (OAuth2ClientProperties) target; + if (StringUtils.hasText(sso.getClientId())) { + if (!StringUtils.hasText(sso.getAuthorizationUri())) { + errors.rejectValue("authorizeUri", "missing.authorizeUri", + "Missing authorizeUri"); + } + if (!StringUtils.hasText(sso.getTokenUri())) { + errors.rejectValue("tokenUri", "missing.tokenUri", "Missing tokenUri"); + } + if (!StringUtils.hasText(sso.getClientSecret())) { + errors.rejectValue("clientSecret", "missing.clientSecret", + "Missing clientSecret"); + } + } + } + +} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerProperties.java b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerProperties.java index 0832fd3..6649b59 100644 --- a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerProperties.java +++ b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerProperties.java @@ -16,7 +16,9 @@ package org.springframework.cloud.cloudfoundry.oauth2; import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.StringUtils; @@ -29,19 +31,16 @@ import org.springframework.validation.Validator; */ @ConfigurationProperties("oauth2.resource") @Data +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class ResourceServerProperties implements Validator { + + private final OAuth2ClientProperties client; private String serviceId = "resource"; @Value("${vcap.services.${oauth2.resource.serviceId:resource}.credentials.id:}") private String id; - @Value("${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientId:${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientId:}}") - private String clientId; - - @Value("${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientSecret:${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientSecret:}}") - private String clientSecret; - @Value("${vcap.services.${oauth2.resource.serviceId:resource}.credentials.userInfoUri:${vcap.services.${oauth2.sso.serviceId:sso}.credentials.userInfoUri:}}") private String userInfoUri; @@ -51,7 +50,7 @@ public class ResourceServerProperties implements Validator { private boolean preferTokenInfo = true; public String getResourceId() { - return !StringUtils.hasText(id) ? clientId : id; + return id; } @Override @@ -62,8 +61,8 @@ public class ResourceServerProperties implements Validator { @Override public void validate(Object target, Errors errors) { ResourceServerProperties resource = (ResourceServerProperties) target; - if (StringUtils.hasText(resource.getClientId())) { - if (!StringUtils.hasText(resource.getClientSecret())) { + if (StringUtils.hasText(client.getClientId())) { + if (!StringUtils.hasText(client.getClientSecret())) { if (!StringUtils.hasText(resource.getUserInfoUri())) { errors.rejectValue("userInfoUri", "missing.userInfoUri", "Missing userInfoUri (no client secret available)"); diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerTokenServicesConfiguration.java b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerTokenServicesConfiguration.java index 42a8e11..fce767e 100644 --- a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerTokenServicesConfiguration.java +++ b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/ResourceServerTokenServicesConfiguration.java @@ -16,7 +16,6 @@ package org.springframework.cloud.cloudfoundry.oauth2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -25,7 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.OAuth2RestOperations; +import org.springframework.context.annotation.Import; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.social.connect.support.OAuth2ConnectionFactory; @@ -36,19 +35,23 @@ import org.springframework.social.connect.support.OAuth2ConnectionFactory; */ @Configuration @EnableConfigurationProperties(ResourceServerProperties.class) +@Import(ClientConfiguration.class) public class ResourceServerTokenServicesConfiguration { @Autowired private ResourceServerProperties resource; + @Autowired + private OAuth2ClientProperties client; + @Bean @ConditionalOnMissingBean(ResourceServerTokenServices.class) @ConditionalOnExpression("${oauth2.resource.preferTokenInfo:${OAUTH2_RESOURCE_PREFERTOKENINFO:true}}") protected RemoteTokenServices remoteTokenServices() { RemoteTokenServices services = new RemoteTokenServices(); services.setCheckTokenEndpointUrl(resource.getTokenInfoUri()); - services.setClientId(resource.getClientId()); - services.setClientSecret(resource.getClientSecret()); + services.setClientId(client.getClientId()); + services.setClientSecret(client.getClientSecret()); return services; } @@ -60,26 +63,24 @@ public class ResourceServerTokenServicesConfiguration { @Autowired private ResourceServerProperties sso; - @Autowired(required = false) - private OAuth2ConnectionFactory connectionFactory; + @Autowired + private OAuth2ClientProperties client; @Autowired(required = false) - @Qualifier("oauth2RestTemplate") - private OAuth2RestOperations restTemplate; + private OAuth2ConnectionFactory connectionFactory; @Bean @ConditionalOnBean(OAuth2ConnectionFactory.class) @ConditionalOnMissingBean(ResourceServerTokenServices.class) public SpringSocialTokenServices socialTokenServices() { - return new SpringSocialTokenServices(connectionFactory, sso.getClientId()); + return new SpringSocialTokenServices(connectionFactory, client.getClientId()); } @Bean @ConditionalOnMissingBean({ OAuth2ConnectionFactory.class, ResourceServerTokenServices.class }) public UserInfoTokenServices userInfoTokenServices() { - return new UserInfoTokenServices(restTemplate, sso.getUserInfoUri(), - sso.getClientId()); + return new UserInfoTokenServices(sso.getUserInfoUri(), client.getClientId()); } } @@ -93,14 +94,12 @@ public class ResourceServerTokenServicesConfiguration { private ResourceServerProperties sso; @Autowired - @Qualifier("oauth2RestTemplate") - private OAuth2RestOperations restTemplate; + private OAuth2ClientProperties client; @Bean @ConditionalOnMissingBean(ResourceServerTokenServices.class) public UserInfoTokenServices userInfoTokenServices() { - return new UserInfoTokenServices(restTemplate, sso.getUserInfoUri(), - sso.getClientId()); + return new UserInfoTokenServices(sso.getUserInfoUri(), client.getClientId()); } } diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/UserInfoTokenServices.java b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/UserInfoTokenServices.java index 0f73bbc..16f5d84 100644 --- a/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/UserInfoTokenServices.java +++ b/src/main/java/org/springframework/cloud/cloudfoundry/oauth2/UserInfoTokenServices.java @@ -22,12 +22,14 @@ import org.apache.commons.logging.LogFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; -import org.springframework.web.client.RestOperations; /** * @author Dave Syer @@ -37,15 +39,11 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { protected final Log logger = LogFactory.getLog(getClass()); - private RestOperations restTemplate; - private String userInfoEndpointUrl; private String clientId; - public UserInfoTokenServices(RestOperations restTemplate, - String userInfoEndpointUrl, String clientId) { - this.restTemplate = restTemplate; + public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) { this.userInfoEndpointUrl = userInfoEndpointUrl; this.clientId = clientId; } @@ -54,7 +52,7 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException { - Map map = getMap(userInfoEndpointUrl); + Map map = getMap(userInfoEndpointUrl, accessToken); if (map.containsKey("error")) { logger.debug("userinfo returned error: " + map.get("error")); @@ -90,11 +88,17 @@ public class UserInfoTokenServices implements ResourceServerTokenServices { throw new UnsupportedOperationException("Not supported: read access token"); } - private Map getMap(String path) { + private Map getMap(String path, String accessToken) { + logger.info("Getting user info from :" + path); + BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails(); + resource.setClientId(clientId); + OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resource); + restTemplate.getOAuth2ClientContext().setAccessToken(new DefaultOAuth2AccessToken(accessToken)); @SuppressWarnings("rawtypes") Map map = restTemplate.getForEntity(path, Map.class).getBody(); @SuppressWarnings("unchecked") Map result = map; return result; } + } \ No newline at end of file diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/resource/OAuth2ResourceConfiguration.java b/src/main/java/org/springframework/cloud/cloudfoundry/resource/OAuth2ResourceConfiguration.java index 10a4ab9..318172a 100644 --- a/src/main/java/org/springframework/cloud/cloudfoundry/resource/OAuth2ResourceConfiguration.java +++ b/src/main/java/org/springframework/cloud/cloudfoundry/resource/OAuth2ResourceConfiguration.java @@ -42,7 +42,7 @@ import org.springframework.util.ClassUtils; * */ @Configuration -@ConditionalOnExpression("'${oauth2.resource.clientId:${vcap.services.resource.credentials.clientId:}}'!=''") +@ConditionalOnExpression("'${oauth2.client.clientId:${vcap.services.resource.credentials.clientId:}}'!=''") @ConditionalOnClass({ EnableResourceServer.class, SecurityProperties.class }) @ConditionalOnWebApplication @EnableResourceServer diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoConfiguration.java b/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoConfiguration.java index bb2b8d1..bd687cf 100644 --- a/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoConfiguration.java +++ b/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoConfiguration.java @@ -16,11 +16,9 @@ package org.springframework.cloud.cloudfoundry.sso; import java.io.IOException; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -31,214 +29,126 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.SecurityProperties; -import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.cloudfoundry.oauth2.ResourceServerTokenServicesConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpRequest; -import org.springframework.http.MediaType; -import org.springframework.http.client.ClientHttpRequestExecution; -import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; -import org.springframework.security.oauth2.client.OAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestOperations; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter; -import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; -import org.springframework.security.oauth2.client.token.AccessTokenRequest; -import org.springframework.security.oauth2.client.token.RequestEnhancer; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; -import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; /** * @author Dave Syer * */ @Configuration -@ConditionalOnExpression("'${oauth2.sso.clientId:${vcap.services.sso.credentials.clientId:}}'!=''") -@ConditionalOnClass({ EnableOAuth2Client.class, SecurityProperties.class }) +@ConditionalOnExpression("'${oauth2.client.clientId:${vcap.services.sso.credentials.clientId:}}'!=''") +@ConditionalOnClass({ ResourceServerTokenServices.class, SecurityProperties.class }) @ConditionalOnWebApplication -@EnableOAuth2Client @EnableConfigurationProperties(OAuth2SsoProperties.class) @Import(ResourceServerTokenServicesConfiguration.class) -public class OAuth2SsoConfiguration { +public class OAuth2SsoConfiguration extends WebSecurityConfigurerAdapter implements Ordered { + + @Autowired + private OAuth2ProtectedResourceDetails remote; @Autowired private OAuth2SsoProperties sso; - @Resource - @Qualifier("accessTokenRequest") - private AccessTokenRequest accessTokenRequest; + @Autowired + private ResourceServerTokenServices tokenServices; - @Bean - public FilterRegistrationBean oauth2ClientFilterRegistration( - OAuth2ClientContextFilter filter) { - FilterRegistrationBean registration = new FilterRegistrationBean(); - registration.setFilter(filter); - registration.setOrder(0); - return registration; + @Autowired + @Qualifier("oauth2RestTemplate") + private OAuth2RestOperations restTemplate; + + private List configurers = Collections.emptyList(); + + @Override + public int getOrder() { + if (ClassUtils + .isPresent( + "org.springframework.boot.actuate.autoconfigure.ManagementServerProperties", + null)) { + return ManagementServerProperties.ACCESS_OVERRIDE_ORDER; + } + return SecurityProperties.ACCESS_OVERRIDE_ORDER; } - @Bean - public OAuth2ProtectedResourceDetails oauth2RemoteResource() { - AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); - // set up resource details, OAuth2 URLs etc. - details.setClientId(sso.getClientId()); - details.setClientSecret(sso.getClientSecret()); - details.setAccessTokenUri(sso.getTokenUri()); - details.setUserAuthorizationUri(sso.getAuthorizationUri()); - details.setClientAuthenticationScheme(sso.getAuthenticationScheme()); - return details; + /** + * @param configurers the configurers to set + */ + @Autowired(required = false) + public void setConfigurers(List configurers) { + this.configurers = configurers; } - @Bean - public OAuth2RestOperations oauth2RestTemplate() { - OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2RemoteResource(), - oauth2ClientContext()); - template.setInterceptors(Arrays - . asList(new ClientHttpRequestInterceptor() { - @Override - public ClientHttpResponse intercept(HttpRequest request, byte[] body, - ClientHttpRequestExecution execution) throws IOException { - request.getHeaders().setAccept( - Arrays.asList(MediaType.APPLICATION_JSON)); - return execution.execute(request, body); - } - })); - AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider(); - accessTokenProvider.setTokenRequestEnhancer(new RequestEnhancer() { + @Override + protected void configure(HttpSecurity http) throws Exception { + + http.addFilterAfter(cloudfoundrySsoFilter(), + AbstractPreAuthenticatedProcessingFilter.class); + + for (OAuth2SsoConfigurer configurer : configurers) { + // Delegates can add authorizeRequests() here + configurer.configure(http); + } + if (configurers.isEmpty()) { + // Add anyRequest() last as a fall back. Spring Security would replace an + // existing anyRequest() matcher with this one, so to avoid that we only + // add it if the user hasn't configured anything. + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry requests = http + .antMatcher("/**").authorizeRequests(); + if (!sso.getHome().isSecure()) { + requests.antMatchers(sso.getHome().getPath()).permitAll(); + } + requests.anyRequest().authenticated(); + } + + http.logout() + .logoutRequestMatcher(new AntPathRequestMatcher(sso.getLogoutPath())) + .addLogoutHandler(logoutHandler()).permitAll(); + http.exceptionHandling().authenticationEntryPoint( + new LoginUrlAuthenticationEntryPoint(sso.getLoginPath())); + + } + + protected OAuth2ClientAuthenticationProcessingFilter cloudfoundrySsoFilter() { + OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( + sso.getLoginPath()); + filter.setRestTemplate(restTemplate); + filter.setTokenServices(tokenServices); + return filter; + } + + private LogoutHandler logoutHandler() { + LogoutHandler handler = new LogoutHandler() { @Override - public void enhance(AccessTokenRequest request, - OAuth2ProtectedResourceDetails resource, - MultiValueMap form, HttpHeaders headers) { - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); - } - }); - template.setAccessTokenProvider(accessTokenProvider); - return template; - } - - @Bean - @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES) - public OAuth2ClientContext oauth2ClientContext() { - return new DefaultOAuth2ClientContext(accessTokenRequest); - } - - @Configuration - protected static class SsoSecurityConfigurer extends WebSecurityConfigurerAdapter - implements Ordered { - - @Autowired - private OAuth2ProtectedResourceDetails remote; - - @Autowired - private OAuth2SsoProperties sso; - - @Autowired - private ResourceServerTokenServices tokenServices; - - @Autowired - @Qualifier("oauth2RestTemplate") - private OAuth2RestOperations restTemplate; - - private List configurers = Collections.emptyList(); - - @Override - public int getOrder() { - if (ClassUtils - .isPresent( - "org.springframework.boot.actuate.autoconfigure.ManagementServerProperties", - null)) { - return ManagementServerProperties.ACCESS_OVERRIDE_ORDER; - } - return SecurityProperties.ACCESS_OVERRIDE_ORDER; - } - - /** - * @param configurers the configurers to set - */ - @Autowired(required = false) - public void setConfigurers(List configurers) { - this.configurers = configurers; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - - http.addFilterAfter(cloudfoundrySsoFilter(), - AbstractPreAuthenticatedProcessingFilter.class); - - for (OAuth2SsoConfigurer configurer : configurers) { - // Delegates can add authorizeRequests() here - configurer.configure(http); - } - if (configurers.isEmpty()) { - // Add anyRequest() last as a fall back. Spring Security would replace an - // existing anyRequest() matcher with this one, so to avoid that we only - // add it if the user hasn't configured anything. - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry requests = http - .antMatcher("/**").authorizeRequests(); - if (!sso.getHome().isSecure()) { - requests.antMatchers(sso.getHome().getPath()).permitAll(); + public void logout(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + restTemplate.getOAuth2ClientContext().setAccessToken(null); + String redirect = request.getRequestURL().toString() + .replace(sso.getLogoutPath(), sso.getHome().getPath()); + try { + response.sendRedirect(sso.getLogoutUri(redirect)); } - requests.anyRequest().authenticated(); - } - - http.logout() - .logoutRequestMatcher(new AntPathRequestMatcher(sso.getLogoutPath())) - .addLogoutHandler(logoutHandler()).permitAll(); - http.exceptionHandling().authenticationEntryPoint( - new LoginUrlAuthenticationEntryPoint(sso.getLoginPath())); - - } - - protected OAuth2ClientAuthenticationProcessingFilter cloudfoundrySsoFilter() { - OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter( - sso.getLoginPath()); - filter.setRestTemplate(restTemplate); - filter.setTokenServices(tokenServices); - return filter; - } - - private LogoutHandler logoutHandler() { - LogoutHandler handler = new LogoutHandler() { - @Override - public void logout(HttpServletRequest request, - HttpServletResponse response, Authentication authentication) { - restTemplate.getOAuth2ClientContext().setAccessToken(null); - String redirect = request.getRequestURL().toString() - .replace(sso.getLogoutPath(), sso.getHome().getPath()); - try { - response.sendRedirect(sso.getLogoutUri(redirect)); - } - catch (IOException e) { - throw new IllegalStateException("Cannot logout remote server", e); - } + catch (IOException e) { + throw new IllegalStateException("Cannot logout remote server", e); } - }; - return handler; - } - + } + }; + return handler; } } diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoProperties.java b/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoProperties.java index 239aba2..18735f4 100644 --- a/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoProperties.java +++ b/src/main/java/org/springframework/cloud/cloudfoundry/sso/OAuth2SsoProperties.java @@ -16,13 +16,13 @@ package org.springframework.cloud.cloudfoundry.sso; import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.cloud.cloudfoundry.oauth2.OAuth2ClientProperties; import org.springframework.util.StringUtils; -import org.springframework.validation.Errors; -import org.springframework.validation.Validator; /** * @author Dave Syer @@ -30,7 +30,10 @@ import org.springframework.validation.Validator; */ @ConfigurationProperties("oauth2.sso") @Data -public class OAuth2SsoProperties implements Validator { +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class OAuth2SsoProperties { + + private final OAuth2ClientProperties client; private String serviceId = "sso"; @@ -41,20 +44,6 @@ public class OAuth2SsoProperties implements Validator { private String loginPath = "/login"; - @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:}") - private String tokenUri; - - @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.authorizationUri:}") - private String authorizationUri; - - @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientId:}") - private String clientId; - - @Value("${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientSecret:}") - private String clientSecret; - - private AuthenticationScheme authenticationScheme = AuthenticationScheme.header; - private Home home = new Home(); @Data @@ -64,31 +53,8 @@ public class OAuth2SsoProperties implements Validator { } public String getLogoutUri(String redirectUrl) { - return StringUtils.hasText(logoutUri) ? logoutUri : tokenUri.replace("/oauth/token", + return StringUtils.hasText(logoutUri) ? logoutUri : client.getTokenUri().replace("/oauth/token", "/logout.do?redirect=" + redirectUrl); } - @Override - public boolean supports(Class clazz) { - return OAuth2SsoProperties.class.isAssignableFrom(clazz); - } - - @Override - public void validate(Object target, Errors errors) { - OAuth2SsoProperties sso = (OAuth2SsoProperties) target; - if (StringUtils.hasText(sso.getClientId())) { - if (!StringUtils.hasText(sso.getAuthorizationUri())) { - errors.rejectValue("authorizeUri", "missing.authorizeUri", - "Missing authorizeUri"); - } - if (!StringUtils.hasText(sso.getTokenUri())) { - errors.rejectValue("tokenUri", "missing.tokenUri", "Missing tokenUri"); - } - if (!StringUtils.hasText(sso.getClientSecret())) { - errors.rejectValue("clientSecret", "missing.clientSecret", - "Missing clientSecret"); - } - } - } - }