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)
This commit is contained in:
Greg Turnquist 2014-10-02 12:13:01 -05:00 committed by Dave Syer
parent dbc538d054
commit 53f67a448f
32 changed files with 2857 additions and 1 deletions

1
.gitignore vendored
View File

@ -32,3 +32,4 @@ overridedb.*
*.jar
.DS_Store
.factorypath
data

View File

@ -299,16 +299,34 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
@ -319,11 +337,23 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.hateoas</groupId>
@ -355,6 +385,16 @@
<artifactId>spring-security-config</artifactId>
<optional>true</optional>
</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>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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());
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View File

@ -52,6 +52,7 @@ org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.SpringSecurityOAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\

View File

@ -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();
}
}
}

View File

@ -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());
}
}

View File

@ -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-----";
}

View File

@ -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());
}
}

View File

@ -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!"]
}
}

View File

@ -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");
}
}

View File

@ -11,7 +11,9 @@ org.springframework.boot.cli.compiler.autoconfigure.JUnitCompilerAutoConfigurati
org.springframework.boot.cli.compiler.autoconfigure.SpockCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.TransactionManagementCompilerAutoConfiguration
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.SpringSecurityOAuth2CompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringMobileCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSocialFacebookCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringSocialLinkedInCompilerAutoConfiguration

View File

@ -69,6 +69,18 @@ public class SampleIntegrationTests {
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
public void reactorSample() throws Exception {
String output = this.cli.run("reactor.groovy", "Phil");

View File

@ -135,8 +135,12 @@
<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-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>
<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>
<thymeleaf.version>2.1.4.RELEASE</thymeleaf.version>
<thymeleaf-extras-springsecurity4.version>2.1.2.RELEASE</thymeleaf-extras-springsecurity4.version>
@ -1521,6 +1525,11 @@
<artifactId>spring-security-jwt</artifactId>
<version>${spring-security-jwt.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${spring-security-oauth2.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
@ -1864,4 +1873,4 @@
<id>integration-test</id>
</profile>
</profiles>
</project>
</project>

View File

@ -58,6 +58,7 @@
<module>spring-boot-sample-parent-context</module>
<module>spring-boot-sample-profile</module>
<module>spring-boot-sample-secure</module>
<module>spring-boot-sample-secure-oauth2</module>
<module>spring-boot-sample-servlet</module>
<module>spring-boot-sample-simple</module>
<module>spring-boot-sample-testng</module>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -0,0 +1,4 @@
insert into FLIGHT
(id, origin, destination, airline, flight_number, traveler)
values
(1, 'Nashville', 'Dallas', 'Spring Ways', 'OAUTH2', 'Greg Turnquist');

View File

@ -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"));
}
}