Commit 09a29a72 authored by Phillip Webb's avatar Phillip Webb

Polish OAuth SSO

parent 31d6a0f1
......@@ -102,4 +102,15 @@ public class ConditionOutcome {
public String toString() {
return (this.message == null ? "" : this.message);
}
/**
* Return the inverse of the specified condition outcome.
* @param outcome the outcome to inverse
* @return the inverse of the condition outcome
* @since 1.3.0
*/
public static ConditionOutcome inverse(ConditionOutcome outcome) {
return new ConditionOutcome(!outcome.isMatch(), outcome.getMessage());
}
}
......@@ -18,16 +18,12 @@ package org.springframework.boot.autoconfigure.condition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
......@@ -42,18 +38,6 @@ public abstract class SpringBootCondition implements Condition {
private final Log logger = LogFactory.getLog(getClass());
public static boolean evaluateForClass(Class<?> annotated, ConditionContext context) {
Conditional conditional = AnnotationUtils.findAnnotation(annotated, Conditional.class);
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(annotated);
for (Class<? extends Condition> type : conditional.value()) {
Condition condition = BeanUtils.instantiateClass(type);
if (condition.matches(context, metadata)) {
return true;
}
}
return false;
}
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
......
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2015 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.
......@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security;
import java.io.IOException;
......@@ -26,26 +27,27 @@ 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
* AuthenticationEntryPoint that sends a 401 and Parameterized by the value of the {@coe
* WWW-Authenticate} header. Like the {@link BasicAuthenticationEntryPoint} but more
* flexible.
*
* @author Dave Syer
*
* @since 1.3.0
*/
public class Http401AuthenticationEntryPoint implements AuthenticationEntryPoint {
private final String authenticateHeader;
private final String headerValue;
public Http401AuthenticationEntryPoint(String authenticateHeader) {
this.authenticateHeader = authenticateHeader;
public Http401AuthenticationEntryPoint(String headerValue) {
this.headerValue = headerValue;
}
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setHeader("WWW-Authenticate", authenticateHeader);
response.setHeader("WWW-Authenticate", this.headerValue);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
authException.getMessage());
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -20,13 +20,14 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.OAuth2RestOperationsConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.method.OAuth2MethodSecurityConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
......@@ -37,16 +38,16 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.R
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Spring Security OAuth2 top level auto-configuration beans
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security OAuth2.
*
* @author Greg Turnquist
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass({ OAuth2AccessToken.class, WebMvcConfigurerAdapter.class })
@Import({ SpringSecurityOAuth2AuthorizationServerConfiguration.class,
OAuth2MethodSecurityConfiguration.class,
OAuth2ResourceServerConfiguration.class,
OAuth2MethodSecurityConfiguration.class, OAuth2ResourceServerConfiguration.class,
OAuth2RestOperationsConfiguration.class })
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
......
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -21,7 +21,10 @@ import java.util.UUID;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for OAuth2 Client.
*
* @author Dave Syer
* @since 1.3.0
*/
@ConfigurationProperties("spring.oauth2.client")
public class OAuth2ClientProperties {
......
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -47,12 +47,13 @@ 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
* Configuration for 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
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass(EnableAuthorizationServer.class)
......@@ -62,6 +63,9 @@ import org.springframework.security.oauth2.provider.token.TokenStore;
public class SpringSecurityOAuth2AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private static final Log logger = LogFactory
.getLog(SpringSecurityOAuth2AuthorizationServerConfiguration.class);
@Autowired
private BaseClientDetails details;
......@@ -74,9 +78,6 @@ public class SpringSecurityOAuth2AuthorizationServerConfiguration extends
@Configuration
protected static class ClientDetailsLogger {
private static final Log logger = LogFactory
.getLog(SpringSecurityOAuth2AuthorizationServerConfiguration.class);
@Autowired
private OAuth2ClientProperties credentials;
......
/*
* Copyright 2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -28,21 +28,22 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
/**
* Configuration for OAuth2 Single Sign On (SSO). If there is an existing
* Enable OAuth2 Single Sign On (SSO). If there is an existing
* {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
* <code>@EnableOAuth2Sso</code>, it is enhanced by adding an authentication filter and an
* authentication entry point. If the user only has <code>@EnableOAuth2Sso</code> but not
* on a WebSecurityConfigurerAdapter then one is added with all paths secured and with an
* order that puts it ahead of the default HTTP Basic security chain in Spring Boot.
* {@code @EnableOAuth2Sso}, it is enhanced by adding an authentication filter and an
* authentication entry point. If the user only has {@code @EnableOAuth2Sso} but not on a
* WebSecurityConfigurerAdapter then one is added with all paths secured and with an order
* that puts it ahead of the default HTTP Basic security chain in Spring Boot.
*
* @author Dave Syer
*
* @since 1.3.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableOAuth2Client
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class, ResourceServerTokenServicesConfiguration.class })
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,
ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {
}
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.client;
import javax.annotation.Resource;
......@@ -49,8 +50,10 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
/**
* @author Dave Syer
* Configuration for OAuth2 Single Sign On REST operations.
*
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass(EnableOAuth2Client.class)
......@@ -109,7 +112,7 @@ public class OAuth2RestOperationsConfiguration {
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(accessTokenRequest);
return new DefaultOAuth2ClientContext(this.accessTokenRequest);
}
@Bean
......
/*
* Copyright 2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -46,7 +46,6 @@ import org.springframework.util.ReflectionUtils;
* authentication filter and an authentication entry point.
*
* @author Dave Syer
*
*/
@Configuration
@Conditional(WebSecurityEnhancerCondition.class)
......@@ -64,7 +63,8 @@ public class OAuth2SsoCustomConfiguration implements ImportAware, BeanPostProces
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
configType = ClassUtils.resolveClassName(importMetadata.getClassName(), null);
this.configType = ClassUtils
.resolveClassName(importMetadata.getClassName(), null);
}
......@@ -77,11 +77,11 @@ public class OAuth2SsoCustomConfiguration implements ImportAware, BeanPostProces
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (configType.isAssignableFrom(bean.getClass())
if (this.configType.isAssignableFrom(bean.getClass())
&& bean instanceof WebSecurityConfigurerAdapter) {
ProxyFactory factory = new ProxyFactory();
factory.setTarget(bean);
factory.addAdvice(new SsoSecurityAdapter(beanFactory));
factory.addAdvice(new SsoSecurityAdapter(this.beanFactory));
bean = factory.getProxy();
}
return bean;
......@@ -92,7 +92,7 @@ public class OAuth2SsoCustomConfiguration implements ImportAware, BeanPostProces
private SsoSecurityConfigurer configurer;
public SsoSecurityAdapter(BeanFactory beanFactory) {
configurer = new SsoSecurityConfigurer(beanFactory);
this.configurer = new SsoSecurityConfigurer(beanFactory);
}
@Override
......@@ -102,8 +102,8 @@ public class OAuth2SsoCustomConfiguration implements ImportAware, BeanPostProces
WebSecurityConfigurerAdapter.class, "getHttp");
ReflectionUtils.makeAccessible(method);
HttpSecurity http = (HttpSecurity) ReflectionUtils.invokeMethod(method,
(WebSecurityConfigurerAdapter) invocation.getThis());
configurer.configure(http);
invocation.getThis());
this.configurer.configure(http);
}
return invocation.proceed();
}
......@@ -127,6 +127,7 @@ public class OAuth2SsoCustomConfiguration implements ImportAware, BeanPostProces
return ConditionOutcome
.noMatch("found no @EnableOAuth2Sso on a WebSecurityConfigurerAdapter");
}
}
}
/*
* Copyright 2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -32,12 +32,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.util.ClassUtils;
/**
* If the user only has <code>@EnableOAuth2Sso</code> but not on a
* WebSecurityConfigurerAdapter then one is added with all paths secured and with an order
* that puts it ahead of the default HTTP Basic security chain in Spring Boot.
* Configuration for OAuth2 Single Sign On (SSO). If the user only has
* {@code @EnableOAuth2Sso} but not on a {@code WebSecurityConfigurerAdapter} then one is
* added with all paths secured and with an order that puts it ahead of the default HTTP
* Basic security chain in Spring Boot.
*
* @author Dave Syer
*
* @since 1.3.0
*/
@Configuration
@EnableConfigurationProperties(OAuth2SsoProperties.class)
......@@ -57,13 +58,13 @@ public class OAuth2SsoDefaultConfiguration {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
new SsoSecurityConfigurer(beanFactory).configure(http);
new SsoSecurityConfigurer(this.beanFactory).configure(http);
}
@Override
public int getOrder() {
if (sso.getFilterOrder() != null) {
return sso.getFilterOrder();
if (this.sso.getFilterOrder() != null) {
return this.sso.getFilterOrder();
}
if (ClassUtils
.isPresent(
......@@ -80,6 +81,7 @@ public class OAuth2SsoDefaultConfiguration {
}
private static class NeedsWebSecurityCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
......@@ -95,6 +97,7 @@ public class OAuth2SsoDefaultConfiguration {
return ConditionOutcome
.match("found no @EnableOAuth2Sso on a WebSecurityConfigurerAdapter");
}
}
}
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,13 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.client;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Dave Syer
* Configuration properties for OAuth2 Single Sign On (SSO).
*
* @author Dave Syer
* @since 1.3.0
*/
@ConfigurationProperties("spring.oauth2.sso")
public class OAuth2SsoProperties {
......@@ -39,7 +42,7 @@ public class OAuth2SsoProperties {
private Integer filterOrder;
public String getLoginPath() {
return loginPath;
return this.loginPath;
}
public void setLoginPath(String loginPath) {
......@@ -47,7 +50,7 @@ public class OAuth2SsoProperties {
}
public Integer getFilterOrder() {
return filterOrder;
return this.filterOrder;
}
public void setFilterOrder(Integer filterOrder) {
......
/*
* Copyright 2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -36,7 +36,7 @@ class SsoSecurityConfigurer {
}
public void configure(HttpSecurity http) throws Exception {
OAuth2SsoProperties sso = beanFactory.getBean(OAuth2SsoProperties.class);
OAuth2SsoProperties sso = this.beanFactory.getBean(OAuth2SsoProperties.class);
// Delay the processing of the filter until we know the
// SessionAuthenticationStrategy is available:
http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
......@@ -46,9 +46,9 @@ class SsoSecurityConfigurer {
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
OAuth2SsoProperties sso) {
OAuth2RestOperations restTemplate = beanFactory
OAuth2RestOperations restTemplate = this.beanFactory
.getBean(OAuth2RestOperations.class);
ResourceServerTokenServices tokenServices = beanFactory
ResourceServerTokenServices tokenServices = this.beanFactory
.getBean(ResourceServerTokenServices.class);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
sso.getLoginPath());
......@@ -59,6 +59,7 @@ class SsoSecurityConfigurer {
private static class OAuth2ClientAuthenticationConfigurer extends
SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private OAuth2ClientAuthenticationProcessingFilter filter;
public OAuth2ClientAuthenticationConfigurer(
......@@ -68,12 +69,13 @@ class SsoSecurityConfigurer {
@Override
public void configure(HttpSecurity builder) throws Exception {
OAuth2ClientAuthenticationProcessingFilter ssoFilter = filter;
OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
ssoFilter.setSessionAuthenticationStrategy(builder
.getSharedObject(SessionAuthenticationStrategy.class));
builder.addFilterAfter(ssoFilter,
AbstractPreAuthenticatedProcessingFilter.class);
}
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -30,22 +30,22 @@ import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecur
/**
* Auto-configure an expression handler for method-level security (if the user already has
* <code>@EnableGlobalMethodSecurity</code>).
* {@code @EnableGlobalMethodSecurity}).
*
* @author Greg Turnquist
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass({ OAuth2AccessToken.class })
@ConditionalOnBean(GlobalMethodSecurityConfiguration.class)
public class OAuth2MethodSecurityConfiguration implements
BeanFactoryPostProcessor {
public class OAuth2MethodSecurityConfiguration implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
beanFactory
.addBeanPostProcessor(new OAuth2ExpressionHandlerInjectionPostProcessor());
OAuth2ExpressionHandlerInjectionPostProcessor processor = new OAuth2ExpressionHandlerInjectionPostProcessor();
beanFactory.addBeanPostProcessor(processor);
}
private static class OAuth2ExpressionHandlerInjectionPostProcessor implements
......
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,12 +13,24 @@
* 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.security.oauth2.provider.token.store.JwtAccessTokenConverter;
/**
* Callback interface that can be used to provide additional configuration to the
* {@link JwtAccessTokenConverter}.
*
* @author Dave Syer
* @since 1.3.0
*/
public interface JwtAccessTokenConverterConfigurer {
/**
* Configure the {@link JwtAccessTokenConverter}.
* @param converter the converter to configure
*/
void configure(JwtAccessTokenConverter converter);
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.security.oauth2.resource;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
......@@ -27,13 +28,16 @@ import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration.ResourceServerCondition;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
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.annotation.AnnotationUtils;
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;
......@@ -50,6 +54,7 @@ import org.springframework.util.StringUtils;
*
* @author Greg Turnquist
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@Conditional(ResourceServerCondition.class)
......@@ -91,10 +96,13 @@ public class OAuth2ResourceServerConfiguration {
}
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
protected static class ResourceServerCondition extends SpringBootCondition implements
ConfigurationCondition {
private static final String AUTHORIZATION_ANNOTATION = "org.springframework."
+ "security.oauth2.config.annotation.web.configuration."
+ "AuthorizationServerEndpointsConfiguration";
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
......@@ -104,31 +112,48 @@ public class OAuth2ResourceServerConfiguration {
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment);
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,
"spring.oauth2.resource.");
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()) {
if (!resolver.getSubProperties("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 (StringUtils.hasText(resolver.getProperty("user-info-uri"))) {
return ConditionOutcome.match("found UserInfo "
+ "URI resource configuration");
}
if (ClassUtils.isPresent(AUTHORIZATION_ANNOTATION, null)) {
if (AuthorizationServerEndpointsConfigurationBeanCondition
.matches(context)) {
return ConditionOutcome.match("found authorization "
+ "server endpoints configuration");
}
}
return ConditionOutcome.noMatch("found neither client id nor "
+ "JWT resource nor authorization server");
}
if (ClassUtils
.isPresent(
"org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration",
null)) {
if (SpringBootCondition.evaluateForClass(ResourceServerCondition.class, context)) {
return ConditionOutcome
.match("found authorization server endpoints configuration");
}
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
private static class AuthorizationServerEndpointsConfigurationBeanCondition {
public static boolean matches(ConditionContext context) {
Class<AuthorizationServerEndpointsConfigurationBeanCondition> type = AuthorizationServerEndpointsConfigurationBeanCondition.class;
Conditional conditional = AnnotationUtils.findAnnotation(type,
Conditional.class);
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(type);
for (Class<? extends Condition> conditionType : conditional.value()) {
Condition condition = BeanUtils.instantiateClass(conditionType);
if (condition.matches(context, metadata)) {
return true;
}
}
return ConditionOutcome
.noMatch("found neither client id nor JWT resource nor authorization server");
return false;
}
}
......
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -30,8 +30,10 @@ import org.springframework.validation.Validator;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author Dave Syer
* Configuration properties for OAuth2 Resources.
*
* @author Dave Syer
* @since 1.3.0
*/
@ConfigurationProperties("spring.oauth2.resource")
public class ResourceServerProperties implements Validator, BeanFactoryAware {
......@@ -133,7 +135,7 @@ public class ResourceServerProperties implements Validator, BeanFactoryAware {
}
public String getTokenType() {
return tokenType;
return this.tokenType;
}
public void setTokenType(String tokenType) {
......@@ -187,7 +189,8 @@ public class ResourceServerProperties implements Validator, BeanFactoryAware {
}
if (!StringUtils.hasText(resource.getUserInfoUri())) {
errors.rejectValue("tokenInfoUri", "missing.tokenInfoUri",
"Missing tokenInfoUri and userInfoUri and there is no JWT verifier key");
"Missing tokenInfoUri and userInfoUri and there is no "
+ "JWT verifier key");
}
}
}
......
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,12 +13,16 @@
* 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.List;
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.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
......@@ -31,16 +35,18 @@ import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import org.springframework.social.oauth2.AccessGrant;
/**
* @author Dave Syer
* {@link ResourceServerTokenServices} backed by Spring Social.
*
* @author Dave Syer
* @since 1.3.0
*/
public class SpringSocialTokenServices implements ResourceServerTokenServices {
protected final Log logger = LogFactory.getLog(getClass());
private OAuth2ConnectionFactory<?> connectionFactory;
private final OAuth2ConnectionFactory<?> connectionFactory;
private String clientId;
private final String clientId;
public SpringSocialTokenServices(OAuth2ConnectionFactory<?> connectionFactory,
String clientId) {
......@@ -51,21 +57,20 @@ public class SpringSocialTokenServices implements ResourceServerTokenServices {
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Connection<?> connection = connectionFactory.createConnection(new AccessGrant(
accessToken));
AccessGrant accessGrant = new AccessGrant(accessToken);
Connection<?> connection = this.connectionFactory.createConnection(accessGrant);
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);
String principal = user.getUsername();
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
return new OAuth2Authentication(request, new UsernamePasswordAuthenticationToken(
principal, "N/A", authorities));
}
@Override
......
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -27,7 +27,7 @@ import org.springframework.security.oauth2.client.OAuth2RestTemplate;
* authentication (in the SSO or Resource Server use cases).
*
* @author Dave Syer
*
* @since 1.3.0
*/
public interface UserInfoRestTemplateCustomizer {
......
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,14 +13,17 @@
* 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.List;
import java.util.Map;
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.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
......@@ -32,13 +35,22 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
/**
* {@link ResourceServerTokenServices} that uses a user info REST service.
*
* @author Dave Syer
* @since 1.3.0
*/
public class UserInfoTokenServices implements ResourceServerTokenServices {
protected final Log logger = LogFactory.getLog(getClass());
private String userInfoEndpointUrl;
private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
"userid", "user_id", "login", "id", "name" };
private final String userInfoEndpointUrl;
private String clientId;
private final String clientId;
private OAuth2RestOperations restTemplate;
......@@ -60,31 +72,28 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Map<String, Object> map = getMap(userInfoEndpointUrl, accessToken);
Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
if (map.containsKey("error")) {
logger.debug("userinfo returned error: " + map.get("error"));
this.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);
Object principal = getPrincipal(map);
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
private Object getPrincipal(Map<String, Object> map) {
String[] keys = new String[] { "user", "username", "userid", "user_id", "login",
"id", "name" };
for (String key : keys) {
for (String key : PRINCIPAL_KEYS) {
if (map.containsKey(key)) {
return map.get(key);
}
......@@ -97,22 +106,19 @@ public class UserInfoTokenServices implements ResourceServerTokenServices {
throw new UnsupportedOperationException("Not supported: read access token");
}
@SuppressWarnings({ "unchecked" })
private Map<String, Object> getMap(String path, String accessToken) {
logger.info("Getting user info from: " + path);
this.logger.info("Getting user info from: " + path);
OAuth2RestOperations restTemplate = this.restTemplate;
if (restTemplate == null) {
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
resource.setClientId(clientId);
resource.setClientId(this.clientId);
restTemplate = new OAuth2RestTemplate(resource);
}
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(accessToken);
token.setTokenType(tokenType);
token.setTokenType(this.tokenType);
restTemplate.getOAuth2ClientContext().setAccessToken(token);
@SuppressWarnings("rawtypes")
Map map = restTemplate.getForEntity(path, Map.class).getBody();
@SuppressWarnings("unchecked")
Map<String, Object> result = map;
return result;
return restTemplate.getForEntity(path, Map.class).getBody();
}
}
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,9 +13,8 @@
* 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;
package org.springframework.boot.autoconfigure.security.oauth2.resource;
import java.util.Map;
......@@ -23,30 +22,34 @@ import org.junit.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.junit.Assert.assertNotNull;
/**
* @author Dave Syer
* Tests for {@link ResourceServerProperties}.
*
* @author Dave Syer
*/
public class ResourceServerPropertiesTests {
private ResourceServerProperties properties = new ResourceServerProperties("client", "secret");
private ResourceServerProperties properties = new ResourceServerProperties("client",
"secret");
@Test
@SuppressWarnings("unchecked")
public void json() throws Exception {
properties.getJwt().setKeyUri("http://example.com/token_key");
this.properties.getJwt().setKeyUri("http://example.com/token_key");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(properties);
@SuppressWarnings("unchecked")
String json = mapper.writeValueAsString(this.properties);
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());
this.properties.setUserInfoUri("http://example.com/userinfo");
assertNotNull("Wrong properties: " + this.properties, this.properties.getJwt()
.getKeyUri());
}
}
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,11 +13,11 @@
* 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.OAuth2ClientProperties;
......@@ -38,13 +38,23 @@ import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.social.connect.ConnectionFactoryLocator;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
/**
* @author Dave Syer
* Tests for {@link ResourceServerTokenServicesConfiguration}.
*
* @author Dave Syer
*/
public class ResourceServerTokenServicesConfigurationTests {
private static String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9"
+ "AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzE"
+ "tgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+T"
+ "Nu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG"
+ "8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8"
+ "dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB\n-----END PUBLIC KEY-----";
private ConfigurableApplicationContext context;
private ConfigurableEnvironment environment = new StandardEnvironment();
......@@ -112,7 +122,7 @@ public class ResourceServerTokenServicesConfigurationTests {
@Test
public void asymmetricJwt() {
EnvironmentTestUtils.addEnvironment(this.environment,
"spring.oauth2.resource.jwt.keyValue=" + publicKey);
"spring.oauth2.resource.jwt.keyValue=" + PUBLIC_KEY);
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
.environment(this.environment).web(false).run();
DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class);
......@@ -141,6 +151,7 @@ public class ResourceServerTokenServicesConfigurationTests {
PropertyPlaceholderAutoConfiguration.class })
@EnableConfigurationProperties(OAuth2ClientProperties.class)
protected static class ResourceConfiguration {
}
@Configuration
......@@ -154,18 +165,17 @@ public class ResourceServerTokenServicesConfigurationTests {
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);
}
return mock(EmbeddedServletContainerFactory.class);
}
private static String publicKey = "-----BEGIN PUBLIC KEY-----\n"
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzEtgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+TNu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB\n"
+ "-----END PUBLIC KEY-----";
}
}
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -15,14 +15,11 @@
*/
package org.springframework.boot.autoconfigure.security.oauth2.resource;
import static org.junit.Assert.assertEquals;
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;
......@@ -30,42 +27,51 @@ import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
/**
* @author Dave Syer
* Tests for {@link UserInfoTokenServices}.
*
* @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 OAuth2RestOperations template = 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));
this.resource.setClientId("foo");
given(this.template.getForEntity(any(String.class), any(Class.class)))
.willReturn(new ResponseEntity<Map>(this.map, HttpStatus.OK));
given(this.template.getAccessToken()).willReturn(
new DefaultOAuth2AccessToken("FOO"));
given(this.template.getResource()).willReturn(this.resource);
given(this.template.getOAuth2ClientContext()).willReturn(
mock(OAuth2ClientContext.class));
}
@Test
public void sunnyDay() {
services.setRestTemplate(template);
assertEquals("unknown", services.loadAuthentication("FOO").getName());
this.services.setRestTemplate(this.template);
assertEquals("unknown", this.services.loadAuthentication("FOO").getName());
}
@Test
public void userId() {
map.put("userid", "spencer");
services.setRestTemplate(template);
assertEquals("spencer", services.loadAuthentication("FOO").getName());
this.map.put("userid", "spencer");
this.services.setRestTemplate(this.template);
assertEquals("spencer", this.services.loadAuthentication("FOO").getName());
}
}
/*
* Copyright 2013-2014 the original author or authors.
* 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.
......@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.sso;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
package org.springframework.boot.autoconfigure.security.oauth2.sso;
import javax.servlet.Filter;
......@@ -39,9 +36,14 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Dave Syer
* Tests for {@link OAuth2AutoConfiguration} with basic configuration.
*
* @author Dave Syer
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
......@@ -64,12 +66,13 @@ public class BasicOAuth2SsoConfigurationTests {
@Before
public void init() {
mvc = MockMvcBuilders.webAppContextSetup(context).addFilters(filter).build();
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter).build();
}
@Test
public void homePageIsSecure() throws Exception {
mvc.perform(get("/")).andExpect(status().isFound())
this.mvc.perform(get("/")).andExpect(status().isFound())
.andExpect(header().string("location", "http://localhost/login"));
}
......
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,13 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.security.oauth2.sso;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
package org.springframework.boot.autoconfigure.security.oauth2.sso;
import javax.servlet.Filter;
......@@ -45,9 +40,16 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.startsWith;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Dave Syer
* Tests for {@link OAuth2AutoConfiguration} with custom configuration.
*
* @author Dave Syer
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfiguration.class)
......@@ -70,24 +72,25 @@ public class CustomOAuth2SsoConfigurationTests {
@Before
public void init() {
mvc = MockMvcBuilders.webAppContextSetup(context).addFilters(filter).build();
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter).build();
}
@Test
public void homePageIsBasicAuth() throws Exception {
mvc.perform(get("/")).andExpect(status().isUnauthorized())
this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", startsWith("Basic")));
}
@Test
public void uiPageIsSecure() throws Exception {
mvc.perform(get("/ui/")).andExpect(status().isFound())
this.mvc.perform(get("/ui/")).andExpect(status().isFound())
.andExpect(header().string("location", "http://localhost/login"));
}
@Test
public void uiTestPageIsAccessible() throws Exception {
mvc.perform(get("/ui/test")).andExpect(status().isOk())
this.mvc.perform(get("/ui/test")).andExpect(status().isOk())
.andExpect(content().string("test"));
}
......@@ -112,6 +115,7 @@ public class CustomOAuth2SsoConfigurationTests {
}
}
}
}
/*
* Copyright 2015 the original author or authors.
* Copyright 2012-2015 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.
......@@ -40,8 +40,8 @@ import org.springframework.context.annotation.Import;
@Import({ EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
SecurityAutoConfiguration.class }) @interface MinimalSecureWebConfiguration {
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, SecurityAutoConfiguration.class })
public @interface MinimalSecureWebConfiguration {
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -28,28 +28,30 @@ import org.springframework.boot.cli.compiler.DependencyCustomizer;
*
* @author Greg Turnquist
* @author Dave Syer
* @since 1.3.0
*/
public class SpringSecurityOAuth2CompilerAutoConfiguration extends CompilerAutoConfiguration {
public class SpringSecurityOAuth2CompilerAutoConfiguration extends
CompilerAutoConfiguration {
@Override
public boolean matches(ClassNode classNode) {
return AstUtils.hasAtLeastOneAnnotation(classNode,
"EnableAuthorizationServer", "EnableResourceServer", "EnableOAuth2Client", "EnableOAuth2Sso");
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableAuthorizationServer",
"EnableResourceServer", "EnableOAuth2Client", "EnableOAuth2Sso");
}
@Override
public void applyDependencies(DependencyCustomizer dependencies) throws CompilationFailedException {
dependencies.add("spring-security-oauth2").add("spring-boot-starter-web")
.add("spring-boot-starter-security");
public void applyDependencies(DependencyCustomizer dependencies)
throws CompilationFailedException {
dependencies.add("spring-security-oauth2", "spring-boot-starter-web",
"spring-boot-starter-security");
}
@Override
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
imports
.addImports(
"org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso")
.addStarImports(
imports.addImports("org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso");
imports.addStarImports(
"org.springframework.security.oauth2.config.annotation.web.configuration",
"org.springframework.security.access.prepost");
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -16,11 +16,6 @@
package org.springframework.boot.cli;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URI;
......@@ -28,6 +23,11 @@ import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Integration tests to exercise the samples.
*
......@@ -72,8 +72,10 @@ public class SampleIntegrationTests {
@Test
public void oauth2Sample() throws Exception {
String output = this.cli.run("oauth2.groovy");
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.client.clientId"));
assertTrue("Wrong output: " + output, output.contains("spring.oauth2.client.secret ="));
assertTrue("Wrong output: " + output,
output.contains("spring.oauth2.client.clientId"));
assertTrue("Wrong output: " + output,
output.contains("spring.oauth2.client.secret ="));
}
@Test
......
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
package sample.secure.oauth2;
import java.util.Date;
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;
......@@ -33,18 +35,24 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Flight {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@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;
return this.id;
}
public void setId(Long id) {
......@@ -52,7 +60,7 @@ public class Flight {
}
public String getOrigin() {
return origin;
return this.origin;
}
public void setOrigin(String origin) {
......@@ -60,7 +68,7 @@ public class Flight {
}
public String getDestination() {
return destination;
return this.destination;
}
public void setDestination(String destination) {
......@@ -68,7 +76,7 @@ public class Flight {
}
public String getAirline() {
return airline;
return this.airline;
}
public void setAirline(String airline) {
......@@ -76,7 +84,7 @@ public class Flight {
}
public String getFlightNumber() {
return flightNumber;
return this.flightNumber;
}
public void setFlightNumber(String flightNumber) {
......@@ -84,7 +92,7 @@ public class Flight {
}
public Date getDate() {
return date;
return this.date;
}
public void setDate(Date date) {
......@@ -92,10 +100,11 @@ public class Flight {
}
public String getTraveler() {
return traveler;
return this.traveler;
}
public void setTraveler(String traveler) {
this.traveler = traveler;
}
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
package sample.secure.oauth2;
import org.springframework.data.repository.CrudRepository;
import org.springframework.security.access.prepost.PreAuthorize;
......@@ -26,15 +27,16 @@ import org.springframework.security.access.prepost.PreAuthorize;
*/
public interface FlightRepository extends CrudRepository<Flight, Long> {
@PreAuthorize("#oauth2.hasScope('read')")
@Override
@PreAuthorize("#oauth2.hasScope('read')")
Iterable<Flight> findAll();
@PreAuthorize("#oauth2.hasScope('read')")
@Override
@PreAuthorize("#oauth2.hasScope('read')")
Flight findOne(Long aLong);
@PreAuthorize("#oauth2.hasScope('write')")
@Override
@PreAuthorize("#oauth2.hasScope('write')")
<S extends Flight> S save(S entity);
}
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
package sample.secure.oauth2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
......@@ -21,14 +21,11 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
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>
......@@ -91,16 +88,14 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.E
* @author Craig Walls
* @author Greg Turnquist
*/
// @formatter:on
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Application {
public class SampleSecureOAuth2Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
SpringApplication.run(SampleSecureOAuth2Application.class, args);
}
}
package sample;
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.request.MockMvcRequestBuilders.post;
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;
package sample.secure.oauth2;
import java.util.Map;
......@@ -28,8 +19,20 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.context.WebApplicationContext;
import sample.secure.oauth2.SampleSecureOAuth2Application;
import sample.secure.oauth2.Flight;
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.request.MockMvcRequestBuilders.post;
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
......@@ -38,12 +41,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = Application.class)
@SpringApplicationConfiguration(classes = SampleSecureOAuth2Application.class)
@IntegrationTest("server.port:0")
public class ApplicationTests {
public class SampleSecureOAuth2ApplicationTests {
@Autowired
WebApplicationContext context;
@Autowired
FilterChainProxy filterChain;
......@@ -53,85 +57,51 @@ public class ApplicationTests {
@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());
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 {
String header = "Basic " + new String(Base64.encode("greg:turnquist".getBytes()));
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());
get("/").accept(MediaTypes.HAL_JSON).header("Authorization", header))
.andExpect(
header().string("Content-Type", MediaTypes.HAL_JSON.toString()))
.andExpect(status().isOk()).andDo(print());
}
@Test
public void useAppSecretsPlusUserAccountToGetBearerToken() throws Exception {
// @formatter:off
String header = "Basic " + new String(Base64.encode("foo:bar".getBytes()));
MvcResult result = this.mvc
.perform(
post("/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();
// @formatter:on
post("/oauth/token").header("Authorization", header)
.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();
.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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment