Commit e9147c2f authored by Andy Wilkinson's avatar Andy Wilkinson

Remove Spring Security OAuth Auto-Configuration

This commit removes auto-configuration support for Spring Security
OAuth, paving the way for the introduction of auto-configuration for
Spring Security 5's new OAuth-related features.

Closes gh-10255
parent a0fee6fe
......@@ -522,16 +522,6 @@
<artifactId>spring-security-data</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.session</groupId>
<artifactId>spring-session-core</artifactId>
......
/*
* Copyright 2012-2017 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.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration;
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.OAuth2ResourceServerConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security OAuth2.
*
* @author Greg Turnquist
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass({ OAuth2AccessToken.class, WebMvcConfigurer.class })
@Import({ OAuth2AuthorizationServerConfiguration.class,
OAuth2MethodSecurityConfiguration.class, OAuth2ResourceServerConfiguration.class,
OAuth2RestOperationsConfiguration.class })
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
public class OAuth2AutoConfiguration {
private final OAuth2ClientProperties credentials;
public OAuth2AutoConfiguration(OAuth2ClientProperties credentials) {
this.credentials = credentials;
}
@Bean
public ResourceServerProperties resourceServerProperties() {
return new ResourceServerProperties(this.credentials.getClientId(),
this.credentials.getClientSecret());
}
}
/*
* Copyright 2012-2017 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;
/**
* Configuration properties for OAuth2 Client.
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "security.oauth2.client")
public class OAuth2ClientProperties {
/**
* OAuth2 client id.
*/
private String clientId;
/**
* OAuth2 client secret. A random secret is generated by default.
*/
private String clientSecret = UUID.randomUUID().toString();
private boolean defaultSecret = true;
public String getClientId() {
return this.clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return this.clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
this.defaultSecret = false;
}
public boolean isDefaultSecret() {
return this.defaultSecret;
}
}
/*
* Copyright 2012-2017 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 org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for OAuth2 Authorization server.
*
* @author Dave Syer
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "security.oauth2.authorization")
public class AuthorizationServerProperties {
/**
* Spring Security access rule for the check token endpoint (e.g. a SpEL expression
* like "isAuthenticated()") . Default is empty, which is interpreted as "denyAll()"
* (no access).
*/
private String checkTokenAccess;
/**
* Spring Security access rule for the token key endpoint (e.g. a SpEL expression like
* "isAuthenticated()"). Default is empty, which is interpreted as "denyAll()" (no
* access).
*/
private String tokenKeyAccess;
/**
* Realm name for client authentication. If an unauthenticated request comes in to the
* token endpoint, it will respond with a challenge including this name.
*/
private String realm;
public String getCheckTokenAccess() {
return this.checkTokenAccess;
}
public void setCheckTokenAccess(String checkTokenAccess) {
this.checkTokenAccess = checkTokenAccess;
}
public String getTokenKeyAccess() {
return this.tokenKeyAccess;
}
public void setTokenKeyAccess(String tokenKeyAccess) {
this.tokenKeyAccess = tokenKeyAccess;
}
public String getRealm() {
return this.realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
}
/*
* Copyright 2012-2017 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 javax.annotation.PostConstruct;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
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.OAuth2ClientProperties;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* 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)
@ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
@EnableConfigurationProperties(AuthorizationServerProperties.class)
public class OAuth2AuthorizationServerConfiguration
extends AuthorizationServerConfigurerAdapter {
private static final Log logger = LogFactory
.getLog(OAuth2AuthorizationServerConfiguration.class);
private final BaseClientDetails details;
private final AuthenticationManager authenticationManager;
private final TokenStore tokenStore;
private final AccessTokenConverter tokenConverter;
private final AuthorizationServerProperties properties;
public OAuth2AuthorizationServerConfiguration(BaseClientDetails details,
AuthenticationManager authenticationManager,
ObjectProvider<TokenStore> tokenStore,
ObjectProvider<AccessTokenConverter> tokenConverter,
AuthorizationServerProperties properties) {
this.details = details;
this.authenticationManager = authenticationManager;
this.tokenStore = tokenStore.getIfAvailable();
this.tokenConverter = tokenConverter.getIfAvailable();
this.properties = properties;
}
@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.getAutoApproveScopes() != null) {
builder.autoApprove(
this.details.getAutoApproveScopes().toArray(new String[0]));
}
if (this.details.getAccessTokenValiditySeconds() != null) {
builder.accessTokenValiditySeconds(
this.details.getAccessTokenValiditySeconds());
}
if (this.details.getRefreshTokenValiditySeconds() != null) {
builder.refreshTokenValiditySeconds(
this.details.getRefreshTokenValiditySeconds());
}
if (this.details.getRegisteredRedirectUri() != null) {
builder.redirectUris(
this.details.getRegisteredRedirectUri().toArray(new String[0]));
}
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
if (this.tokenConverter != null) {
endpoints.accessTokenConverter(this.tokenConverter);
}
if (this.tokenStore != null) {
endpoints.tokenStore(this.tokenStore);
}
if (this.details.getAuthorizedGrantTypes().contains("password")) {
endpoints.authenticationManager(this.authenticationManager);
}
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
if (this.properties.getCheckTokenAccess() != null) {
security.checkTokenAccess(this.properties.getCheckTokenAccess());
}
if (this.properties.getTokenKeyAccess() != null) {
security.tokenKeyAccess(this.properties.getTokenKeyAccess());
}
if (this.properties.getRealm() != null) {
security.realm(this.properties.getRealm());
}
}
@Configuration
protected static class ClientDetailsLogger {
private final OAuth2ClientProperties credentials;
protected ClientDetailsLogger(OAuth2ClientProperties credentials) {
this.credentials = credentials;
}
@PostConstruct
public void init() {
String prefix = "security.oauth2.client";
boolean defaultSecret = this.credentials.isDefaultSecret();
logger.info(String.format(
"Initialized OAuth2 Client%n%n%s.client-id = %s%n"
+ "%s.client-secret = %s%n%n",
prefix, this.credentials.getClientId(), prefix,
defaultSecret ? this.credentials.getClientSecret() : "****"));
}
}
@Configuration
@ConditionalOnMissingBean(BaseClientDetails.class)
protected static class BaseClientDetailsConfiguration {
private final OAuth2ClientProperties client;
protected BaseClientDetailsConfiguration(OAuth2ClientProperties client) {
this.client = client;
}
@Bean
@ConfigurationProperties(prefix = "security.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;
}
}
}
/*
* Copyright 2012-2017 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
/**
* Enable OAuth2 Single Sign On (SSO). If there is an existing
* {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
* {@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.
*
* @author Dave Syer
* @since 1.3.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableOAuth2Client
@EnableConfigurationProperties(OAuth2SsoProperties.class)
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,
ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {
}
/*
* Copyright 2012-2016 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 org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Condition that checks for {@link EnableOAuth2Sso} on a
* {@link WebSecurityConfigurerAdapter}.
*
* @author Dave Syer
*/
class EnableOAuth2SsoCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String[] enablers = context.getBeanFactory()
.getBeanNamesForAnnotation(EnableOAuth2Sso.class);
ConditionMessage.Builder message = ConditionMessage
.forCondition("@EnableOAuth2Sso Condition");
for (String name : enablers) {
if (context.getBeanFactory().isTypeMatch(name,
WebSecurityConfigurerAdapter.class)) {
return ConditionOutcome.match(message
.found("@EnableOAuth2Sso annotation on WebSecurityConfigurerAdapter")
.items(name));
}
}
return ConditionOutcome.noMatch(message.didNotFind(
"@EnableOAuth2Sso annotation " + "on any WebSecurityConfigurerAdapter")
.atAll());
}
}
/*
* Copyright 2012-2017 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 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.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
/**
* Shared {@link AuthorizationCodeResourceDetails} configuration.
*
* @author Stephane Nicoll
*/
@Configuration
class OAuth2ProtectedResourceDetailsConfiguration {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
@Primary
public AuthorizationCodeResourceDetails oauth2RemoteResource() {
return new AuthorizationCodeResourceDetails();
}
}
/*
* Copyright 2012-2017 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 org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
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.ConditionalOnNotWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
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.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.util.StringUtils;
/**
* Configuration for OAuth2 Single Sign On REST operations.
*
* @author Dave Syer
* @author Madhura Bhave
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass(EnableOAuth2Client.class)
public class OAuth2RestOperationsConfiguration {
@Configuration
@Conditional(ClientCredentialsCondition.class)
protected static class SingletonScopedConfiguration {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
@Primary
public ClientCredentialsResourceDetails oauth2RemoteResource() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
return details;
}
@Bean
public DefaultOAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest());
}
}
@Configuration
@ConditionalOnBean(OAuth2ClientConfiguration.class)
@Conditional({ OAuth2ClientIdCondition.class, NoClientCredentialsCondition.class })
@Import(OAuth2ProtectedResourceDetailsConfiguration.class)
protected static class SessionScopedConfiguration {
@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter, SecurityProperties security) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(filter);
registration.setOrder(security.getFilter().getOrder() - 10);
return registration;
}
@Configuration
protected static class ClientContextConfiguration {
private final AccessTokenRequest accessTokenRequest;
public ClientContextConfiguration(
@Qualifier("accessTokenRequest") ObjectProvider<AccessTokenRequest> accessTokenRequest) {
this.accessTokenRequest = accessTokenRequest.getIfAvailable();
}
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public DefaultOAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(this.accessTokenRequest);
}
}
}
// When the authentication is per cookie but the stored token is an oauth2 one, we can
// pass that on to a client that wants to call downstream. We don't even need an
// OAuth2ClientContextFilter until we need to refresh the access token. To handle
// refresh tokens you need to @EnableOAuth2Client
@Configuration
@ConditionalOnMissingBean(OAuth2ClientConfiguration.class)
@Conditional({ OAuth2ClientIdCondition.class, NoClientCredentialsCondition.class })
@Import(OAuth2ProtectedResourceDetailsConfiguration.class)
protected static class RequestScopedConfiguration {
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public DefaultOAuth2ClientContext oauth2ClientContext() {
DefaultOAuth2ClientContext context = new DefaultOAuth2ClientContext(
new DefaultAccessTokenRequest());
Authentication principal = SecurityContextHolder.getContext()
.getAuthentication();
if (principal instanceof OAuth2Authentication) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
Object details = authentication.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauthsDetails = (OAuth2AuthenticationDetails) details;
String token = oauthsDetails.getTokenValue();
context.setAccessToken(new DefaultOAuth2AccessToken(token));
}
}
return context;
}
}
/**
* Condition to check if a {@code security.oauth2.client.client-id} is specified.
*/
static class OAuth2ClientIdCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
String clientId = context.getEnvironment()
.getProperty("security.oauth2.client.client-id");
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth Client ID");
if (StringUtils.hasLength(clientId)) {
return ConditionOutcome.match(message
.foundExactly("security.oauth2.client.client-id property"));
}
return ConditionOutcome.noMatch(message
.didNotFind("security.oauth2.client.client-id property").atAll());
}
}
/**
* Condition to check for no client credentials.
*/
static class NoClientCredentialsCondition extends NoneNestedConditions {
NoClientCredentialsCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@Conditional(ClientCredentialsCondition.class)
static class ClientCredentialsActivated {
}
}
/**
* Condition to check for client credentials.
*/
static class ClientCredentialsCondition extends AnyNestedCondition {
ClientCredentialsCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnProperty(prefix = "security.oauth2.client", name = "grant-type", havingValue = "client_credentials", matchIfMissing = false)
static class ClientCredentialsConfigured {
}
@ConditionalOnNotWebApplication
static class NoWebApplication {
}
}
}
/*
* Copyright 2012-2016 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.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Configuration for OAuth2 Single Sign On (SSO) when there is an existing
* {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
* {@code @EnableOAuth2Sso}. The user-provided configuration is enhanced by adding an
* authentication filter and an authentication entry point.
*
* @author Dave Syer
*/
@Configuration
@Conditional(EnableOAuth2SsoCondition.class)
public class OAuth2SsoCustomConfiguration
implements ImportAware, BeanPostProcessor, ApplicationContextAware {
private Class<?> configType;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.configType = ClassUtils.resolveClassName(importMetadata.getClassName(),
null);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (this.configType.isAssignableFrom(bean.getClass())
&& bean instanceof WebSecurityConfigurerAdapter) {
ProxyFactory factory = new ProxyFactory();
factory.setTarget(bean);
factory.addAdvice(new SsoSecurityAdapter(this.applicationContext));
bean = factory.getProxy();
}
return bean;
}
private static class SsoSecurityAdapter implements MethodInterceptor {
private SsoSecurityConfigurer configurer;
SsoSecurityAdapter(ApplicationContext applicationContext) {
this.configurer = new SsoSecurityConfigurer(applicationContext);
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (invocation.getMethod().getName().equals("init")) {
Method method = ReflectionUtils
.findMethod(WebSecurityConfigurerAdapter.class, "getHttp");
ReflectionUtils.makeAccessible(method);
HttpSecurity http = (HttpSecurity) ReflectionUtils.invokeMethod(method,
invocation.getThis());
this.configurer.configure(http);
}
return invocation.proceed();
}
}
}
/*
* Copyright 2012-2017 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 org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2SsoDefaultConfiguration.NeedsWebSecurityCondition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* 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.
*
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@Conditional(NeedsWebSecurityCondition.class)
public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter {
private final ApplicationContext applicationContext;
public OAuth2SsoDefaultConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
new SsoSecurityConfigurer(this.applicationContext).configure(http);
}
protected static class NeedsWebSecurityCondition extends EnableOAuth2SsoCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata));
}
}
}
/*
* Copyright 2012-2017 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 org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for OAuth2 Single Sign On (SSO).
*
* @author Dave Syer
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "security.oauth2.sso")
public class OAuth2SsoProperties {
public static final String DEFAULT_LOGIN_PATH = "/login";
/**
* Path to the login page, i.e. the one that triggers the redirect to the OAuth2
* Authorization Server.
*/
private String loginPath = DEFAULT_LOGIN_PATH;
public String getLoginPath() {
return this.loginPath;
}
public void setLoginPath(String loginPath) {
this.loginPath = loginPath;
}
}
/*
* Copyright 2012-2016 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.util.Collections;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoRestTemplateFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
/**
* Configurer for OAuth2 Single Sign On (SSO).
*
* @author Dave Syer
*/
class SsoSecurityConfigurer {
private ApplicationContext applicationContext;
SsoSecurityConfigurer(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void configure(HttpSecurity http) throws Exception {
OAuth2SsoProperties sso = this.applicationContext
.getBean(OAuth2SsoProperties.class);
// Delay the processing of the filter until we know the
// SessionAuthenticationStrategy is available:
http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
addAuthenticationEntryPoint(http, sso);
}
private void addAuthenticationEntryPoint(HttpSecurity http, OAuth2SsoProperties sso)
throws Exception {
ExceptionHandlingConfigurer<HttpSecurity> exceptions = http.exceptionHandling();
ContentNegotiationStrategy contentNegotiationStrategy = http
.getSharedObject(ContentNegotiationStrategy.class);
if (contentNegotiationStrategy == null) {
contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
}
MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(
contentNegotiationStrategy, MediaType.APPLICATION_XHTML_XML,
new MediaType("image", "*"), MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
exceptions.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint(sso.getLoginPath()),
preferredMatcher);
// When multiple entry points are provided the default is the first one
exceptions.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
}
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
OAuth2SsoProperties sso) {
OAuth2RestOperations restTemplate = this.applicationContext
.getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
ResourceServerTokenServices tokenServices = this.applicationContext
.getBean(ResourceServerTokenServices.class);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
sso.getLoginPath());
filter.setRestTemplate(restTemplate);
filter.setTokenServices(tokenServices);
filter.setApplicationEventPublisher(this.applicationContext);
return filter;
}
private static class OAuth2ClientAuthenticationConfigurer
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private OAuth2ClientAuthenticationProcessingFilter filter;
OAuth2ClientAuthenticationConfigurer(
OAuth2ClientAuthenticationProcessingFilter filter) {
this.filter = filter;
}
@Override
public void configure(HttpSecurity builder) throws Exception {
OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
ssoFilter.setSessionAuthenticationStrategy(
builder.getSharedObject(SessionAuthenticationStrategy.class));
builder.addFilterAfter(ssoFilter,
AbstractPreAuthenticatedProcessingFilter.class);
}
}
}
/*
* Copyright 2012-2017 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.method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
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.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationTrustResolver;
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}).
*
* @author Greg Turnquist
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@ConditionalOnClass({ OAuth2AccessToken.class })
@ConditionalOnBean(GlobalMethodSecurityConfiguration.class)
public class OAuth2MethodSecurityConfiguration
implements BeanFactoryPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
OAuth2ExpressionHandlerInjectionPostProcessor processor = new OAuth2ExpressionHandlerInjectionPostProcessor(
this.applicationContext);
beanFactory.addBeanPostProcessor(processor);
}
private static class OAuth2ExpressionHandlerInjectionPostProcessor
implements BeanPostProcessor {
private ApplicationContext applicationContext;
OAuth2ExpressionHandlerInjectionPostProcessor(
ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@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
&& !(bean instanceof OAuth2MethodSecurityExpressionHandler)) {
return getExpressionHandler(
(DefaultMethodSecurityExpressionHandler) bean);
}
return bean;
}
private OAuth2MethodSecurityExpressionHandler getExpressionHandler(
DefaultMethodSecurityExpressionHandler bean) {
OAuth2MethodSecurityExpressionHandler handler = new OAuth2MethodSecurityExpressionHandler();
handler.setApplicationContext(this.applicationContext);
AuthenticationTrustResolver trustResolver = findInContext(
AuthenticationTrustResolver.class);
if (trustResolver != null) {
handler.setTrustResolver(trustResolver);
}
handler.setExpressionParser(bean.getExpressionParser());
return handler;
}
private <T> T findInContext(Class<T> type) {
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.applicationContext, type).length == 1) {
return this.applicationContext.getBean(type);
}
return null;
}
}
}
/*
* Copyright 2012-2017 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.List;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
/**
* Strategy used by {@link UserInfoTokenServices} to extract authorities from the resource
* server's response.
*
* @author Dave Syer
* @since 1.3.0
*/
@FunctionalInterface
public interface AuthoritiesExtractor {
/**
* Extract the authorities from the resource server's response.
* @param map the response
* @return the extracted authorities
*/
List<GrantedAuthority> extractAuthorities(Map<String, Object> map);
}
/*
* Copyright 2012-2017 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.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.AcceptJsonRequestEnhancer;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.AcceptJsonRequestInterceptor;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.util.CollectionUtils;
/**
* Factory used to create the {@link OAuth2RestTemplate} used for extracting user info
* during authentication if none is available.
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.5.0
*/
public class DefaultUserInfoRestTemplateFactory implements UserInfoRestTemplateFactory {
private static final AuthorizationCodeResourceDetails DEFAULT_RESOURCE_DETAILS;
static {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setClientId("<N/A>");
details.setUserAuthorizationUri("Not a URI because there is no client");
details.setAccessTokenUri("Not a URI because there is no client");
DEFAULT_RESOURCE_DETAILS = details;
}
private final List<UserInfoRestTemplateCustomizer> customizers;
private final OAuth2ProtectedResourceDetails details;
private final OAuth2ClientContext oauth2ClientContext;
private OAuth2RestTemplate oauth2RestTemplate;
public DefaultUserInfoRestTemplateFactory(
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
ObjectProvider<OAuth2ProtectedResourceDetails> details,
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
this.customizers = customizers.getIfAvailable();
this.details = details.getIfAvailable();
this.oauth2ClientContext = oauth2ClientContext.getIfAvailable();
}
@Override
public OAuth2RestTemplate getUserInfoRestTemplate() {
if (this.oauth2RestTemplate == null) {
this.oauth2RestTemplate = createOAuth2RestTemplate(
this.details == null ? DEFAULT_RESOURCE_DETAILS : this.details);
this.oauth2RestTemplate.getInterceptors()
.add(new AcceptJsonRequestInterceptor());
AuthorizationCodeAccessTokenProvider accessTokenProvider = new AuthorizationCodeAccessTokenProvider();
accessTokenProvider.setTokenRequestEnhancer(new AcceptJsonRequestEnhancer());
this.oauth2RestTemplate.setAccessTokenProvider(accessTokenProvider);
if (!CollectionUtils.isEmpty(this.customizers)) {
AnnotationAwareOrderComparator.sort(this.customizers);
for (UserInfoRestTemplateCustomizer customizer : this.customizers) {
customizer.customize(this.oauth2RestTemplate);
}
}
}
return this.oauth2RestTemplate;
}
private OAuth2RestTemplate createOAuth2RestTemplate(
OAuth2ProtectedResourceDetails details) {
if (this.oauth2ClientContext == null) {
return new OAuth2RestTemplate(details);
}
return new OAuth2RestTemplate(details, this.oauth2ClientContext);
}
}
/*
* Copyright 2012-2017 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.List;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Default implementation of {@link AuthoritiesExtractor}. Extracts the authorities from
* the map with the key {@code authorities}. If no such value exists, a single
* {@code ROLE_USER} authority is returned.
*
* @author Dave Syer
* @since 1.3.0
*/
public class FixedAuthoritiesExtractor implements AuthoritiesExtractor {
private static final String AUTHORITIES = "authorities";
private static final String[] AUTHORITY_KEYS = { "authority", "role", "value" };
@Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
String authorities = "ROLE_USER";
if (map.containsKey(AUTHORITIES)) {
authorities = asAuthorities(map.get(AUTHORITIES));
}
return AuthorityUtils.commaSeparatedStringToAuthorityList(authorities);
}
private String asAuthorities(Object object) {
List<Object> authorities = new ArrayList<>();
if (object instanceof Collection) {
Collection<?> collection = (Collection<?>) object;
object = collection.toArray(new Object[0]);
}
if (ObjectUtils.isArray(object)) {
Object[] array = (Object[]) object;
for (Object value : array) {
if (value instanceof String) {
authorities.add(value);
}
else if (value instanceof Map) {
authorities.add(asAuthority((Map<?, ?>) value));
}
else {
authorities.add(value);
}
}
return StringUtils.collectionToCommaDelimitedString(authorities);
}
return object.toString();
}
private Object asAuthority(Map<?, ?> map) {
if (map.size() == 1) {
return map.values().iterator().next();
}
for (String key : AUTHORITY_KEYS) {
if (map.containsKey(key)) {
return map.get(key);
}
}
return map;
}
}
/*
* Copyright 2012-2016 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.Map;
/**
* Default implementation of {@link PrincipalExtractor}. Extracts the principal from the
* map with well known keys.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class FixedPrincipalExtractor implements PrincipalExtractor {
private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
"userid", "user_id", "login", "id", "name" };
@Override
public Object extractPrincipal(Map<String, Object> map) {
for (String key : PRINCIPAL_KEYS) {
if (map.containsKey(key)) {
return map.get(key);
}
}
return null;
}
}
/*
* Copyright 2012-2017 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.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
*/
@FunctionalInterface
public interface JwtAccessTokenConverterConfigurer {
/**
* Configure the {@link JwtAccessTokenConverter}.
* @param converter the converter to configure
*/
void configure(JwtAccessTokenConverter converter);
}
/*
* Copyright 2012-2017 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.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.web.client.RestTemplate;
/**
* Callback for customizing the {@link RestTemplate} that is used to fetch the keys used
* by {@link JwtAccessTokenConverter}.
*
* @author Eddú Meléndez
* @since 1.5.2
* @see JwtAccessTokenConverter#setSigningKey(String)
* @see JwtAccessTokenConverter#setVerifierKey(String)
*/
@FunctionalInterface
public interface JwtAccessTokenConverterRestTemplateCustomizer {
/**
* Customize the {@code template} before it is initialized.
* @param template the rest template
*/
void customize(RestTemplate template);
}
/*
* Copyright 2012-2017 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.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
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.SpringBootCondition;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerConfiguration.ResourceServerCondition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
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.ConfigurableEnvironment;
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
* @author Madhura Bhave
* @since 1.3.0
*/
@Configuration
@Conditional(ResourceServerCondition.class)
@ConditionalOnClass({ EnableResourceServer.class, SecurityProperties.class })
@ConditionalOnWebApplication
@ConditionalOnBean(ResourceServerConfiguration.class)
@Import(ResourceServerTokenServicesConfiguration.class)
public class OAuth2ResourceServerConfiguration {
private final ResourceServerProperties resource;
public OAuth2ResourceServerConfiguration(ResourceServerProperties resource) {
this.resource = resource;
}
@Bean
@ConditionalOnMissingBean(ResourceServerConfigurer.class)
public ResourceServerConfigurer resourceServer() {
return new ResourceSecurityConfigurer(this.resource);
}
protected static class ResourceSecurityConfigurer
extends ResourceServerConfigurerAdapter {
private ResourceServerProperties resource;
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();
}
}
protected static class ResourceServerCondition extends SpringBootCondition
implements ConfigurationCondition {
private static final Bindable<Map<String, Object>> STRING_OBJECT_MAP = Bindable
.mapOf(String.class, Object.class);
private static final String AUTHORIZATION_ANNOTATION = "org.springframework."
+ "security.oauth2.config.annotation.web.configuration."
+ "AuthorizationServerEndpointsConfiguration";
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("OAuth ResourceServer Condition");
Environment environment = context.getEnvironment();
if (!(environment instanceof ConfigurableEnvironment)) {
return ConditionOutcome
.noMatch(message.didNotFind("A ConfigurableEnvironment").atAll());
}
if (hasOAuthClientId(environment)) {
return ConditionOutcome.match(message.foundExactly("client-id property"));
}
Binder binder = Binder.get(environment);
String prefix = "security.oauth2.resource.";
if (binder.bind(prefix + "jwt", STRING_OBJECT_MAP).isBound()) {
return ConditionOutcome
.match(message.foundExactly("JWT resource configuration"));
}
if (binder.bind(prefix + "jwk", STRING_OBJECT_MAP).isBound()) {
return ConditionOutcome
.match(message.foundExactly("JWK resource configuration"));
}
if (StringUtils.hasText(environment.getProperty(prefix + "user-info-uri"))) {
return ConditionOutcome
.match(message.foundExactly("user-info-uri property"));
}
if (StringUtils.hasText(environment.getProperty(prefix + "token-info-uri"))) {
return ConditionOutcome
.match(message.foundExactly("token-info-uri property"));
}
if (ClassUtils.isPresent(AUTHORIZATION_ANNOTATION, null)) {
if (AuthorizationServerEndpointsConfigurationBeanCondition
.matches(context)) {
return ConditionOutcome.match(
message.found("class").items(AUTHORIZATION_ANNOTATION));
}
}
return ConditionOutcome.noMatch(
message.didNotFind("client ID, JWT resource or authorization server")
.atAll());
}
private boolean hasOAuthClientId(Environment environment) {
return StringUtils.hasLength(
environment.getProperty("security.oauth2.client.client-id"));
}
}
@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 false;
}
}
}
/*
* Copyright 2012-2017 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.Map;
/**
* Strategy used by {@link UserInfoTokenServices} to extract the principal from the
* resource server's response.
*
* @author Phillip Webb
* @since 1.4.0
*/
@FunctionalInterface
public interface PrincipalExtractor {
/**
* Extract the principal that should be used for the token.
* @param map the source map
* @return the extracted principal or {@code null}
*/
Object extractPrincipal(Map<String, Object> map);
}
/*
* Copyright 2012-2017 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 javax.annotation.PostConstruct;
import com.fasterxml.jackson.annotation.JsonIgnore;
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.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.util.StringUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
/**
* Configuration properties for OAuth2 Resources.
*
* @author Dave Syer
* @author Madhura Bhave
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "security.oauth2.resource")
public class ResourceServerProperties implements 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;
/**
* The token type to send when using the userInfoUri.
*/
private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
private Jwt jwt = new Jwt();
private Jwk jwk = new Jwk();
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 String getTokenType() {
return this.tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public Jwt getJwt() {
return this.jwt;
}
public void setJwt(Jwt jwt) {
this.jwt = jwt;
}
public Jwk getJwk() {
return this.jwk;
}
public void setJwk(Jwk jwk) {
this.jwk = jwk;
}
public String getClientId() {
return this.clientId;
}
public String getClientSecret() {
return this.clientSecret;
}
@PostConstruct
public void validate() {
if (countBeans(AuthorizationServerEndpointsConfiguration.class) > 0) {
// If we are an authorization server we don't need remote resource token
// services
return;
}
if (countBeans(ResourceServerTokenServicesConfiguration.class) == 0) {
// If we are not a resource server or an SSO client we don't need remote
// resource token services
return;
}
if (!StringUtils.hasText(this.clientId)) {
return;
}
try {
doValidate();
}
catch (BindException ex) {
throw new IllegalStateException(ex);
}
}
private int countBeans(Class<?> type) {
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, type,
true, false).length;
}
private void doValidate() throws BindException {
BindingResult errors = new BeanPropertyBindingResult(this,
"resourceServerProperties");
boolean jwtConfigPresent = StringUtils.hasText(this.jwt.getKeyUri())
|| StringUtils.hasText(this.jwt.getKeyValue());
boolean jwkConfigPresent = StringUtils.hasText(this.jwk.getKeySetUri());
if (jwtConfigPresent && jwkConfigPresent) {
errors.reject("ambiguous.keyUri",
"Only one of jwt.keyUri (or jwt.keyValue) and jwk.keySetUri should"
+ " be configured.");
}
if (!jwtConfigPresent && !jwkConfigPresent) {
if (!StringUtils.hasText(this.userInfoUri)
&& !StringUtils.hasText(this.tokenInfoUri)) {
errors.rejectValue("tokenInfoUri", "missing.tokenInfoUri",
"Missing tokenInfoUri and userInfoUri and there is no "
+ "JWT verifier key");
}
if (StringUtils.hasText(this.tokenInfoUri) && isPreferTokenInfo()) {
if (!StringUtils.hasText(this.clientSecret)) {
errors.rejectValue("clientSecret", "missing.clientSecret",
"Missing client secret");
}
}
}
if (errors.hasErrors()) {
throw new BindException(errors);
}
}
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() {
return this.keyUri;
}
}
public class Jwk {
/**
* The URI to get verification keys to verify the JWT token. This can be set when
* the authorization server returns a set of verification keys.
*/
private String keySetUri;
public String getKeySetUri() {
return this.keySetUri;
}
public void setKeySetUri(String keySetUri) {
this.keySetUri = keySetUri;
}
}
}
/*
* Copyright 2012-2016 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.List;
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;
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;
/**
* {@link ResourceServerTokenServices} backed by Spring Social.
*
* @author Dave Syer
* @since 1.3.0
*/
public class SpringSocialTokenServices implements ResourceServerTokenServices {
private final OAuth2ConnectionFactory<?> connectionFactory;
private final String clientId;
public SpringSocialTokenServices(OAuth2ConnectionFactory<?> connectionFactory,
String clientId) {
this.connectionFactory = connectionFactory;
this.clientId = clientId;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
AccessGrant accessGrant = new AccessGrant(accessToken);
Connection<?> connection = this.connectionFactory.createConnection(accessGrant);
UserProfile user = connection.fetchUserProfile();
return extractAuthentication(user);
}
private OAuth2Authentication extractAuthentication(UserProfile user) {
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
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}
/*
* Copyright 2012-2017 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.security.oauth2.client.OAuth2RestTemplate;
/**
* Callback for customizing the rest template used to fetch user details if authentication
* is done via OAuth2 access tokens. The default should be fine for most providers, but
* occasionally you might need to add additional interceptors, or change the request
* authenticator (which is how the token gets attached to outgoing requests). The rest
* template that is being customized here is <i>only</i> used internally to carry out
* authentication (in the SSO or Resource Server use cases).
*
* @author Dave Syer
* @since 1.3.0
*/
@FunctionalInterface
public interface UserInfoRestTemplateCustomizer {
/**
* Customize the rest template before it is initialized.
* @param template the rest template
*/
void customize(OAuth2RestTemplate template);
}
/*
* Copyright 2012-2017 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.security.oauth2.client.OAuth2RestTemplate;
/**
* Factory used to create the {@link OAuth2RestTemplate} used for extracting user info
* during authentication if none is available.
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.4.0
*/
@FunctionalInterface
public interface UserInfoRestTemplateFactory {
/**
* Return the {@link OAuth2RestTemplate} used for extracting user info during
* authentication if none is available.
* @return the OAuth2RestTemplate used for authentication
*/
OAuth2RestTemplate getUserInfoRestTemplate();
}
/*
* Copyright 2012-2017 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.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.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;
import org.springframework.util.Assert;
/**
* {@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 final String userInfoEndpointUrl;
private final String clientId;
private OAuth2RestOperations restTemplate;
private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
private PrincipalExtractor principalExtractor = new FixedPrincipalExtractor();
public UserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
this.userInfoEndpointUrl = userInfoEndpointUrl;
this.clientId = clientId;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
Assert.notNull(authoritiesExtractor, "AuthoritiesExtractor must not be null");
this.authoritiesExtractor = authoritiesExtractor;
}
public void setPrincipalExtractor(PrincipalExtractor principalExtractor) {
Assert.notNull(principalExtractor, "PrincipalExtractor must not be null");
this.principalExtractor = principalExtractor;
}
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
if (map.containsKey("error")) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("userinfo returned error: " + map.get("error"));
}
throw new InvalidTokenException(accessToken);
}
return extractAuthentication(map);
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
List<GrantedAuthority> authorities = this.authoritiesExtractor
.extractAuthorities(map);
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);
}
/**
* Return the principal that should be used for the token. The default implementation
* delegates to the {@link PrincipalExtractor}.
* @param map the source map
* @return the principal or {@literal "unknown"}
*/
protected Object getPrincipal(Map<String, Object> map) {
Object principal = this.principalExtractor.extractPrincipal(map);
return (principal == null ? "unknown" : principal);
}
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
@SuppressWarnings({ "unchecked" })
private Map<String, Object> getMap(String path, String accessToken) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Getting user info from: " + path);
}
try {
OAuth2RestOperations restTemplate = this.restTemplate;
if (restTemplate == null) {
BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
resource.setClientId(this.clientId);
restTemplate = new OAuth2RestTemplate(resource);
}
OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
.getAccessToken();
if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
accessToken);
token.setTokenType(this.tokenType);
restTemplate.getOAuth2ClientContext().setAccessToken(token);
}
return restTemplate.getForEntity(path, Map.class).getBody();
}
catch (Exception ex) {
this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
+ ex.getMessage());
return Collections.<String, Object>singletonMap("error",
"Could not fetch user details");
}
}
}
......@@ -895,25 +895,6 @@
"level": "error"
}
},
{
"name": "security.oauth2.resource.filter-order",
"type": "java.lang.Integer",
"description": "The order of the filter chain used to authenticate tokens. Default puts it after\n the actuator endpoints and before the default HTTP basic filter chain (catchall).",
"defaultValue": 0,
"deprecation": {
"reason": "The security auto-configuration does no longer provide several security configurations. Their ordering is now explicit in your own security configuration.",
"level": "error"
}
},
{
"name": "security.oauth2.sso.filter-order",
"type": "java.lang.Integer",
"description": "Filter order to apply if not providing an explicit WebSecurityConfigurerAdapter (in\n which case the order can be provided there instead).",
"deprecation": {
"reason": "The security auto-configuration does no longer provide several security configurations. Their ordering is now explicit in your own security configuration.",
"level": "error"
}
},
{
"name": "security.require-ssl",
"type": "java.lang.Boolean",
......
......@@ -98,7 +98,6 @@ org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
......
/*
* Copyright 2012-2017 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 org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.autoconfigure.web.servlet.MockServletWebServerFactory;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.util.TestPropertyValues;
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.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2RestOperationsConfiguration}.
*
* @author Madhura Bhave
*/
public class OAuth2RestOperationsConfigurationTests {
private ConfigurableApplicationContext context;
private ConfigurableEnvironment environment = new StandardEnvironment();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void clientCredentialsWithClientId() throws Exception {
TestPropertyValues.of("security.oauth2.client.client-id=acme")
.applyTo(this.environment);
initializeContext(OAuth2RestOperationsConfiguration.class, true);
assertThat(this.context.getBean(OAuth2RestOperationsConfiguration.class))
.isNotNull();
assertThat(this.context.getBean(ClientCredentialsResourceDetails.class))
.isNotNull();
}
@Test
public void clientCredentialsWithNoClientId() throws Exception {
initializeContext(OAuth2RestOperationsConfiguration.class, true);
assertThat(this.context.getBean(OAuth2RestOperationsConfiguration.class))
.isNotNull();
assertThat(this.context.getBean(ClientCredentialsResourceDetails.class))
.isNotNull();
}
@Test
public void requestScopedWithClientId() throws Exception {
TestPropertyValues.of("security.oauth2.client.client-id=acme")
.applyTo(this.environment);
initializeContext(ConfigForRequestScopedConfiguration.class, false);
assertThat(this.context.containsBean("oauth2ClientContext")).isTrue();
}
@Test
public void requestScopedWithNoClientId() throws Exception {
initializeContext(ConfigForRequestScopedConfiguration.class, false);
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(DefaultOAuth2ClientContext.class);
}
@Test
public void sessionScopedWithClientId() throws Exception {
TestPropertyValues.of("security.oauth2.client.client-id=acme")
.applyTo(this.environment);
initializeContext(ConfigForSessionScopedConfiguration.class, false);
assertThat(this.context.containsBean("oauth2ClientContext")).isTrue();
}
@Test
public void sessionScopedWithNoClientId() throws Exception {
initializeContext(ConfigForSessionScopedConfiguration.class, false);
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(DefaultOAuth2ClientContext.class);
}
private void initializeContext(Class<?> configuration, boolean clientCredentials) {
this.context = new SpringApplicationBuilder(configuration)
.environment(this.environment).web(clientCredentials
? WebApplicationType.NONE : WebApplicationType.SERVLET)
.run();
}
@Configuration
@Import({ OAuth2RestOperationsConfiguration.class })
protected static class WebApplicationConfiguration {
@Bean
public MockServletWebServerFactory webServerFactory() {
return new MockServletWebServerFactory();
}
}
@Configuration
@Import({ SecurityProperties.class, OAuth2ClientConfiguration.class,
OAuth2RestOperationsConfiguration.class })
protected static class ConfigForSessionScopedConfiguration
extends WebApplicationConfiguration {
}
@Configuration
protected static class ConfigForRequestScopedConfiguration
extends WebApplicationConfiguration {
}
}
/*
* Copyright 2012-2017 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.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link FixedAuthoritiesExtractor}.
*
* @author Dave Syer
*/
public class FixedAuthoritiesExtractorTests {
private FixedAuthoritiesExtractor extractor = new FixedAuthoritiesExtractor();
private Map<String, Object> map = new LinkedHashMap<>();
@Test
public void authorities() {
this.map.put("authorities", "ROLE_ADMIN");
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_ADMIN]");
}
@Test
public void authoritiesCommaSeparated() {
this.map.put("authorities", "ROLE_USER,ROLE_ADMIN");
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_USER, ROLE_ADMIN]");
}
@Test
public void authoritiesArray() {
this.map.put("authorities", new String[] { "ROLE_USER", "ROLE_ADMIN" });
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_USER, ROLE_ADMIN]");
}
@Test
public void authoritiesList() {
this.map.put("authorities", Arrays.asList("ROLE_USER", "ROLE_ADMIN"));
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_USER, ROLE_ADMIN]");
}
@Test
public void authoritiesAsListOfMaps() {
this.map.put("authorities",
Arrays.asList(Collections.singletonMap("authority", "ROLE_ADMIN")));
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_ADMIN]");
}
@Test
public void authoritiesAsListOfMapsWithStandardKey() {
Map<String, String> map = new LinkedHashMap<>();
map.put("role", "ROLE_ADMIN");
map.put("extra", "value");
this.map.put("authorities", Arrays.asList(map));
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_ADMIN]");
}
@Test
public void authoritiesAsListOfMapsWithNonStandardKey() {
this.map.put("authorities",
Arrays.asList(Collections.singletonMap("any", "ROLE_ADMIN")));
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[ROLE_ADMIN]");
}
@Test
public void authoritiesAsListOfMapsWithMultipleNonStandardKeys() {
Map<String, String> map = new HashMap<>();
map.put("any", "ROLE_ADMIN");
map.put("foo", "bar");
this.map.put("authorities", Arrays.asList(map));
assertThat(this.extractor.extractAuthorities(this.map).toString())
.isEqualTo("[{foo=bar, any=ROLE_ADMIN}]");
}
}
/*
* Copyright 2012-2017 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.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2ResourceServerConfiguration} when there are multiple
* {@link ResourceServerConfiguration} beans.
*
* @author Dave Syer
*/
public class MultipleResourceServerConfigurationTests {
private AnnotationConfigWebApplicationContext context;
@After
public void close() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void orderIsUnchangedWhenThereAreMultipleResourceServerConfigurations() {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(DoubleResourceConfiguration.class);
TestPropertyValues.of("security.oauth2.resource.tokenInfoUri:http://example.com",
"security.oauth2.client.clientId=acme").applyTo(this.context);
this.context.refresh();
assertThat(this.context
.getBean("adminResources", ResourceServerConfiguration.class).getOrder())
.isEqualTo(3);
assertThat(this.context
.getBean("otherResources", ResourceServerConfiguration.class).getOrder())
.isEqualTo(4);
}
@ImportAutoConfiguration({ OAuth2AutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
@EnableWebSecurity
@Configuration
protected static class DoubleResourceConfiguration {
@Bean
protected ResourceServerConfiguration adminResources() {
ResourceServerConfiguration resource = new ResourceServerConfiguration() {
// Switch off the Spring Boot @Autowired configurers
@Override
public void setConfigurers(List<ResourceServerConfigurer> configurers) {
super.setConfigurers(configurers);
}
};
resource.setOrder(3);
return resource;
}
@Bean
protected ResourceServerConfiguration otherResources() {
ResourceServerConfiguration resource = new ResourceServerConfiguration() {
// Switch off the Spring Boot @Autowired configurers
@Override
public void setConfigurers(List<ResourceServerConfigurer> configurers) {
super.setConfigurers(configurers);
}
};
resource.setOrder(4);
return resource;
}
}
}
/*
* Copyright 2012-2017 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.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link ResourceServerProperties}.
*
* @author Dave Syer
* @author Vedran Pavic
* @author Madhura Bhave
*/
public class ResourceServerPropertiesTests {
private ResourceServerProperties properties = new ResourceServerProperties("client",
"secret");
private Errors errors = mock(Errors.class);
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
@SuppressWarnings("unchecked")
public void json() throws Exception {
this.properties.getJwt().setKeyUri("http://example.com/token_key");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(this.properties);
Map<String, Object> value = mapper.readValue(json, Map.class);
Map<String, Object> jwt = (Map<String, Object>) value.get("jwt");
assertThat(jwt.get("keyUri")).isNotNull();
}
@Test
public void validateWhenClientIdNullShouldNotFail() throws Exception {
this.properties = new ResourceServerProperties(null, "secret");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenBothJwtAndJwkKeyUrisPresentShouldFail() throws Exception {
this.properties.getJwk().setKeySetUri("http://my-auth-server/token_keys");
this.properties.getJwt().setKeyUri("http://my-auth-server/token_key");
setListableBeanFactory();
this.thrown.expect(IllegalStateException.class);
this.thrown.expect(getMatcher("Only one of jwt.keyUri (or jwt.keyValue) "
+ "and jwk.keySetUri should be configured.", null));
this.properties.validate();
}
@Test
public void validateWhenBothJwtKeyValueAndJwkKeyUriPresentShouldFail()
throws Exception {
this.properties.getJwk().setKeySetUri("http://my-auth-server/token_keys");
this.properties.getJwt().setKeyValue("my-key");
setListableBeanFactory();
this.thrown.expect(IllegalStateException.class);
this.thrown.expect(getMatcher("Only one of jwt.keyUri (or jwt.keyValue) "
+ "and jwk.keySetUri should be configured.", null));
this.properties.validate();
}
@Test
public void validateWhenJwkKeySetUriProvidedShouldSucceed() throws Exception {
this.properties.getJwk().setKeySetUri("http://my-auth-server/token_keys");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenKeyValuePresentShouldSucceed() throws Exception {
this.properties.getJwt().setKeyValue("my-key");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenKeysUriOrValuePresentAndUserInfoAbsentShouldNotFail()
throws Exception {
this.properties = new ResourceServerProperties("client", "");
this.properties.getJwk().setKeySetUri("http://my-auth-server/token_keys");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenKeyConfigAbsentAndInfoUrisNotConfiguredShouldFail()
throws Exception {
setListableBeanFactory();
this.thrown.expect(IllegalStateException.class);
this.thrown.expect(getMatcher("Missing tokenInfoUri and userInfoUri and there"
+ " is no JWT verifier key", "tokenInfoUri"));
this.properties.validate();
}
@Test
public void validateWhenTokenUriConfiguredShouldNotFail() throws Exception {
this.properties.setTokenInfoUri("http://my-auth-server/userinfo");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenUserInfoUriConfiguredShouldNotFail() throws Exception {
this.properties.setUserInfoUri("http://my-auth-server/userinfo");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenTokenUriPreferredAndClientSecretAbsentShouldFail()
throws Exception {
this.properties = new ResourceServerProperties("client", "");
this.properties.setTokenInfoUri("http://my-auth-server/check_token");
this.properties.setUserInfoUri("http://my-auth-server/userinfo");
setListableBeanFactory();
this.thrown.expect(IllegalStateException.class);
this.thrown.expect(getMatcher("Missing client secret", "clientSecret"));
this.properties.validate();
}
@Test
public void validateWhenTokenUriAbsentAndClientSecretAbsentShouldNotFail()
throws Exception {
this.properties = new ResourceServerProperties("client", "");
this.properties.setUserInfoUri("http://my-auth-server/userinfo");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
@Test
public void validateWhenTokenUriNotPreferredAndClientSecretAbsentShouldNotFail()
throws Exception {
this.properties = new ResourceServerProperties("client", "");
this.properties.setPreferTokenInfo(false);
this.properties.setTokenInfoUri("http://my-auth-server/check_token");
this.properties.setUserInfoUri("http://my-auth-server/userinfo");
setListableBeanFactory();
this.properties.validate();
verifyZeroInteractions(this.errors);
}
private void setListableBeanFactory() {
ListableBeanFactory beanFactory = new StaticWebApplicationContext() {
@Override
public String[] getBeanNamesForType(Class<?> type,
boolean includeNonSingletons, boolean allowEagerInit) {
if (type.isAssignableFrom(
ResourceServerTokenServicesConfiguration.class)) {
return new String[] { "ResourceServerTokenServicesConfiguration" };
}
return new String[0];
}
};
this.properties.setBeanFactory(beanFactory);
}
private BaseMatcher<BindException> getMatcher(String message, String field) {
return new BaseMatcher<BindException>() {
@Override
public void describeTo(Description description) {
}
@Override
public boolean matches(Object item) {
BindException ex = (BindException) ((Exception) item).getCause();
ObjectError error = ex.getAllErrors().get(0);
boolean messageMatches = message.equals(error.getDefaultMessage());
if (field == null) {
return messageMatches;
}
String fieldErrors = ((FieldError) error).getField();
return messageMatches && fieldErrors.equals(field);
}
};
}
}
/*
* Copyright 2012-2017 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.Date;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UserInfoTokenServices}.
*
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"security.oauth2.resource.userInfoUri:http://example.com",
"security.oauth2.client.clientId=foo" })
@DirtiesContext
public class UserInfoTokenServicesRefreshTokenTests {
@Rule
public ExpectedException expected = ExpectedException.none();
@LocalServerPort
private int port;
private UserInfoTokenServices services;
@Before
public void init() {
this.services = new UserInfoTokenServices(
"http://localhost:" + this.port + "/user", "foo");
}
@Test
public void sunnyDay() {
assertThat(this.services.loadAuthentication("FOO").getName()).isEqualTo("me");
}
@Test
public void withRestTemplate() {
OAuth2ProtectedResourceDetails resource = new AuthorizationCodeResourceDetails();
OAuth2ClientContext context = new DefaultOAuth2ClientContext();
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken("FOO");
token.setRefreshToken(new DefaultExpiringOAuth2RefreshToken("BAR", new Date(0L)));
context.setAccessToken(token);
this.services.setRestTemplate(new OAuth2RestTemplate(resource, context));
assertThat(this.services.loadAuthentication("FOO").getName()).isEqualTo("me");
assertThat(context.getAccessToken().getValue()).isEqualTo("FOO");
// The refresh token is still intact
assertThat(context.getAccessToken().getRefreshToken())
.isEqualTo(token.getRefreshToken());
}
@Test
public void withRestTemplateChangesState() {
OAuth2ProtectedResourceDetails resource = new AuthorizationCodeResourceDetails();
OAuth2ClientContext context = new DefaultOAuth2ClientContext();
context.setAccessToken(new DefaultOAuth2AccessToken("FOO"));
this.services.setRestTemplate(new OAuth2RestTemplate(resource, context));
assertThat(this.services.loadAuthentication("BAR").getName()).isEqualTo("me");
assertThat(context.getAccessToken().getValue()).isEqualTo("BAR");
}
@Configuration
@Import({ ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
@RestController
protected static class Application {
@RequestMapping("/user")
public User user(@RequestHeader("Authorization") String authorization) {
if (authorization.endsWith("EXPIRED")) {
throw new InvalidTokenException("Expired");
}
return new User();
}
@ExceptionHandler(InvalidTokenException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public void expired() {
}
}
public static class User {
private String userid = "me";
public String getUserid() {
return this.userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
}
}
/*
* Copyright 2012-2017 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.LinkedHashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
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.client.resource.UserRedirectRequiredException;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link UserInfoTokenServices}.
*
* @author Dave Syer
*/
public class UserInfoTokenServicesTests {
@Rule
public ExpectedException expected = ExpectedException.none();
private UserInfoTokenServices services = new UserInfoTokenServices(
"http://example.com", "foo");
private BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
private OAuth2RestOperations template = mock(OAuth2RestOperations.class);
private Map<String, Object> map = new LinkedHashMap<>();
@Before
public void init() {
this.resource.setClientId("foo");
given(this.template.getForEntity(any(String.class), eq(Map.class)))
.willReturn(new ResponseEntity<>(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() {
this.services.setRestTemplate(this.template);
assertThat(this.services.loadAuthentication("FOO").getName())
.isEqualTo("unknown");
}
@Test
public void badToken() {
this.services.setRestTemplate(this.template);
given(this.template.getForEntity(any(String.class), eq(Map.class)))
.willThrow(new UserRedirectRequiredException("foo:bar",
Collections.<String, String>emptyMap()));
this.expected.expect(InvalidTokenException.class);
assertThat(this.services.loadAuthentication("FOO").getName())
.isEqualTo("unknown");
}
@Test
public void userId() {
this.map.put("userid", "spencer");
this.services.setRestTemplate(this.template);
assertThat(this.services.loadAuthentication("FOO").getName())
.isEqualTo("spencer");
}
}
/*
* Copyright 2012-2016 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.sso;
import javax.servlet.Filter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
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;
/**
* Tests for {@link OAuth2AutoConfiguration} with basic configuration.
*
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest
@TestPropertySource(properties = { "security.oauth2.client.clientId=client",
"security.oauth2.client.clientSecret=secret",
"security.oauth2.client.userAuthorizationUri=http://example.com/oauth/authorize",
"security.oauth2.client.accessTokenUri=http://example.com/oauth/token",
"security.oauth2.resource.jwt.keyValue=SSSSHHH" })
public class BasicOAuth2SsoConfigurationTests {
@Autowired
private WebApplicationContext context;
@Autowired
@Qualifier("springSecurityFilterChain")
private Filter filter;
private MockMvc mvc;
@Before
public void init() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.addFilters(this.filter).build();
}
@Test
public void homePageIsSecure() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isFound())
.andExpect(header().string("location", "http://localhost/login"));
}
@Test
public void homePageSends401ToXhr() throws Exception {
this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest"))
.andExpect(status().isUnauthorized());
}
@Configuration
@Import(OAuth2AutoConfiguration.class)
@EnableOAuth2Sso
@MinimalSecureWebConfiguration
protected static class TestConfiguration {
}
}
/*
* Copyright 2012-2016 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.sso;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Test to validate that a custom {@link RestTemplate} can be defined with OAuth2 SSO.
*
* @author Stephane Nicoll
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest
@TestPropertySource(properties = { "security.oauth2.client.clientId=client",
"security.oauth2.client.clientSecret=secret",
"security.oauth2.client.userAuthorizationUri=http://example.com/oauth/authorize",
"security.oauth2.client.accessTokenUri=http://example.com/oauth/token",
"security.oauth2.resource.jwt.keyValue=SSSSHHH" })
public class CustomRestTemplateBasicOAuth2SsoConfigurationTests {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private ObjectProvider<RestTemplate> restTemplate;
@Test
public void customRestTemplateCanBePrimary() {
RestTemplate restTemplate = this.restTemplate.getIfAvailable();
verifyZeroInteractions(restTemplate);
assertThat(this.applicationContext.getBeansOfType(RestTemplate.class)).hasSize(1);
}
@Configuration
@Import(OAuth2AutoConfiguration.class)
@EnableOAuth2Sso
@MinimalSecureWebConfiguration
protected static class TestConfiguration {
@Bean
@Primary
public RestTemplate myRestTemplate() {
return mock(RestTemplate.class);
}
}
}
package org.test
@EnableAuthorizationServer
@EnableResourceServer
@RestController
class SampleController {
@RequestMapping("/")
def hello() {
[message: "Hello World!"]
}
}
......@@ -8,7 +8,6 @@ org.springframework.boot.cli.compiler.autoconfigure.JdbcCompilerAutoConfiguratio
org.springframework.boot.cli.compiler.autoconfigure.JmsCompilerAutoConfiguration
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.SpringMobileCompilerAutoConfiguration
org.springframework.boot.cli.compiler.autoconfigure.SpringRetryCompilerAutoConfiguration
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
server.port=8081
endpoints.default.web.enabled=true
security.oauth2.resource.id=service
security.oauth2.resource.userInfoUri=http://localhost:8080/user
logging.level.org.springframework.security=DEBUG
server.port=8081
spring.datasource.platform=h2
security.oauth2.resource.id=service
security.oauth2.resource.userInfoUri=http://localhost:8080/user
logging.level.org.springframework.security=DEBUG
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