Commit 53f67a44 authored by Greg Turnquist's avatar Greg Turnquist Committed by Dave Syer

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)
parent dbc538d0
...@@ -32,3 +32,4 @@ overridedb.* ...@@ -32,3 +32,4 @@ overridedb.*
*.jar *.jar
.DS_Store .DS_Store
.factorypath .factorypath
data
...@@ -299,16 +299,34 @@ ...@@ -299,16 +299,34 @@
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId> <artifactId>spring-data-jpa</artifactId>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId> <artifactId>spring-data-rest-webmvc</artifactId>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId> <artifactId>spring-data-mongodb</artifactId>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
...@@ -319,11 +337,23 @@ ...@@ -319,11 +337,23 @@
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId> <artifactId>spring-data-elasticsearch</artifactId>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.data</groupId> <groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId> <artifactId>spring-data-solr</artifactId>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.hateoas</groupId> <groupId>org.springframework.hateoas</groupId>
...@@ -355,6 +385,16 @@ ...@@ -355,6 +385,16 @@
<artifactId>spring-security-config</artifactId> <artifactId>spring-security-config</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.amqp</groupId> <groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId> <artifactId>spring-rabbit</artifactId>
......
/*
* 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());
}
}
\ No newline at end of file
/*
* 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;
}
}
/*
* 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;
}
}
}
/*
* 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;
}
}
}
/*
* 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);
}
}
}
/*
* 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);
}
}
}
/*
* 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;
}
}
}
/*
* 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");
}
}
}
/*
* 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");
}
}
\ No newline at end of file
/*
* 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;
}
}
\ No newline at end of file
...@@ -52,6 +52,7 @@ org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\ ...@@ -52,6 +52,7 @@ org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.SpringSecurityOAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
......
/*
* 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());
}
}
/*
* 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-----";
}
/*
* 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());
}
}
package org.test
@EnableAuthorizationServer
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RestController
class SampleController {
@PreAuthorize("#oauth2.hasScope('read')")
@RequestMapping("/")
def hello() {
[message: "Hello World!"]
}
}
/*
<<<<<<< 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");
}
}
...@@ -11,7 +11,9 @@ org.springframework.boot.cli.compiler.autoconfigure.JUnitCompilerAutoConfigurati ...@@ -11,7 +11,9 @@ org.springframework.boot.cli.compiler.autoconfigure.JUnitCompilerAutoConfigurati
org.springframework.boot.cli.compiler.autoconfigure.SpockCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpockCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.TransactionManagementCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.TransactionManagementCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringIntegrationCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringIntegrationCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityOAuth2CompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSecurityOAuth2CompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringMobileCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringMobileCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSocialFacebookCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringSocialFacebookCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSocialLinkedInCompilerAutoConfiguration org.springframework.boot.cli.compiler.autoconfigure.SpringSocialLinkedInCompilerAutoConfiguration
......
...@@ -69,6 +69,18 @@ public class SampleIntegrationTests { ...@@ -69,6 +69,18 @@ public class SampleIntegrationTests {
output.contains("completed with the following parameters")); output.contains("completed with the following parameters"));
} }
@Test
@Ignore("Spring Security Oauth2 autoconfiguration reports bean creation issue with methodSecurityInterceptor")
public void oauth2Sample() throws Exception {
String output = this.cli.run("oauth2.groovy");
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.clientId"));
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.secret = ****"));
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.resourceId"));
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.authorizationTypes"));
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.scopes"));
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.redirectUris"));
}
@Test @Test
public void reactorSample() throws Exception { public void reactorSample() throws Exception {
String output = this.cli.run("reactor.groovy", "Phil"); String output = this.cli.run("reactor.groovy", "Phil");
......
...@@ -135,8 +135,12 @@ ...@@ -135,8 +135,12 @@
<spring-social-facebook.version>2.0.1.RELEASE</spring-social-facebook.version> <spring-social-facebook.version>2.0.1.RELEASE</spring-social-facebook.version>
<spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version> <spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version>
<spring-social-twitter.version>1.1.0.RELEASE</spring-social-twitter.version> <spring-social-twitter.version>1.1.0.RELEASE</spring-social-twitter.version>
<spring-security.version>3.2.5.RELEASE</spring-security.version>
<spring-security-oauth2.version>2.0.5.RELEASE</spring-security-oauth2.version>
<spring-security-jwt.version>1.0.2.RELEASE</spring-security-jwt.version>
<spring-ws.version>2.2.1.RELEASE</spring-ws.version> <spring-ws.version>2.2.1.RELEASE</spring-ws.version>
<statsd-client.version>3.1.0</statsd-client.version> <statsd-client.version>3.1.0</statsd-client.version>
<sendgrid.version>2.1.0</sendgrid.version>
<sun-mail.version>${javax-mail.version}</sun-mail.version> <sun-mail.version>${javax-mail.version}</sun-mail.version>
<thymeleaf.version>2.1.4.RELEASE</thymeleaf.version> <thymeleaf.version>2.1.4.RELEASE</thymeleaf.version>
<thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version> <thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version>
...@@ -1521,6 +1525,11 @@ ...@@ -1521,6 +1525,11 @@
<artifactId>spring-security-jwt</artifactId> <artifactId>spring-security-jwt</artifactId>
<version>${spring-security-jwt.version}</version> <version>${spring-security-jwt.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${spring-security-oauth2.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.social</groupId> <groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId> <artifactId>spring-social-config</artifactId>
...@@ -1864,4 +1873,4 @@ ...@@ -1864,4 +1873,4 @@
<id>integration-test</id> <id>integration-test</id>
</profile> </profile>
</profiles> </profiles>
</project> </project>
\ No newline at end of file
...@@ -58,6 +58,7 @@ ...@@ -58,6 +58,7 @@
<module>spring-boot-sample-parent-context</module> <module>spring-boot-sample-parent-context</module>
<module>spring-boot-sample-profile</module> <module>spring-boot-sample-profile</module>
<module>spring-boot-sample-secure</module> <module>spring-boot-sample-secure</module>
<module>spring-boot-sample-secure-oauth2</module>
<module>spring-boot-sample-servlet</module> <module>spring-boot-sample-servlet</module>
<module>spring-boot-sample-simple</module> <module>spring-boot-sample-simple</module>
<module>spring-boot-sample-testng</module> <module>spring-boot-sample-testng</module>
......
<?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>
/*
* 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);
}
}
/*
* 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;
}
}
/*
* 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);
}
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
insert into FLIGHT
(id, origin, destination, airline, flight_number, traveler)
values
(1, 'Nashville', 'Dallas', 'Spring Ways', 'OAUTH2', 'Greg Turnquist');
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"));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment