Auto-configure Spring Security OAuth2 when detected on the classpath
* Automatically spin up Authorization Server and Resource Server * Automatically configures method level security included OAuth2Expression handler * Wrote extensive unit tests verifying default behavior as well as the auto-configuration backing off when custom Authorization/Resource servers are included * Created org.springframework.boot.security.oauth2 subpackage to contain it * Can also disable either resource of authorization server completely with a single property for each * Print out the auto-generated secrets and other settings * Added spring-boot-sample-secure-oauth2 to provide a sample that can be run and poked with curl as well as some automated tests. * Make users ask for which servers to install by adding @Enable* * User has to @EnableGlobalMethodSecurity instead of using properties files Add Spring Security OAuth2 support to Spring Boot CLI * Triggered from either @EnableAuthorizationServer or @EnableResourceServer * Needs to have @EnableGlobalMethodSecurity to allow picking the annotation model. * By default, comes with import support for @PreAuthorize, @PreFilter, @PostAuthorize, and @PostFilter via a single start import * Also need import support for the enable annotations mentioned above. * Added extra test case and sample (oauth2.groovy)pull/3040/merge
parent
dbc538d054
commit
53f67a448f
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationEntryPoint that sends a 401 and Parameterized by the value of the
|
||||||
|
* WWW-Authenticate header. Like the {@link BasicAuthenticationEntryPoint} but more
|
||||||
|
* flexible.
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Http401AuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
private final String authenticateHeader;
|
||||||
|
|
||||||
|
public Http401AuthenticationEntryPoint(String authenticateHeader) {
|
||||||
|
this.authenticateHeader = authenticateHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
AuthenticationException authException) throws IOException, ServletException {
|
||||||
|
response.setHeader("WWW-Authenticate", authenticateHeader);
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
|
||||||
|
authException.getMessage());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.autoconfigure.security.oauth2;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("spring.oauth2.client")
|
||||||
|
public class ClientCredentialsProperties {
|
||||||
|
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
private String clientSecret = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
private boolean defaultSecret = true;
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientSecret(String clientSecret) {
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
this.defaultSecret = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultSecret() {
|
||||||
|
return this.defaultSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.autoconfigure.security.oauth2;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.authserver.SpringSecurityOAuth2AuthorizationServerConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.client.SpringSecurityOAuth2ClientConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.SpringSecurityOAuth2ResourceServerConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Security OAuth2 top level auto-configuration beans
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass({ OAuth2AccessToken.class, WebMvcConfigurerAdapter.class })
|
||||||
|
@ConditionalOnWebApplication
|
||||||
|
@Import({ SpringSecurityOAuth2AuthorizationServerConfiguration.class,
|
||||||
|
SpringSecurityOAuth2MethodSecurityConfiguration.class,
|
||||||
|
SpringSecurityOAuth2ResourceServerConfiguration.class,
|
||||||
|
SpringSecurityOAuth2ClientConfiguration.class })
|
||||||
|
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
|
||||||
|
@EnableConfigurationProperties(ClientCredentialsProperties.class)
|
||||||
|
public class SpringSecurityOAuth2AutoConfiguration {
|
||||||
|
|
||||||
|
@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() {
|
||||||
|
// Before the authorization server (default 0)
|
||||||
|
return -10;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.autoconfigure.security.oauth2;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||||
|
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configure an expression handler for method-level security (if the user already has
|
||||||
|
* <code>@EnableGlobalMethodSecurity</code>).
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass({ OAuth2AccessToken.class })
|
||||||
|
@ConditionalOnBean(GlobalMethodSecurityConfiguration.class)
|
||||||
|
public class SpringSecurityOAuth2MethodSecurityConfiguration implements
|
||||||
|
BeanFactoryPostProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||||
|
throws BeansException {
|
||||||
|
beanFactory
|
||||||
|
.addBeanPostProcessor(new OAuth2ExpressionHandlerInjectionPostProcessor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OAuth2ExpressionHandlerInjectionPostProcessor implements
|
||||||
|
BeanPostProcessor {
|
||||||
|
|
||||||
|
@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 DefaultMethodSecurityExpressionHandler) {
|
||||||
|
return new OAuth2MethodSecurityExpressionHandler();
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.autoconfigure.security.oauth2.authserver;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.ClientCredentialsProperties;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
|
||||||
|
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||||
|
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configure a Spring Security OAuth2 authorization server. Back off if another
|
||||||
|
* {@link AuthorizationServerConfigurer} already exists or if authorization server is not
|
||||||
|
* enabled.
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass(EnableAuthorizationServer.class)
|
||||||
|
@ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
|
||||||
|
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
|
||||||
|
@EnableConfigurationProperties
|
||||||
|
public class SpringSecurityOAuth2AuthorizationServerConfiguration extends
|
||||||
|
AuthorizationServerConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BaseClientDetails details;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private TokenStore tokenStore;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnMissingBean(BaseClientDetails.class)
|
||||||
|
protected static class BaseClientDetailsConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientCredentialsProperties client;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConfigurationProperties("spring.oauth2.client")
|
||||||
|
public BaseClientDetails oauth2ClientDetails() {
|
||||||
|
BaseClientDetails details = new BaseClientDetails();
|
||||||
|
if (this.client.getClientId() == null) {
|
||||||
|
this.client.setClientId(UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
details.setClientId(this.client.getClientId());
|
||||||
|
details.setClientSecret(this.client.getClientSecret());
|
||||||
|
details.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
|
||||||
|
"password", "client_credentials", "implicit", "refresh_token"));
|
||||||
|
details.setAuthorities(AuthorityUtils
|
||||||
|
.commaSeparatedStringToAuthorityList("ROLE_USER"));
|
||||||
|
details.setRegisteredRedirectUri(Collections.<String> emptySet());
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
|
||||||
|
ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder builder = clients
|
||||||
|
.inMemory().withClient(this.details.getClientId());
|
||||||
|
builder.secret(this.details.getClientSecret())
|
||||||
|
.resourceIds(this.details.getResourceIds().toArray(new String[0]))
|
||||||
|
.authorizedGrantTypes(
|
||||||
|
this.details.getAuthorizedGrantTypes().toArray(new String[0]))
|
||||||
|
.authorities(
|
||||||
|
AuthorityUtils.authorityListToSet(this.details.getAuthorities())
|
||||||
|
.toArray(new String[0]))
|
||||||
|
.scopes(this.details.getScope().toArray(new String[0]));
|
||||||
|
if (this.details.getRegisteredRedirectUri() != null) {
|
||||||
|
builder.redirectUris(this.details.getRegisteredRedirectUri().toArray(
|
||||||
|
new String[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
|
||||||
|
throws Exception {
|
||||||
|
if (this.tokenStore != null) {
|
||||||
|
endpoints.tokenStore(this.tokenStore);
|
||||||
|
}
|
||||||
|
if (this.details.getAuthorizedGrantTypes().contains("password")) {
|
||||||
|
endpoints.authenticationManager(this.authenticationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
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.security.oauth2.ClientCredentialsProperties;
|
||||||
|
import org.springframework.boot.context.embedded.FilterRegistrationBean;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
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.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass(EnableOAuth2Client.class)
|
||||||
|
@ConditionalOnBean(OAuth2ClientConfiguration.class)
|
||||||
|
public class SpringSecurityOAuth2ClientConfiguration {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory
|
||||||
|
.getLog(SpringSecurityOAuth2ClientConfiguration.class);
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class ClientAuthenticationFilterConfiguration {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
@Qualifier("accessTokenRequest")
|
||||||
|
private AccessTokenRequest accessTokenRequest;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientCredentialsProperties credentials;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
String prefix = "spring.oauth2.client";
|
||||||
|
boolean defaultSecret = this.credentials.isDefaultSecret();
|
||||||
|
logger.info(String.format(
|
||||||
|
"Initialized OAuth2 Client\n\n%s.clientId = %s\n%s.secret = %s\n\n",
|
||||||
|
prefix, this.credentials.getClientId(), prefix,
|
||||||
|
defaultSecret ? this.credentials.getClientSecret() : "****"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConfigurationProperties("spring.oauth2.client")
|
||||||
|
@Primary
|
||||||
|
public AuthorizationCodeResourceDetails authorizationCodeResourceDetails() {
|
||||||
|
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
|
||||||
|
details.setClientSecret(this.credentials.getClientSecret());
|
||||||
|
details.setClientId(this.credentials.getClientId());
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean oauth2ClientFilterRegistration(
|
||||||
|
OAuth2ClientContextFilter filter) {
|
||||||
|
FilterRegistrationBean registration = new FilterRegistrationBean();
|
||||||
|
registration.setFilter(filter);
|
||||||
|
registration.setOrder(0);
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OAuth2RestOperations authorizationCodeRestTemplate(
|
||||||
|
AuthorizationCodeResourceDetails oauth2RemoteResource) {
|
||||||
|
OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2RemoteResource,
|
||||||
|
oauth2ClientContext());
|
||||||
|
template.setInterceptors(Arrays
|
||||||
|
.<ClientHttpRequestInterceptor> 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<String, String> 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(this.accessTokenRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
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.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("spring.oauth2.resource")
|
||||||
|
public class ResourceServerProperties implements Validator, BeanFactoryAware {
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final String clientId;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private final String clientSecret;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private ListableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
private String serviceId = "resource";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of the resource.
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI of the user endpoint.
|
||||||
|
*/
|
||||||
|
private String userInfoUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URI of the token decoding endpoint.
|
||||||
|
*/
|
||||||
|
private String tokenInfoUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the token info, can be set to false to use the user info.
|
||||||
|
*/
|
||||||
|
private boolean preferTokenInfo = true;
|
||||||
|
|
||||||
|
private Jwt jwt = new Jwt();
|
||||||
|
|
||||||
|
public ResourceServerProperties() {
|
||||||
|
this(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceServerProperties(String clientId, String clientSecret) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
this.clientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||||
|
this.beanFactory = (ListableBeanFactory) beanFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResourceId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServiceId() {
|
||||||
|
return this.serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceId(String serviceId) {
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserInfoUri() {
|
||||||
|
return this.userInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserInfoUri(String userInfoUri) {
|
||||||
|
this.userInfoUri = userInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTokenInfoUri() {
|
||||||
|
return this.tokenInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenInfoUri(String tokenInfoUri) {
|
||||||
|
this.tokenInfoUri = tokenInfoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPreferTokenInfo() {
|
||||||
|
return this.preferTokenInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreferTokenInfo(boolean preferTokenInfo) {
|
||||||
|
this.preferTokenInfo = preferTokenInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jwt getJwt() {
|
||||||
|
return this.jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwt(Jwt jwt) {
|
||||||
|
this.jwt = jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return this.clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientSecret() {
|
||||||
|
return this.clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return ResourceServerProperties.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory,
|
||||||
|
AuthorizationServerEndpointsConfiguration.class).length > 0) {
|
||||||
|
// If we are an authorization server we don't need remote resource token
|
||||||
|
// services
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ResourceServerProperties resource = (ResourceServerProperties) target;
|
||||||
|
if (StringUtils.hasText(this.clientId)) {
|
||||||
|
if (!StringUtils.hasText(this.clientSecret)) {
|
||||||
|
if (!StringUtils.hasText(resource.getUserInfoUri())) {
|
||||||
|
errors.rejectValue("userInfoUri", "missing.userInfoUri",
|
||||||
|
"Missing userInfoUri (no client secret available)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isPreferTokenInfo()
|
||||||
|
&& !StringUtils.hasText(resource.getTokenInfoUri())) {
|
||||||
|
if (StringUtils.hasText(getJwt().getKeyUri())
|
||||||
|
|| StringUtils.hasText(getJwt().getKeyValue())) {
|
||||||
|
// It's a JWT decoder
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(resource.getUserInfoUri())) {
|
||||||
|
errors.rejectValue("tokenInfoUri", "missing.tokenInfoUri",
|
||||||
|
"Missing tokenInfoUri and userInfoUri and there is no JWT verifier key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Jwt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The verification key of the JWT token. Can either be a symmetric secret or
|
||||||
|
* PEM-encoded RSA public key. If the value is not available, you can set the URI
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
|
private String keyValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URI of the JWT token. Can be set if the value is not available and the key
|
||||||
|
* is public.
|
||||||
|
*/
|
||||||
|
private String keyUri;
|
||||||
|
|
||||||
|
public String getKeyValue() {
|
||||||
|
return this.keyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyValue(String keyValue) {
|
||||||
|
this.keyValue = keyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyUri(String keyUri) {
|
||||||
|
this.keyUri = keyUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyUri() {
|
||||||
|
if (this.keyUri != null) {
|
||||||
|
return this.keyUri;
|
||||||
|
}
|
||||||
|
if (ResourceServerProperties.this.userInfoUri != null
|
||||||
|
&& ResourceServerProperties.this.userInfoUri.endsWith("/userinfo")) {
|
||||||
|
return ResourceServerProperties.this.userInfoUri.replace("/userinfo",
|
||||||
|
"/token_key");
|
||||||
|
}
|
||||||
|
if (ResourceServerProperties.this.tokenInfoUri != null
|
||||||
|
&& ResourceServerProperties.this.tokenInfoUri
|
||||||
|
.endsWith("/check_token")) {
|
||||||
|
return ResourceServerProperties.this.userInfoUri.replace("/check_token",
|
||||||
|
"/token_key");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,281 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.ClientCredentialsProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.client.SpringSecurityOAuth2ClientConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.client.SpringSecurityOAuth2ClientConfiguration.ClientAuthenticationFilterConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
||||||
|
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
|
||||||
|
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||||
|
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||||
|
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
|
||||||
|
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||||
|
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||||
|
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||||
|
import org.springframework.social.connect.ConnectionFactoryLocator;
|
||||||
|
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.client.ResourceAccessException;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
|
||||||
|
public class ResourceServerTokenServicesConfiguration {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory
|
||||||
|
.getLog(ResourceServerTokenServicesConfiguration.class);
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Conditional(NotJwtToken.class)
|
||||||
|
@EnableOAuth2Client
|
||||||
|
@Import(ClientAuthenticationFilterConfiguration.class)
|
||||||
|
protected static class RemoteTokenServicesConfiguration {
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import(SpringSecurityOAuth2ClientConfiguration.class)
|
||||||
|
@Conditional(TokenInfo.class)
|
||||||
|
protected static class TokenInfoServicesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceServerProperties resource;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthorizationCodeResourceDetails client;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ResourceServerTokenServices remoteTokenServices() {
|
||||||
|
RemoteTokenServices services = new RemoteTokenServices();
|
||||||
|
services.setCheckTokenEndpointUrl(this.resource.getTokenInfoUri());
|
||||||
|
services.setClientId(this.client.getClientId());
|
||||||
|
services.setClientSecret(this.client.getClientSecret());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnClass(OAuth2ConnectionFactory.class)
|
||||||
|
@Conditional(NotTokenInfo.class)
|
||||||
|
protected static class SocialTokenServicesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceServerProperties sso;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientCredentialsProperties client;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private OAuth2ConnectionFactory<?> connectionFactory;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private Map<String, OAuth2RestOperations> resources = Collections.emptyMap();
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnBean(ConnectionFactoryLocator.class)
|
||||||
|
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||||
|
public SpringSocialTokenServices socialTokenServices() {
|
||||||
|
return new SpringSocialTokenServices(this.connectionFactory,
|
||||||
|
this.client.getClientId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean({ ConnectionFactoryLocator.class,
|
||||||
|
ResourceServerTokenServices.class })
|
||||||
|
public ResourceServerTokenServices userInfoTokenServices() {
|
||||||
|
UserInfoTokenServices services = new UserInfoTokenServices(
|
||||||
|
this.sso.getUserInfoUri(), this.client.getClientId());
|
||||||
|
services.setResources(this.resources);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnMissingClass(name = "org.springframework.social.connect.support.OAuth2ConnectionFactory")
|
||||||
|
@Conditional(NotTokenInfo.class)
|
||||||
|
protected static class UserInfoTokenServicesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceServerProperties sso;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientCredentialsProperties client;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private Map<String, OAuth2RestOperations> resources = Collections.emptyMap();
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||||
|
public ResourceServerTokenServices userInfoTokenServices() {
|
||||||
|
UserInfoTokenServices services = new UserInfoTokenServices(
|
||||||
|
this.sso.getUserInfoUri(), this.client.getClientId());
|
||||||
|
services.setResources(this.resources);
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Conditional(JwtToken.class)
|
||||||
|
protected static class JwtTokenServicesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceServerProperties resource;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||||
|
public ResourceServerTokenServices jwtTokenServices() {
|
||||||
|
DefaultTokenServices services = new DefaultTokenServices();
|
||||||
|
services.setTokenStore(jwtTokenStore());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenStore jwtTokenStore() {
|
||||||
|
return new JwtTokenStore(jwtTokenEnhancer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtAccessTokenConverter jwtTokenEnhancer() {
|
||||||
|
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||||
|
String keyValue = this.resource.getJwt().getKeyValue();
|
||||||
|
if (!StringUtils.hasText(keyValue)) {
|
||||||
|
try {
|
||||||
|
keyValue = (String) new RestTemplate().getForObject(
|
||||||
|
this.resource.getJwt().getKeyUri(), Map.class).get("value");
|
||||||
|
}
|
||||||
|
catch (ResourceAccessException e) {
|
||||||
|
// ignore
|
||||||
|
logger.warn("Failed to fetch token key (you may need to refresh when the auth server is back)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (StringUtils.hasText(keyValue) && !keyValue.startsWith("-----BEGIN")) {
|
||||||
|
converter.setSigningKey(keyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyValue != null) {
|
||||||
|
converter.setVerifierKey(keyValue);
|
||||||
|
}
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TokenInfo extends SpringBootCondition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||||
|
AnnotatedTypeMetadata metadata) {
|
||||||
|
Environment environment = context.getEnvironment();
|
||||||
|
boolean preferTokenInfo = environment
|
||||||
|
.resolvePlaceholders(
|
||||||
|
"${spring.oauth2.resource.preferTokenInfo:${OAUTH2_RESOURCE_PREFERTOKENINFO:true}}")
|
||||||
|
.equals("true");
|
||||||
|
boolean hasTokenInfo = !environment.resolvePlaceholders(
|
||||||
|
"${spring.oauth2.resource.tokenInfoUri:}").equals("");
|
||||||
|
boolean hasUserInfo = !environment.resolvePlaceholders(
|
||||||
|
"${spring.oauth2.resource.userInfoUri:}").equals("");
|
||||||
|
if (!hasUserInfo) {
|
||||||
|
return ConditionOutcome.match("No user info provided");
|
||||||
|
}
|
||||||
|
if (hasTokenInfo) {
|
||||||
|
if (preferTokenInfo) {
|
||||||
|
return ConditionOutcome
|
||||||
|
.match("Token info endpoint is preferred and user info provided");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ConditionOutcome.noMatch("Token info endpoint is not provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class JwtToken extends SpringBootCondition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||||
|
AnnotatedTypeMetadata metadata) {
|
||||||
|
if (StringUtils.hasText(context.getEnvironment().getProperty(
|
||||||
|
"spring.oauth2.resource.jwt.keyValue"))
|
||||||
|
|| StringUtils.hasText(context.getEnvironment().getProperty(
|
||||||
|
"spring.oauth2.resource.jwt.keyUri"))) {
|
||||||
|
return ConditionOutcome.match("public key is provided");
|
||||||
|
}
|
||||||
|
return ConditionOutcome.noMatch("public key is not provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NotTokenInfo extends SpringBootCondition {
|
||||||
|
|
||||||
|
private TokenInfo opposite = new TokenInfo();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||||
|
AnnotatedTypeMetadata metadata) {
|
||||||
|
ConditionOutcome outcome = this.opposite.getMatchOutcome(context, metadata);
|
||||||
|
if (outcome.isMatch()) {
|
||||||
|
return ConditionOutcome.noMatch(outcome.getMessage());
|
||||||
|
}
|
||||||
|
return ConditionOutcome.match(outcome.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NotJwtToken extends SpringBootCondition {
|
||||||
|
|
||||||
|
private JwtToken opposite = new JwtToken();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||||
|
AnnotatedTypeMetadata metadata) {
|
||||||
|
ConditionOutcome outcome = this.opposite.getMatchOutcome(context, metadata);
|
||||||
|
if (outcome.isMatch()) {
|
||||||
|
return ConditionOutcome.noMatch(outcome.getMessage());
|
||||||
|
}
|
||||||
|
return ConditionOutcome.match(outcome.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.OnBeanCondition;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||||
|
import org.springframework.boot.autoconfigure.security.SecurityProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.ClientCredentialsProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.SpringSecurityOAuth2ResourceServerConfiguration.ResourceServerCondition;
|
||||||
|
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.ConfigurationCondition;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
|
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||||
|
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.util.ClassUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configure a Spring Security OAuth2 resource server. Back off if another
|
||||||
|
* {@link ResourceServerConfigurer} already exists or if resource server not enabled.
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Conditional(ResourceServerCondition.class)
|
||||||
|
@ConditionalOnClass({ EnableResourceServer.class, SecurityProperties.class })
|
||||||
|
@ConditionalOnWebApplication
|
||||||
|
@ConditionalOnBean(ResourceServerConfiguration.class)
|
||||||
|
@Import(ResourceServerTokenServicesConfiguration.class)
|
||||||
|
public class SpringSecurityOAuth2ResourceServerConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceServerProperties resource;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(ResourceServerConfigurer.class)
|
||||||
|
public ResourceServerConfigurer resourceServer() {
|
||||||
|
return new ResourceSecurityConfigurer(this.resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
protected static class ResourceServerPropertiesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientCredentialsProperties credentials;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ResourceServerProperties resourceServerProperties() {
|
||||||
|
return new ResourceServerProperties(this.credentials.getClientId(),
|
||||||
|
this.credentials.getClientSecret());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class ResourceSecurityConfigurer extends
|
||||||
|
ResourceServerConfigurerAdapter {
|
||||||
|
|
||||||
|
private ResourceServerProperties resource;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ResourceSecurityConfigurer(ResourceServerProperties resource) {
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(ResourceServerSecurityConfigurer resources)
|
||||||
|
throws Exception {
|
||||||
|
resources.resourceId(this.resource.getResourceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(HttpSecurity http) throws Exception {
|
||||||
|
http.authorizeRequests().anyRequest().authenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
|
||||||
|
protected static class ResourceServerCondition extends SpringBootCondition implements
|
||||||
|
ConfigurationCondition {
|
||||||
|
|
||||||
|
private OnBeanCondition condition = new OnBeanCondition();
|
||||||
|
|
||||||
|
private StandardAnnotationMetadata beanMetaData = new StandardAnnotationMetadata(
|
||||||
|
ResourceServerCondition.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigurationPhase getConfigurationPhase() {
|
||||||
|
return ConfigurationPhase.REGISTER_BEAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||||
|
AnnotatedTypeMetadata metadata) {
|
||||||
|
Environment environment = context.getEnvironment();
|
||||||
|
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment);
|
||||||
|
String client = environment
|
||||||
|
.resolvePlaceholders("${spring.oauth2.client.clientId:}");
|
||||||
|
if (StringUtils.hasText(client)) {
|
||||||
|
return ConditionOutcome.match("found client id");
|
||||||
|
}
|
||||||
|
if (!resolver.getSubProperties("spring.oauth2.resource.jwt").isEmpty()) {
|
||||||
|
return ConditionOutcome.match("found JWT resource configuration");
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(resolver
|
||||||
|
.getProperty("spring.oauth2.resource.userInfoUri"))) {
|
||||||
|
return ConditionOutcome
|
||||||
|
.match("found UserInfo URI resource configuration");
|
||||||
|
}
|
||||||
|
if (ClassUtils
|
||||||
|
.isPresent(
|
||||||
|
"org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration",
|
||||||
|
null)) {
|
||||||
|
if (this.condition.matches(context, this.beanMetaData)) {
|
||||||
|
return ConditionOutcome
|
||||||
|
.match("found authorization server configuration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ConditionOutcome
|
||||||
|
.noMatch("found neither client id nor JWT resource nor authorization server");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
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.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.social.connect.Connection;
|
||||||
|
import org.springframework.social.connect.UserProfile;
|
||||||
|
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
|
||||||
|
import org.springframework.social.oauth2.AccessGrant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SpringSocialTokenServices implements ResourceServerTokenServices {
|
||||||
|
|
||||||
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
private OAuth2ConnectionFactory<?> connectionFactory;
|
||||||
|
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
public SpringSocialTokenServices(OAuth2ConnectionFactory<?> connectionFactory,
|
||||||
|
String clientId) {
|
||||||
|
this.connectionFactory = connectionFactory;
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuth2Authentication loadAuthentication(String accessToken)
|
||||||
|
throws AuthenticationException, InvalidTokenException {
|
||||||
|
|
||||||
|
Connection<?> connection = connectionFactory.createConnection(new AccessGrant(
|
||||||
|
accessToken));
|
||||||
|
UserProfile user = connection.fetchUserProfile();
|
||||||
|
return extractAuthentication(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2Authentication extractAuthentication(UserProfile user) {
|
||||||
|
UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(
|
||||||
|
user.getUsername(), "N/A",
|
||||||
|
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
|
||||||
|
principal.setDetails(user);
|
||||||
|
OAuth2Request request = new OAuth2Request(null, clientId, null, true, null, null,
|
||||||
|
null, null, null);
|
||||||
|
return new OAuth2Authentication(request, principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuth2AccessToken readAccessToken(String accessToken) {
|
||||||
|
throw new UnsupportedOperationException("Not supported: read access token");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
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.OAuth2RestOperations;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class UserInfoTokenServices implements ResourceServerTokenServices {
|
||||||
|
|
||||||
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
private String userInfoEndpointUrl;
|
||||||
|
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
private Collection<OAuth2RestOperations> resources = Collections.emptySet();
|
||||||
|
|
||||||
|
public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
|
||||||
|
this.userInfoEndpointUrl = userInfoEndpointUrl;
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResources(Map<String, OAuth2RestOperations> resources) {
|
||||||
|
this.resources = new ArrayList<OAuth2RestOperations>();
|
||||||
|
for (Entry<String, OAuth2RestOperations> key : resources.entrySet()) {
|
||||||
|
OAuth2RestOperations value = key.getValue();
|
||||||
|
String clientIdForTemplate = value.getResource().getClientId();
|
||||||
|
if (clientIdForTemplate!=null && clientIdForTemplate.equals(clientId)) {
|
||||||
|
this.resources.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuth2Authentication loadAuthentication(String accessToken)
|
||||||
|
throws AuthenticationException, InvalidTokenException {
|
||||||
|
|
||||||
|
Map<String, Object> map = getMap(userInfoEndpointUrl, accessToken);
|
||||||
|
|
||||||
|
if (map.containsKey("error")) {
|
||||||
|
logger.debug("userinfo returned error: " + map.get("error"));
|
||||||
|
throw new InvalidTokenException(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extractAuthentication(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
|
||||||
|
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(
|
||||||
|
getPrincipal(map), "N/A",
|
||||||
|
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
|
||||||
|
user.setDetails(map);
|
||||||
|
OAuth2Request request = new OAuth2Request(null, clientId, null, true, null, null,
|
||||||
|
null, null, null);
|
||||||
|
return new OAuth2Authentication(request, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getPrincipal(Map<String, Object> map) {
|
||||||
|
String[] keys = new String[] { "user", "username", "userid", "user_id", "login",
|
||||||
|
"id" };
|
||||||
|
for (String key : keys) {
|
||||||
|
if (map.containsKey(key)) {
|
||||||
|
return map.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuth2AccessToken readAccessToken(String accessToken) {
|
||||||
|
throw new UnsupportedOperationException("Not supported: read access token");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> getMap(String path, String accessToken) {
|
||||||
|
logger.info("Getting user info from: " + path);
|
||||||
|
OAuth2RestOperations restTemplate = null;
|
||||||
|
for (OAuth2RestOperations candidate : resources) {
|
||||||
|
try {
|
||||||
|
if (accessToken.equals(candidate.getAccessToken().getValue())) {
|
||||||
|
restTemplate = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (restTemplate == null) {
|
||||||
|
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
|
||||||
|
resource.setClientId(clientId);
|
||||||
|
restTemplate = new OAuth2RestTemplate(resource);
|
||||||
|
restTemplate.getOAuth2ClientContext().setAccessToken(
|
||||||
|
new DefaultOAuth2AccessToken(accessToken));
|
||||||
|
}
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map map = restTemplate.getForEntity(path, Map.class).getBody();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> result = map;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,558 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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.boot.autoconfigure.security.oauth2;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.oauth2.authserver.SpringSecurityOAuth2AuthorizationServerConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.SpringSecurityOAuth2ResourceServerConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
|
||||||
|
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
|
||||||
|
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||||
|
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||||
|
import org.springframework.boot.test.TestRestTemplate;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
|
||||||
|
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
|
||||||
|
import org.springframework.security.access.method.MethodSecurityMetadataSource;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
|
||||||
|
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||||
|
import org.springframework.security.oauth2.provider.ClientDetailsService;
|
||||||
|
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
|
||||||
|
import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler;
|
||||||
|
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
|
||||||
|
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
|
||||||
|
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||||
|
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
|
||||||
|
import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
|
||||||
|
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||||
|
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||||
|
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify Spring Security OAuth2 auto-configuration secures end points properly, accepts
|
||||||
|
* environmental overrides, and also backs off in the presence of other
|
||||||
|
* resource/authorization components.
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
public class SpringSecurityOAuth2AutoConfigurationTests {
|
||||||
|
|
||||||
|
private AnnotationConfigEmbeddedWebApplicationContext context;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultConfiguration() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
this.context.getBean(SpringSecurityOAuth2AuthorizationServerConfiguration.class);
|
||||||
|
this.context.getBean(SpringSecurityOAuth2ResourceServerConfiguration.class);
|
||||||
|
this.context.getBean(SpringSecurityOAuth2MethodSecurityConfiguration.class);
|
||||||
|
|
||||||
|
ClientDetails config = this.context.getBean(BaseClientDetails.class);
|
||||||
|
AuthorizationEndpoint endpoint = this.context
|
||||||
|
.getBean(AuthorizationEndpoint.class);
|
||||||
|
UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils.getField(
|
||||||
|
endpoint, "userApprovalHandler");
|
||||||
|
ClientDetailsService clientDetailsService = this.context
|
||||||
|
.getBean(ClientDetailsService.class);
|
||||||
|
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(config
|
||||||
|
.getClientId());
|
||||||
|
|
||||||
|
assertThat(AopUtils.isJdkDynamicProxy(clientDetailsService), is(true));
|
||||||
|
assertThat(AopUtils.getTargetClass(clientDetailsService).getName(),
|
||||||
|
is(ClientDetailsService.class.getName()));
|
||||||
|
|
||||||
|
assertThat(handler instanceof ApprovalStoreUserApprovalHandler, is(true));
|
||||||
|
|
||||||
|
assertThat(clientDetails, equalTo(config));
|
||||||
|
|
||||||
|
verifyAuthentication(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEnvironmentalOverrides() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.context,
|
||||||
|
"spring.oauth2.client.clientId:myclientid",
|
||||||
|
"spring.oauth2.client.clientSecret:mysecret");
|
||||||
|
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||||
|
|
||||||
|
assertThat(config.getClientId(), is("myclientid"));
|
||||||
|
assertThat(config.getClientSecret(), is("mysecret"));
|
||||||
|
|
||||||
|
verifyAuthentication(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisablingResourceServer() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(AuthorizationServerConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2ResourceServerConfiguration.class).length,
|
||||||
|
is(0));
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2AuthorizationServerConfiguration.class).length,
|
||||||
|
is(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDisablingAuthorizationServer() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(ResourceServerConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.context,
|
||||||
|
"spring.oauth2.resource.jwt.keyValue:DEADBEEF");
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2ResourceServerConfiguration.class).length,
|
||||||
|
is(1));
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2AuthorizationServerConfiguration.class).length,
|
||||||
|
is(0));
|
||||||
|
|
||||||
|
assertThat(this.context.getBeanNamesForType(UserApprovalHandler.class).length,
|
||||||
|
is(0));
|
||||||
|
assertThat(this.context.getBeanNamesForType(DefaultTokenServices.class).length,
|
||||||
|
is(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResourceServerOverride() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||||
|
CustomResourceServer.class, MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2AuthorizationServerConfiguration.class).length,
|
||||||
|
is(1));
|
||||||
|
|
||||||
|
assertThat(this.context.getBeanNamesForType(CustomResourceServer.class).length,
|
||||||
|
is(1));
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2ResourceServerConfiguration.class).length,
|
||||||
|
is(1));
|
||||||
|
|
||||||
|
verifyAuthentication(config);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthorizationServerOverride() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.context,
|
||||||
|
"spring.oauth2.resourceId:resource-id");
|
||||||
|
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||||
|
CustomAuthorizationServer.class, MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
BaseClientDetails config = new BaseClientDetails();
|
||||||
|
config.setClientId("client");
|
||||||
|
config.setClientSecret("secret");
|
||||||
|
config.setResourceIds(Arrays.asList("resource-id"));
|
||||||
|
config.setAuthorizedGrantTypes(Arrays.asList("password"));
|
||||||
|
config.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
|
||||||
|
config.setScope(Arrays.asList("read"));
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2AuthorizationServerConfiguration.class).length,
|
||||||
|
is(0));
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
this.context
|
||||||
|
.getBeanNamesForType(SpringSecurityOAuth2ResourceServerConfiguration.class).length,
|
||||||
|
is(1));
|
||||||
|
|
||||||
|
verifyAuthentication(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultPrePostSecurityAnnotations() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
this.context.getBean(SpringSecurityOAuth2MethodSecurityConfiguration.class);
|
||||||
|
|
||||||
|
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||||
|
|
||||||
|
DelegatingMethodSecurityMetadataSource source = this.context
|
||||||
|
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||||
|
List<MethodSecurityMetadataSource> sources = source
|
||||||
|
.getMethodSecurityMetadataSources();
|
||||||
|
|
||||||
|
assertThat(sources.size(), is(1));
|
||||||
|
assertThat(sources.get(0).getClass().getName(),
|
||||||
|
is(PrePostAnnotationSecurityMetadataSource.class.getName()));
|
||||||
|
|
||||||
|
verifyAuthentication(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassicSecurityAnnotationOverride() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(SecuredEnabledConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
this.context.getBean(SpringSecurityOAuth2MethodSecurityConfiguration.class);
|
||||||
|
|
||||||
|
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||||
|
|
||||||
|
DelegatingMethodSecurityMetadataSource source = this.context
|
||||||
|
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||||
|
List<MethodSecurityMetadataSource> sources = source
|
||||||
|
.getMethodSecurityMetadataSources();
|
||||||
|
|
||||||
|
assertThat(sources.size(), is(1));
|
||||||
|
assertThat(sources.get(0).getClass().getName(),
|
||||||
|
is(SecuredAnnotationSecurityMetadataSource.class.getName()));
|
||||||
|
|
||||||
|
verifyAuthentication(config, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJsr250SecurityAnnotationOverride() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(Jsr250EnabledConfiguration.class,
|
||||||
|
MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
this.context.getBean(SpringSecurityOAuth2MethodSecurityConfiguration.class);
|
||||||
|
|
||||||
|
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||||
|
|
||||||
|
DelegatingMethodSecurityMetadataSource source = this.context
|
||||||
|
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||||
|
List<MethodSecurityMetadataSource> sources = source
|
||||||
|
.getMethodSecurityMetadataSources();
|
||||||
|
|
||||||
|
assertThat(sources.size(), is(1));
|
||||||
|
assertThat(sources.get(0).getClass().getName(),
|
||||||
|
is(Jsr250MethodSecurityMetadataSource.class.getName()));
|
||||||
|
|
||||||
|
verifyAuthentication(config, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodSecurityBackingOff() {
|
||||||
|
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
|
||||||
|
this.context.register(CustomMethodSecurity.class,
|
||||||
|
TestSecurityConfiguration.class, MinimalSecureWebApplication.class);
|
||||||
|
this.context.refresh();
|
||||||
|
|
||||||
|
DelegatingMethodSecurityMetadataSource source = this.context
|
||||||
|
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||||
|
List<MethodSecurityMetadataSource> sources = source
|
||||||
|
.getMethodSecurityMetadataSources();
|
||||||
|
assertThat(sources.size(), is(1));
|
||||||
|
assertThat(sources.get(0).getClass().getName(),
|
||||||
|
is(PrePostAnnotationSecurityMetadataSource.class.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the oauth service, get a token, and then attempt some operations using
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
*/
|
||||||
|
private void verifyAuthentication(ClientDetails config) {
|
||||||
|
verifyAuthentication(config, HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyAuthentication(ClientDetails config, HttpStatus finalStatus) {
|
||||||
|
String baseUrl = "http://localhost:"
|
||||||
|
+ this.context.getEmbeddedServletContainer().getPort();
|
||||||
|
|
||||||
|
RestTemplate rest = new TestRestTemplate();
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
|
||||||
|
// First, verify the web endpoint can't be reached
|
||||||
|
ResponseEntity<String> entity = rest.exchange(new RequestEntity<Void>(headers,
|
||||||
|
HttpMethod.GET, URI.create(baseUrl + "/secured")), String.class);
|
||||||
|
assertThat(entity.getStatusCode(), is(HttpStatus.UNAUTHORIZED));
|
||||||
|
|
||||||
|
// Since we can't reach it, need to collect an authorization token
|
||||||
|
String base64Creds = new String(
|
||||||
|
Base64.encode((config.getClientId() + ":" + config.getClientSecret())
|
||||||
|
.getBytes()));
|
||||||
|
headers.set("Authorization", "Basic " + base64Creds);
|
||||||
|
|
||||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
|
||||||
|
body.set("grant_type", "password");
|
||||||
|
body.set("username", "foo");
|
||||||
|
body.set("password", "bar");
|
||||||
|
body.set("scope", "read");
|
||||||
|
|
||||||
|
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(
|
||||||
|
body, headers);
|
||||||
|
|
||||||
|
JsonNode response = rest.postForObject(baseUrl + "/oauth/token", request,
|
||||||
|
JsonNode.class);
|
||||||
|
String authorizationToken = response.findValue("access_token").asText();
|
||||||
|
String tokenType = response.findValue("token_type").asText();
|
||||||
|
String scope = response.findValues("scope").get(0).toString();
|
||||||
|
assertThat(tokenType, is("bearer"));
|
||||||
|
assertThat(scope, is("\"read\""));
|
||||||
|
|
||||||
|
// Now we should be able to see that endpoint.
|
||||||
|
headers.set("Authorization", "BEARER " + authorizationToken);
|
||||||
|
|
||||||
|
ResponseEntity<String> securedResponse = rest.exchange(new RequestEntity<Void>(
|
||||||
|
headers, HttpMethod.GET, URI.create(baseUrl + "/securedFind")),
|
||||||
|
String.class);
|
||||||
|
assertThat(securedResponse.getStatusCode(), is(HttpStatus.OK));
|
||||||
|
assertThat(securedResponse.getBody(),
|
||||||
|
is("You reached an endpoint secured by Spring Security OAuth2"));
|
||||||
|
|
||||||
|
entity = rest.exchange(
|
||||||
|
new RequestEntity<Void>(headers, HttpMethod.POST, URI.create(baseUrl
|
||||||
|
+ "/securedSave")), String.class);
|
||||||
|
assertThat(entity.getStatusCode(), is(finalStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import({ UseFreePortEmbeddedContainerConfiguration.class,
|
||||||
|
SecurityAutoConfiguration.class, ServerPropertiesAutoConfiguration.class,
|
||||||
|
DispatcherServletAutoConfiguration.class,
|
||||||
|
SpringSecurityOAuth2AutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||||
|
HttpMessageConvertersAutoConfiguration.class })
|
||||||
|
protected static class MinimalSecureWebApplication {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
protected static class TestSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
|
return super.authenticationManagerBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
TestWebApp testWebApp() {
|
||||||
|
return new TestWebApp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
@EnableResourceServer
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
protected static class AuthorizationAndResourceServerConfiguration extends
|
||||||
|
TestSecurityConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
@EnableResourceServer
|
||||||
|
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||||
|
protected static class SecuredEnabledConfiguration extends TestSecurityConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
@EnableResourceServer
|
||||||
|
@EnableGlobalMethodSecurity(jsr250Enabled = true)
|
||||||
|
protected static class Jsr250EnabledConfiguration extends TestSecurityConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
protected static class AuthorizationServerConfiguration extends
|
||||||
|
TestSecurityConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableResourceServer
|
||||||
|
protected static class ResourceServerConfiguration extends TestSecurityConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
protected static class TestWebApp {
|
||||||
|
|
||||||
|
@RequestMapping(value = "/securedFind", method = RequestMethod.GET)
|
||||||
|
@PreAuthorize("#oauth2.hasScope('read')")
|
||||||
|
public String secureFind() {
|
||||||
|
return "You reached an endpoint secured by Spring Security OAuth2";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/securedSave", method = RequestMethod.POST)
|
||||||
|
@PreAuthorize("#oauth2.hasScope('write')")
|
||||||
|
public String secureSave() {
|
||||||
|
return "You reached an endpoint secured by Spring Security OAuth2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
protected static class UseFreePortEmbeddedContainerConfiguration {
|
||||||
|
@Bean
|
||||||
|
TomcatEmbeddedServletContainerFactory containerFactory() {
|
||||||
|
return new TomcatEmbeddedServletContainerFactory(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableResourceServer
|
||||||
|
protected static class CustomResourceServer extends ResourceServerConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ResourceServerProperties config;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(ResourceServerSecurityConfigurer resources)
|
||||||
|
throws Exception {
|
||||||
|
if (this.config.getId() != null) {
|
||||||
|
resources.resourceId(this.config.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(HttpSecurity http) throws Exception {
|
||||||
|
http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and()
|
||||||
|
.csrf().disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
protected static class CustomAuthorizationServer extends
|
||||||
|
AuthorizationServerConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TokenStore tokenStore() {
|
||||||
|
return new InMemoryTokenStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApprovalStore approvalStore(final TokenStore tokenStore) {
|
||||||
|
TokenApprovalStore approvalStore = new TokenApprovalStore();
|
||||||
|
approvalStore.setTokenStore(tokenStore);
|
||||||
|
return approvalStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
|
||||||
|
clients.inMemory().withClient("client").secret("secret")
|
||||||
|
.resourceIds("resource-id").authorizedGrantTypes("password")
|
||||||
|
.authorities("USER").scopes("read")
|
||||||
|
.redirectUris("http://localhost:8080");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
|
||||||
|
throws Exception {
|
||||||
|
endpoints.tokenStore(tokenStore()).authenticationManager(
|
||||||
|
this.authenticationManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
protected static class CustomMethodSecurity extends GlobalMethodSecurityConfiguration {
|
||||||
|
@Override
|
||||||
|
protected MethodSecurityExpressionHandler createExpressionHandler() {
|
||||||
|
return new OAuth2MethodSecurityExpressionHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ResourceServerPropertiesTests {
|
||||||
|
|
||||||
|
private ResourceServerProperties properties = new ResourceServerProperties("client", "secret");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void json() throws Exception {
|
||||||
|
properties.getJwt().setKeyUri("http://example.com/token_key");
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
String json = mapper.writeValueAsString(properties);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> value = mapper.readValue(json, Map.class);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> jwt = (Map<String, Object>) value.get("jwt");
|
||||||
|
assertNotNull("Wrong json: " + json, jwt.get("keyUri"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tokenKeyDerived() throws Exception {
|
||||||
|
properties.setUserInfoUri("http://example.com/userinfo");
|
||||||
|
assertNotNull("Wrong properties: " + properties, properties.getJwt().getKeyUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.ClientCredentialsProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
|
import org.springframework.core.env.StandardEnvironment;
|
||||||
|
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||||
|
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||||
|
import org.springframework.social.connect.ConnectionFactoryLocator;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class ResourceServerTokenServicesConfigurationTests {
|
||||||
|
|
||||||
|
private ConfigurableApplicationContext context;
|
||||||
|
|
||||||
|
private ConfigurableEnvironment environment = new StandardEnvironment();
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
if (this.context != null) {
|
||||||
|
this.context.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultIsRemoteTokenServices() {
|
||||||
|
this.context = new SpringApplicationBuilder(ResourceConfiguration.class).web(
|
||||||
|
false).run();
|
||||||
|
RemoteTokenServices services = this.context.getBean(RemoteTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useRemoteTokenServices() {
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.environment,
|
||||||
|
"spring.oauth2.resource.tokenInfoUri:http://example.com",
|
||||||
|
"spring.oauth2.resource.clientId=acme");
|
||||||
|
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||||
|
.environment(this.environment).web(false).run();
|
||||||
|
RemoteTokenServices services = this.context.getBean(RemoteTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void switchToUserInfo() {
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.environment,
|
||||||
|
"spring.oauth2.resource.userInfoUri:http://example.com");
|
||||||
|
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||||
|
.environment(this.environment).web(false).run();
|
||||||
|
UserInfoTokenServices services = this.context
|
||||||
|
.getBean(UserInfoTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void preferUserInfo() {
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.environment,
|
||||||
|
"spring.oauth2.resource.userInfoUri:http://example.com",
|
||||||
|
"spring.oauth2.resource.tokenInfoUri:http://example.com",
|
||||||
|
"spring.oauth2.resource.preferTokenInfo:false");
|
||||||
|
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||||
|
.environment(this.environment).web(false).run();
|
||||||
|
UserInfoTokenServices services = this.context
|
||||||
|
.getBean(UserInfoTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void switchToJwt() {
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.environment,
|
||||||
|
"spring.oauth2.resource.jwt.keyValue=FOOBAR");
|
||||||
|
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||||
|
.environment(this.environment).web(false).run();
|
||||||
|
DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void asymmetricJwt() {
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.environment,
|
||||||
|
"spring.oauth2.resource.jwt.keyValue=" + publicKey);
|
||||||
|
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||||
|
.environment(this.environment).web(false).run();
|
||||||
|
DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void springSocialUserInfo() {
|
||||||
|
EnvironmentTestUtils.addEnvironment(this.environment,
|
||||||
|
"spring.oauth2.resource.userInfoUri:http://example.com",
|
||||||
|
"spring.social.facebook.app-id=foo",
|
||||||
|
"spring.social.facebook.app-secret=bar");
|
||||||
|
this.context = new SpringApplicationBuilder(SocialResourceConfiguration.class)
|
||||||
|
.environment(this.environment).web(true).run();
|
||||||
|
ConnectionFactoryLocator connectionFactory = this.context
|
||||||
|
.getBean(ConnectionFactoryLocator.class);
|
||||||
|
assertNotNull(connectionFactory);
|
||||||
|
SpringSocialTokenServices services = this.context
|
||||||
|
.getBean(SpringSocialTokenServices.class);
|
||||||
|
assertNotNull(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import({ ResourceServerTokenServicesConfiguration.class,
|
||||||
|
ResourceServerPropertiesConfiguration.class,
|
||||||
|
PropertyPlaceholderAutoConfiguration.class })
|
||||||
|
@EnableConfigurationProperties(ClientCredentialsProperties.class)
|
||||||
|
protected static class ResourceConfiguration {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
protected static class ResourceServerPropertiesConfiguration {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientCredentialsProperties credentials;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ResourceServerProperties resourceServerProperties() {
|
||||||
|
return new ResourceServerProperties(this.credentials.getClientId(),
|
||||||
|
this.credentials.getClientSecret());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Import({ FacebookAutoConfiguration.class, SocialWebAutoConfiguration.class })
|
||||||
|
protected static class SocialResourceConfiguration extends ResourceConfiguration {
|
||||||
|
@Bean
|
||||||
|
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
|
||||||
|
return Mockito.mock(EmbeddedServletContainerFactory.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String publicKey = "-----BEGIN PUBLIC KEY-----\n"
|
||||||
|
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzEtgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+TNu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB\n"
|
||||||
|
+ "-----END PUBLIC KEY-----";
|
||||||
|
|
||||||
|
}
|
@ -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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||||
|
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
||||||
|
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
|
||||||
|
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class UserInfoTokenServicesTests {
|
||||||
|
|
||||||
|
private UserInfoTokenServices services = new UserInfoTokenServices(
|
||||||
|
"http://example.com", "foo");
|
||||||
|
private BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
|
||||||
|
private OAuth2RestOperations template = Mockito.mock(OAuth2RestOperations.class);
|
||||||
|
private Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
public void init() {
|
||||||
|
resource.setClientId("foo");
|
||||||
|
Mockito.when(
|
||||||
|
template.getForEntity(Mockito.any(String.class), Mockito.any(Class.class)))
|
||||||
|
.thenReturn(new ResponseEntity<Map>(map, HttpStatus.OK));
|
||||||
|
Mockito.when(template.getAccessToken()).thenReturn(new DefaultOAuth2AccessToken("FOO"));
|
||||||
|
Mockito.when(template.getResource()).thenReturn(resource);
|
||||||
|
Mockito.when(template.getOAuth2ClientContext()).thenReturn(
|
||||||
|
Mockito.mock(OAuth2ClientContext.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sunnyDay() {
|
||||||
|
services.setResources(Collections.singletonMap("foo", template));
|
||||||
|
assertEquals("unknown", services.loadAuthentication("FOO").getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void userId() {
|
||||||
|
map.put("userid", "spencer");
|
||||||
|
services.setResources(Collections.singletonMap("foo", template));
|
||||||
|
assertEquals("spencer", services.loadAuthentication("FOO").getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package org.test
|
||||||
|
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
@EnableResourceServer
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
@RestController
|
||||||
|
class SampleController {
|
||||||
|
|
||||||
|
@PreAuthorize("#oauth2.hasScope('read')")
|
||||||
|
@RequestMapping("/")
|
||||||
|
def hello() {
|
||||||
|
[message: "Hello World!"]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
<<<<<<< HEAD
|
||||||
|
* Copyright 2012-2014 the original author or authors.
|
||||||
|
=======
|
||||||
|
* Copyright 2012-2013 the original author or authors.
|
||||||
|
>>>>>>> 12b17e3... Add Spring Security OAuth2 support to Spring Boot CLI
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.cli.compiler.autoconfigure;
|
||||||
|
|
||||||
|
import org.codehaus.groovy.ast.ClassNode;
|
||||||
|
import org.codehaus.groovy.control.CompilationFailedException;
|
||||||
|
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||||
|
import org.springframework.boot.cli.compiler.AstUtils;
|
||||||
|
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||||
|
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link CompilerAutoConfiguration} for Spring Security OAuth2.
|
||||||
|
*
|
||||||
|
* @author Greg Turnquist
|
||||||
|
*/
|
||||||
|
public class SpringSecurityOAuth2CompilerAutoConfiguration extends CompilerAutoConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(ClassNode classNode) {
|
||||||
|
return AstUtils.hasAtLeastOneAnnotation(classNode,
|
||||||
|
"EnableAuthorizationServer", "EnableResourceServer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
|
||||||
|
dependencies.add("spring-security-oauth2").add("spring-boot-starter-web")
|
||||||
|
.add("spring-boot-starter-security");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
|
||||||
|
imports
|
||||||
|
.addImports(
|
||||||
|
"org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity")
|
||||||
|
.addStarImports(
|
||||||
|
"org.springframework.security.oauth2.config.annotation.web.configuration",
|
||||||
|
"org.springframework.security.access.prepost");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-samples</artifactId>
|
||||||
|
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>spring-boot-sample-secure-oauth2</artifactId>
|
||||||
|
<name>Spring Boot Security OAuth2 Sample</name>
|
||||||
|
<description>Spring Boot Security OAuth2 Sample</description>
|
||||||
|
<url>http://projects.spring.io/spring-boot/</url>
|
||||||
|
<organization>
|
||||||
|
<name>Pivotal Software, Inc.</name>
|
||||||
|
<url>http://www.spring.io</url>
|
||||||
|
</organization>
|
||||||
|
<properties>
|
||||||
|
<main.basedir>${basedir}/../..</main.basedir>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security.oauth</groupId>
|
||||||
|
<artifactId>spring-security-oauth2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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 sample;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
|
||||||
|
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
/**
|
||||||
|
* After you launch the app, you can seek a bearer token like this:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* curl localhost:8080/oauth/token -d "grant_type=password&scope=read&username=greg&password=turnquist" -u foo:bar
|
||||||
|
*
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>grant_type=password (user credentials will be supplied)</li>
|
||||||
|
* <li>scope=read (read only scope)</li>
|
||||||
|
* <li>username=greg (username checked against user details service)</li>
|
||||||
|
* <li>password=turnquist (password checked against user details service)</li>
|
||||||
|
* <li>-u foo:bar (clientid:secret)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* Response should be similar to this:
|
||||||
|
* <code>{"access_token":"533de99b-5a0f-4175-8afd-1a64feb952d5","token_type":"bearer","expires_in":43199,"scope":"read"}</code>
|
||||||
|
*
|
||||||
|
* With the token value, you can now interrogate the RESTful interface like this:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* curl -H "Authorization: bearer [access_token]" localhost:8080/flights/1
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* You should then see the pre-loaded data like this:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "origin" : "Nashville",
|
||||||
|
* "destination" : "Dallas",
|
||||||
|
* "airline" : "Spring Ways",
|
||||||
|
* "flightNumber" : "OAUTH2",
|
||||||
|
* "date" : null,
|
||||||
|
* "traveler" : "Greg Turnquist",
|
||||||
|
* "_links" : {
|
||||||
|
* "self" : {
|
||||||
|
* "href" : "http://localhost:8080/flights/1"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Test creating a new entry:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* curl -i -H "Authorization: bearer [access token]" -H "Content-Type:application/json" localhost:8080/flights -X POST -d @flight.json
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Insufficient scope? (read not write) Ask for a new token!
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* curl localhost:8080/oauth/token -d "grant_type=password&scope=write&username=greg&password=turnquist" -u foo:bar
|
||||||
|
*
|
||||||
|
* {"access_token":"cfa69736-e2aa-4ae7-abbb-3085acda560e","token_type":"bearer","expires_in":43200,"scope":"write"}
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Retry with the new token. There should be a Location header.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Location: http://localhost:8080/flights/2
|
||||||
|
*
|
||||||
|
* curl -H "Authorization: bearer [access token]" localhost:8080/flights/2
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author Craig Walls
|
||||||
|
* @author Greg Turnquist
|
||||||
|
*/
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
@EnableAuthorizationServer
|
||||||
|
@EnableResourceServer
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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 sample;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain object for tracking flights
|
||||||
|
*
|
||||||
|
* @author Craig Walls
|
||||||
|
* @author Greg Turnquist
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class Flight {
|
||||||
|
|
||||||
|
@Id @GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String origin;
|
||||||
|
private String destination;
|
||||||
|
private String airline;
|
||||||
|
private String flightNumber;
|
||||||
|
private Date date;
|
||||||
|
private String traveler;
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOrigin(String origin) {
|
||||||
|
this.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDestination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDestination(String destination) {
|
||||||
|
this.destination = destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAirline() {
|
||||||
|
return airline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAirline(String airline) {
|
||||||
|
this.airline = airline;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFlightNumber() {
|
||||||
|
return flightNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlightNumber(String flightNumber) {
|
||||||
|
this.flightNumber = flightNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate(Date date) {
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTraveler() {
|
||||||
|
return traveler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTraveler(String traveler) {
|
||||||
|
this.traveler = traveler;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-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 sample;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Data interface with secured methods
|
||||||
|
*
|
||||||
|
* @author Craig Walls
|
||||||
|
* @author Greg Turnquist
|
||||||
|
*/
|
||||||
|
public interface FlightRepository extends CrudRepository<Flight, Long> {
|
||||||
|
|
||||||
|
@PreAuthorize("#oauth2.hasScope('read')")
|
||||||
|
@Override
|
||||||
|
Iterable<Flight> findAll();
|
||||||
|
|
||||||
|
@PreAuthorize("#oauth2.hasScope('read')")
|
||||||
|
@Override
|
||||||
|
Flight findOne(Long aLong);
|
||||||
|
|
||||||
|
@PreAuthorize("#oauth2.hasScope('write')")
|
||||||
|
@Override
|
||||||
|
<S extends Flight> S save(S entity);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
spring.datasource.platform=h2
|
||||||
|
spring.oauth2.client.client-id=foo
|
||||||
|
spring.oauth2.client.client-secret=bar
|
||||||
|
|
||||||
|
security.user.name=greg
|
||||||
|
security.user.password=turnquist
|
||||||
|
|
||||||
|
logging.level.org.springframework.security=DEBUG
|
@ -0,0 +1,4 @@
|
|||||||
|
insert into FLIGHT
|
||||||
|
(id, origin, destination, airline, flight_number, traveler)
|
||||||
|
values
|
||||||
|
(1, 'Nashville', 'Dallas', 'Spring Ways', 'OAUTH2', 'Greg Turnquist');
|
@ -0,0 +1,145 @@
|
|||||||
|
package sample;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.IntegrationTest;
|
||||||
|
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||||
|
import org.springframework.hateoas.MediaTypes;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.test.context.web.WebAppConfiguration;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
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 Greg Turnquist
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@WebAppConfiguration
|
||||||
|
@SpringApplicationConfiguration(classes = Application.class)
|
||||||
|
@IntegrationTest("server.port:0")
|
||||||
|
public class ApplicationTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
WebApplicationContext context;
|
||||||
|
@Autowired
|
||||||
|
FilterChainProxy filterChain;
|
||||||
|
|
||||||
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
|
||||||
|
this.mvc = webAppContextSetup(this.context).addFilters(this.filterChain).build();
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void everythingIsSecuredByDefault() throws Exception {
|
||||||
|
|
||||||
|
this.mvc.perform(get("/").//
|
||||||
|
accept(MediaTypes.HAL_JSON)).// /
|
||||||
|
andExpect(status().isUnauthorized()).//
|
||||||
|
andDo(print());
|
||||||
|
|
||||||
|
this.mvc.perform(get("/flights").//
|
||||||
|
accept(MediaTypes.HAL_JSON)).// /
|
||||||
|
andExpect(status().isUnauthorized()).//
|
||||||
|
andDo(print());
|
||||||
|
|
||||||
|
this.mvc.perform(get("/flights/1").//
|
||||||
|
accept(MediaTypes.HAL_JSON)).// /
|
||||||
|
andExpect(status().isUnauthorized()).//
|
||||||
|
andDo(print());
|
||||||
|
|
||||||
|
this.mvc.perform(get("/alps").//
|
||||||
|
accept(MediaTypes.HAL_JSON)).// /
|
||||||
|
andExpect(status().isUnauthorized()).//
|
||||||
|
andDo(print());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
// TODO: maybe show mixed basic + token auth on different resources?
|
||||||
|
public void accessingRootUriPossibleWithUserAccount() throws Exception {
|
||||||
|
|
||||||
|
this.mvc.perform(
|
||||||
|
get("/").//
|
||||||
|
accept(MediaTypes.HAL_JSON).//
|
||||||
|
header("Authorization",
|
||||||
|
"Basic "
|
||||||
|
+ new String(Base64.encode("greg:turnquist"
|
||||||
|
.getBytes()))))
|
||||||
|
.//
|
||||||
|
andExpect(header().string("Content-Type", MediaTypes.HAL_JSON.toString()))
|
||||||
|
.//
|
||||||
|
andExpect(status().isOk()).//
|
||||||
|
andDo(print());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useAppSecretsPlusUserAccountToGetBearerToken() throws Exception {
|
||||||
|
|
||||||
|
MvcResult result = this.mvc
|
||||||
|
.perform(
|
||||||
|
get("/oauth/token").//
|
||||||
|
header("Authorization",
|
||||||
|
"Basic "
|
||||||
|
+ new String(Base64.encode("foo:bar"
|
||||||
|
.getBytes()))).//
|
||||||
|
param("grant_type", "password").//
|
||||||
|
param("scope", "read").//
|
||||||
|
param("username", "greg").//
|
||||||
|
param("password", "turnquist")).//
|
||||||
|
andExpect(status().isOk()).//
|
||||||
|
andDo(print()).//
|
||||||
|
andReturn();
|
||||||
|
|
||||||
|
Object accessToken = this.objectMapper.readValue(
|
||||||
|
result.getResponse().getContentAsString(), Map.class).get("access_token");
|
||||||
|
|
||||||
|
MvcResult flightsAction = this.mvc
|
||||||
|
.perform(get("/flights/1").//
|
||||||
|
accept(MediaTypes.HAL_JSON).//
|
||||||
|
header("Authorization", "Bearer " + accessToken))
|
||||||
|
.//
|
||||||
|
andExpect(header().string("Content-Type", MediaTypes.HAL_JSON.toString()))
|
||||||
|
.//
|
||||||
|
andExpect(status().isOk()).//
|
||||||
|
andDo(print()).//
|
||||||
|
andReturn();
|
||||||
|
|
||||||
|
Flight flight = this.objectMapper.readValue(flightsAction.getResponse()
|
||||||
|
.getContentAsString(), Flight.class);
|
||||||
|
|
||||||
|
assertThat(flight.getOrigin(), is("Nashville"));
|
||||||
|
assertThat(flight.getDestination(), is("Dallas"));
|
||||||
|
assertThat(flight.getAirline(), is("Spring Ways"));
|
||||||
|
assertThat(flight.getFlightNumber(), is("OAUTH2"));
|
||||||
|
assertThat(flight.getTraveler(), is("Greg Turnquist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue