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
This commit is contained in:
parent
a0fee6fe3b
commit
e9147c2f20
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,443 +0,0 @@
|
|||
/*
|
||||
* 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.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
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.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
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.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
|
||||
import org.springframework.security.oauth2.client.token.RequestEnhancer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;
|
||||
import org.springframework.social.connect.ConnectionFactoryLocator;
|
||||
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* Configuration for an OAuth2 resource server.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Madhura Bhave
|
||||
* @author Eddú Meléndez
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)
|
||||
public class ResourceServerTokenServicesConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public UserInfoRestTemplateFactory userInfoRestTemplateFactory(
|
||||
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
|
||||
ObjectProvider<OAuth2ProtectedResourceDetails> details,
|
||||
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
|
||||
return new DefaultUserInfoRestTemplateFactory(customizers, details,
|
||||
oauth2ClientContext);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Conditional(RemoteTokenCondition.class)
|
||||
protected static class RemoteTokenServicesConfiguration {
|
||||
|
||||
@Configuration
|
||||
@Conditional(TokenInfoCondition.class)
|
||||
protected static class TokenInfoServicesConfiguration {
|
||||
|
||||
private final ResourceServerProperties resource;
|
||||
|
||||
protected TokenInfoServicesConfiguration(ResourceServerProperties resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RemoteTokenServices remoteTokenServices() {
|
||||
RemoteTokenServices services = new RemoteTokenServices();
|
||||
services.setCheckTokenEndpointUrl(this.resource.getTokenInfoUri());
|
||||
services.setClientId(this.resource.getClientId());
|
||||
services.setClientSecret(this.resource.getClientSecret());
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(OAuth2ConnectionFactory.class)
|
||||
@Conditional(NotTokenInfoCondition.class)
|
||||
protected static class SocialTokenServicesConfiguration {
|
||||
|
||||
private final ResourceServerProperties sso;
|
||||
|
||||
private final OAuth2ConnectionFactory<?> connectionFactory;
|
||||
|
||||
private final OAuth2RestOperations restTemplate;
|
||||
|
||||
private final AuthoritiesExtractor authoritiesExtractor;
|
||||
|
||||
private final PrincipalExtractor principalExtractor;
|
||||
|
||||
public SocialTokenServicesConfiguration(ResourceServerProperties sso,
|
||||
ObjectProvider<OAuth2ConnectionFactory<?>> connectionFactory,
|
||||
UserInfoRestTemplateFactory restTemplateFactory,
|
||||
ObjectProvider<AuthoritiesExtractor> authoritiesExtractor,
|
||||
ObjectProvider<PrincipalExtractor> principalExtractor) {
|
||||
this.sso = sso;
|
||||
this.connectionFactory = connectionFactory.getIfAvailable();
|
||||
this.restTemplate = restTemplateFactory.getUserInfoRestTemplate();
|
||||
this.authoritiesExtractor = authoritiesExtractor.getIfAvailable();
|
||||
this.principalExtractor = principalExtractor.getIfAvailable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(ConnectionFactoryLocator.class)
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public SpringSocialTokenServices socialTokenServices() {
|
||||
return new SpringSocialTokenServices(this.connectionFactory,
|
||||
this.sso.getClientId());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean({ ConnectionFactoryLocator.class,
|
||||
ResourceServerTokenServices.class })
|
||||
public UserInfoTokenServices userInfoTokenServices() {
|
||||
UserInfoTokenServices services = new UserInfoTokenServices(
|
||||
this.sso.getUserInfoUri(), this.sso.getClientId());
|
||||
services.setTokenType(this.sso.getTokenType());
|
||||
services.setRestTemplate(this.restTemplate);
|
||||
if (this.authoritiesExtractor != null) {
|
||||
services.setAuthoritiesExtractor(this.authoritiesExtractor);
|
||||
}
|
||||
if (this.principalExtractor != null) {
|
||||
services.setPrincipalExtractor(this.principalExtractor);
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("org.springframework.social.connect.support.OAuth2ConnectionFactory")
|
||||
@Conditional(NotTokenInfoCondition.class)
|
||||
protected static class UserInfoTokenServicesConfiguration {
|
||||
|
||||
private final ResourceServerProperties sso;
|
||||
|
||||
private final OAuth2RestOperations restTemplate;
|
||||
|
||||
private final AuthoritiesExtractor authoritiesExtractor;
|
||||
|
||||
private final PrincipalExtractor principalExtractor;
|
||||
|
||||
public UserInfoTokenServicesConfiguration(ResourceServerProperties sso,
|
||||
UserInfoRestTemplateFactory restTemplateFactory,
|
||||
ObjectProvider<AuthoritiesExtractor> authoritiesExtractor,
|
||||
ObjectProvider<PrincipalExtractor> principalExtractor) {
|
||||
this.sso = sso;
|
||||
this.restTemplate = restTemplateFactory.getUserInfoRestTemplate();
|
||||
this.authoritiesExtractor = authoritiesExtractor.getIfAvailable();
|
||||
this.principalExtractor = principalExtractor.getIfAvailable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public UserInfoTokenServices userInfoTokenServices() {
|
||||
UserInfoTokenServices services = new UserInfoTokenServices(
|
||||
this.sso.getUserInfoUri(), this.sso.getClientId());
|
||||
services.setRestTemplate(this.restTemplate);
|
||||
services.setTokenType(this.sso.getTokenType());
|
||||
if (this.authoritiesExtractor != null) {
|
||||
services.setAuthoritiesExtractor(this.authoritiesExtractor);
|
||||
}
|
||||
if (this.principalExtractor != null) {
|
||||
services.setPrincipalExtractor(this.principalExtractor);
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Conditional(JwkCondition.class)
|
||||
protected static class JwkTokenStoreConfiguration {
|
||||
|
||||
private final ResourceServerProperties resource;
|
||||
|
||||
public JwkTokenStoreConfiguration(ResourceServerProperties resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public DefaultTokenServices jwkTokenServices(TokenStore jwkTokenStore) {
|
||||
DefaultTokenServices services = new DefaultTokenServices();
|
||||
services.setTokenStore(jwkTokenStore);
|
||||
return services;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TokenStore.class)
|
||||
public TokenStore jwkTokenStore() {
|
||||
return new JwkTokenStore(this.resource.getJwk().getKeySetUri());
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Conditional(JwtTokenCondition.class)
|
||||
protected static class JwtTokenServicesConfiguration {
|
||||
|
||||
private final ResourceServerProperties resource;
|
||||
|
||||
private final List<JwtAccessTokenConverterConfigurer> configurers;
|
||||
|
||||
private final List<JwtAccessTokenConverterRestTemplateCustomizer> customizers;
|
||||
|
||||
public JwtTokenServicesConfiguration(ResourceServerProperties resource,
|
||||
ObjectProvider<List<JwtAccessTokenConverterConfigurer>> configurers,
|
||||
ObjectProvider<List<JwtAccessTokenConverterRestTemplateCustomizer>> customizers) {
|
||||
this.resource = resource;
|
||||
this.configurers = configurers.getIfAvailable();
|
||||
this.customizers = customizers.getIfAvailable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
|
||||
public DefaultTokenServices jwtTokenServices(TokenStore jwtTokenStore) {
|
||||
DefaultTokenServices services = new DefaultTokenServices();
|
||||
services.setTokenStore(jwtTokenStore);
|
||||
return services;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(TokenStore.class)
|
||||
public TokenStore jwtTokenStore() {
|
||||
return new JwtTokenStore(jwtTokenEnhancer());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JwtAccessTokenConverter jwtTokenEnhancer() {
|
||||
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
|
||||
String keyValue = this.resource.getJwt().getKeyValue();
|
||||
if (!StringUtils.hasText(keyValue)) {
|
||||
keyValue = getKeyFromServer();
|
||||
}
|
||||
if (StringUtils.hasText(keyValue) && !keyValue.startsWith("-----BEGIN")) {
|
||||
converter.setSigningKey(keyValue);
|
||||
}
|
||||
if (keyValue != null) {
|
||||
converter.setVerifierKey(keyValue);
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.configurers)) {
|
||||
AnnotationAwareOrderComparator.sort(this.configurers);
|
||||
for (JwtAccessTokenConverterConfigurer configurer : this.configurers) {
|
||||
configurer.configure(converter);
|
||||
}
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
private String getKeyFromServer() {
|
||||
RestTemplate keyUriRestTemplate = new RestTemplate();
|
||||
if (!CollectionUtils.isEmpty(this.customizers)) {
|
||||
for (JwtAccessTokenConverterRestTemplateCustomizer customizer : this.customizers) {
|
||||
customizer.customize(keyUriRestTemplate);
|
||||
}
|
||||
}
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
String username = this.resource.getClientId();
|
||||
String password = this.resource.getClientSecret();
|
||||
if (username != null && password != null) {
|
||||
byte[] token = Base64.getEncoder()
|
||||
.encode((username + ":" + password).getBytes());
|
||||
headers.add("Authorization", "Basic " + new String(token));
|
||||
}
|
||||
HttpEntity<Void> request = new HttpEntity<>(headers);
|
||||
String url = this.resource.getJwt().getKeyUri();
|
||||
return (String) keyUriRestTemplate
|
||||
.exchange(url, HttpMethod.GET, request, Map.class).getBody()
|
||||
.get("value");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TokenInfoCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage
|
||||
.forCondition("OAuth TokenInfo Condition");
|
||||
Environment environment = context.getEnvironment();
|
||||
Boolean preferTokenInfo = environment.getProperty(
|
||||
"security.oauth2.resource.prefer-token-info", Boolean.class);
|
||||
if (preferTokenInfo == null) {
|
||||
preferTokenInfo = environment
|
||||
.resolvePlaceholders("${OAUTH2_RESOURCE_PREFERTOKENINFO:true}")
|
||||
.equals("true");
|
||||
}
|
||||
String tokenInfoUri = environment
|
||||
.getProperty("security.oauth2.resource.token-info-uri");
|
||||
String userInfoUri = environment
|
||||
.getProperty("security.oauth2.resource.user-info-uri");
|
||||
if (!StringUtils.hasLength(userInfoUri)
|
||||
&& !StringUtils.hasLength(tokenInfoUri)) {
|
||||
return ConditionOutcome
|
||||
.match(message.didNotFind("user-info-uri property").atAll());
|
||||
}
|
||||
if (StringUtils.hasLength(tokenInfoUri) && preferTokenInfo) {
|
||||
return ConditionOutcome
|
||||
.match(message.foundExactly("preferred token-info-uri property"));
|
||||
}
|
||||
return ConditionOutcome.noMatch(message.didNotFind("token info").atAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class JwtTokenCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage
|
||||
.forCondition("OAuth JWT Condition");
|
||||
Environment environment = context.getEnvironment();
|
||||
String keyValue = environment
|
||||
.getProperty("security.oauth2.resource.jwt.key-value");
|
||||
String keyUri = environment
|
||||
.getProperty("security.oauth2.resource.jwt.key-uri");
|
||||
if (StringUtils.hasText(keyValue) || StringUtils.hasText(keyUri)) {
|
||||
return ConditionOutcome
|
||||
.match(message.foundExactly("provided public key"));
|
||||
}
|
||||
return ConditionOutcome
|
||||
.noMatch(message.didNotFind("provided public key").atAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class JwkCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
ConditionMessage.Builder message = ConditionMessage
|
||||
.forCondition("OAuth JWK Condition");
|
||||
Environment environment = context.getEnvironment();
|
||||
String keyUri = environment
|
||||
.getProperty("security.oauth2.resource.jwk.key-set-uri");
|
||||
if (StringUtils.hasText(keyUri)) {
|
||||
return ConditionOutcome
|
||||
.match(message.foundExactly("provided jwk key set URI"));
|
||||
}
|
||||
return ConditionOutcome
|
||||
.noMatch(message.didNotFind("key jwk set URI not provided").atAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class NotTokenInfoCondition extends SpringBootCondition {
|
||||
|
||||
private TokenInfoCondition tokenInfoCondition = new TokenInfoCondition();
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
return ConditionOutcome
|
||||
.inverse(this.tokenInfoCondition.getMatchOutcome(context, metadata));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class RemoteTokenCondition extends NoneNestedConditions {
|
||||
|
||||
RemoteTokenCondition() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@Conditional(JwtTokenCondition.class)
|
||||
static class HasJwtConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Conditional(JwkCondition.class)
|
||||
static class HasJwkConfiguration {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static class AcceptJsonRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
|
||||
ClientHttpRequestExecution execution) throws IOException {
|
||||
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
return execution.execute(request, body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class AcceptJsonRequestEnhancer implements RequestEnhancer {
|
||||
|
||||
@Override
|
||||
public void enhance(AccessTokenRequest request,
|
||||
OAuth2ProtectedResourceDetails resource,
|
||||
MultiValueMap<String, String> form, HttpHeaders headers) {
|
||||
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* 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,\
|
||||
|
|
|
|||
|
|
@ -1,683 +0,0 @@
|
|||
/*
|
||||
* 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.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.authserver.OAuth2AuthorizationServerConfiguration;
|
||||
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.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
|
||||
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
|
||||
import org.springframework.security.access.method.MethodSecurityMetadataSource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
|
||||
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestOperations;
|
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
|
||||
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.ClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
|
||||
import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler;
|
||||
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
|
||||
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
|
||||
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
|
||||
import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
|
||||
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Verify Spring Security OAuth2 auto-configuration secures end points properly, accepts
|
||||
* environmental overrides, and also backs off in the presence of other
|
||||
* resource/authorization components.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class OAuth2AutoConfigurationTests {
|
||||
|
||||
private static final Class<?> RESOURCE_SERVER_CONFIG = OAuth2ResourceServerConfiguration.class;
|
||||
|
||||
private static final Class<?> AUTHORIZATION_SERVER_CONFIG = OAuth2AuthorizationServerConfiguration.class;
|
||||
|
||||
private AnnotationConfigServletWebServerApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testDefaultConfiguration() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
this.context.getBean(AUTHORIZATION_SERVER_CONFIG);
|
||||
this.context.getBean(RESOURCE_SERVER_CONFIG);
|
||||
this.context.getBean(OAuth2MethodSecurityConfiguration.class);
|
||||
ClientDetails config = this.context.getBean(BaseClientDetails.class);
|
||||
AuthorizationEndpoint endpoint = this.context
|
||||
.getBean(AuthorizationEndpoint.class);
|
||||
UserApprovalHandler handler = (UserApprovalHandler) ReflectionTestUtils
|
||||
.getField(endpoint, "userApprovalHandler");
|
||||
ClientDetailsService clientDetailsService = this.context
|
||||
.getBean(ClientDetailsService.class);
|
||||
ClientDetails clientDetails = clientDetailsService
|
||||
.loadClientByClientId(config.getClientId());
|
||||
assertThat(AopUtils.isJdkDynamicProxy(clientDetailsService)).isTrue();
|
||||
assertThat(AopUtils.getTargetClass(clientDetailsService).getName())
|
||||
.isEqualTo(InMemoryClientDetailsService.class.getName());
|
||||
assertThat(handler).isInstanceOf(ApprovalStoreUserApprovalHandler.class);
|
||||
assertThat(clientDetails).isEqualTo(config);
|
||||
verifyAuthentication(config);
|
||||
assertThat(this.context.getBeanNamesForType(OAuth2RestOperations.class))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodSecurityExpressionHandlerIsConfiguredWithRoleHierarchyFromTheContext() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(RoleHierarchyConfiguration.class,
|
||||
AuthorizationAndResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
PreInvocationAuthorizationAdvice advice = this.context
|
||||
.getBean(PreInvocationAuthorizationAdvice.class);
|
||||
MethodSecurityExpressionHandler expressionHandler = (MethodSecurityExpressionHandler) ReflectionTestUtils
|
||||
.getField(advice, "expressionHandler");
|
||||
RoleHierarchy roleHierarchy = (RoleHierarchy) ReflectionTestUtils
|
||||
.getField(expressionHandler, "roleHierarchy");
|
||||
assertThat(roleHierarchy).isSameAs(this.context.getBean(RoleHierarchy.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodSecurityExpressionHandlerIsConfiguredWithPermissionEvaluatorFromTheContext() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(PermissionEvaluatorConfiguration.class,
|
||||
AuthorizationAndResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
PreInvocationAuthorizationAdvice advice = this.context
|
||||
.getBean(PreInvocationAuthorizationAdvice.class);
|
||||
MethodSecurityExpressionHandler expressionHandler = (MethodSecurityExpressionHandler) ReflectionTestUtils
|
||||
.getField(advice, "expressionHandler");
|
||||
PermissionEvaluator permissionEvaluator = (PermissionEvaluator) ReflectionTestUtils
|
||||
.getField(expressionHandler, "permissionEvaluator");
|
||||
assertThat(permissionEvaluator)
|
||||
.isSameAs(this.context.getBean(PermissionEvaluator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnvironmentalOverrides() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.client.clientId:myclientid",
|
||||
"security.oauth2.client.clientSecret:mysecret",
|
||||
"security.oauth2.client.autoApproveScopes:read,write",
|
||||
"security.oauth2.client.accessTokenValiditySeconds:40",
|
||||
"security.oauth2.client.refreshTokenValiditySeconds:80")
|
||||
.applyTo(this.context);
|
||||
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||
assertThat(config.getClientId()).isEqualTo("myclientid");
|
||||
assertThat(config.getClientSecret()).isEqualTo("mysecret");
|
||||
assertThat(config.isAutoApprove("read")).isTrue();
|
||||
assertThat(config.isAutoApprove("write")).isTrue();
|
||||
assertThat(config.isAutoApprove("foo")).isFalse();
|
||||
assertThat(config.getAccessTokenValiditySeconds()).isEqualTo(40);
|
||||
assertThat(config.getRefreshTokenValiditySeconds()).isEqualTo(80);
|
||||
verifyAuthentication(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisablingResourceServer() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(AuthorizationServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(0);
|
||||
assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientIsNotResourceServer() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(ClientConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(0);
|
||||
assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(0);
|
||||
// Scoped target and proxy:
|
||||
assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanUseClientCredentials() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(TestSecurityConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.client.clientId=client",
|
||||
"security.oauth2.client.grantType=client_credentials")
|
||||
.applyTo(this.context);
|
||||
ConfigurationPropertySources.attach(this.context.getEnvironment());
|
||||
this.context.refresh();
|
||||
OAuth2ClientContext bean = this.context.getBean(OAuth2ClientContext.class);
|
||||
assertThat(bean.getAccessTokenRequest()).isNotNull();
|
||||
assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1);
|
||||
assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanUseClientCredentialsWithEnableOAuth2Client() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(ClientConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.client.clientId=client",
|
||||
"security.oauth2.client.grantType=client_credentials")
|
||||
.applyTo(this.context);
|
||||
ConfigurationPropertySources.attach(this.context.getEnvironment());
|
||||
this.context.refresh();
|
||||
// The primary context is fine (not session scoped):
|
||||
OAuth2ClientContext bean = this.context.getBean(OAuth2ClientContext.class);
|
||||
assertThat(bean.getAccessTokenRequest()).isNotNull();
|
||||
assertThat(countBeans(ClientCredentialsResourceDetails.class)).isEqualTo(1);
|
||||
// Kind of a bug (should ideally be 1), but the cause is in Spring OAuth2 (there
|
||||
// is no need for the extra session-scoped bean). What this test proves is that
|
||||
// even if the user screws up and does @EnableOAuth2Client for client credentials,
|
||||
// it will still just about work (because of the @Primary annotation on the
|
||||
// Boot-created instance of OAuth2ClientContext).
|
||||
assertThat(countBeans(OAuth2ClientContext.class)).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientIsNotAuthCode() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(MinimalSecureNonWebApplication.class);
|
||||
TestPropertyValues.of("security.oauth2.client.clientId=client").applyTo(context);
|
||||
context.refresh();
|
||||
assertThat(countBeans(context, ClientCredentialsResourceDetails.class))
|
||||
.isEqualTo(1);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisablingAuthorizationServer() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(ResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
TestPropertyValues.of("security.oauth2.resource.jwt.keyValue:DEADBEEF")
|
||||
.applyTo(this.context);
|
||||
ConfigurationPropertySources.attach(this.context.getEnvironment());
|
||||
this.context.refresh();
|
||||
assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
|
||||
assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(0);
|
||||
assertThat(countBeans(UserApprovalHandler.class)).isEqualTo(0);
|
||||
assertThat(countBeans(DefaultTokenServices.class)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceServerOverride() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||
CustomResourceServer.class, MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||
assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(1);
|
||||
assertThat(countBeans(CustomResourceServer.class)).isEqualTo(1);
|
||||
assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
|
||||
verifyAuthentication(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationServerOverride() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
TestPropertyValues.of("security.oauth2.resourceId:resource-id")
|
||||
.applyTo(this.context);
|
||||
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||
CustomAuthorizationServer.class, MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
BaseClientDetails config = new BaseClientDetails();
|
||||
config.setClientId("client");
|
||||
config.setClientSecret("secret");
|
||||
config.setResourceIds(Arrays.asList("resource-id"));
|
||||
config.setAuthorizedGrantTypes(Arrays.asList("password"));
|
||||
config.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
|
||||
config.setScope(Arrays.asList("read"));
|
||||
assertThat(countBeans(AUTHORIZATION_SERVER_CONFIG)).isEqualTo(0);
|
||||
assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
|
||||
verifyAuthentication(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultPrePostSecurityAnnotations() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(AuthorizationAndResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
this.context.getBean(OAuth2MethodSecurityConfiguration.class);
|
||||
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||
DelegatingMethodSecurityMetadataSource source = this.context
|
||||
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||
List<MethodSecurityMetadataSource> sources = source
|
||||
.getMethodSecurityMetadataSources();
|
||||
assertThat(sources.size()).isEqualTo(1);
|
||||
assertThat(sources.get(0).getClass().getName())
|
||||
.isEqualTo(PrePostAnnotationSecurityMetadataSource.class.getName());
|
||||
verifyAuthentication(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassicSecurityAnnotationOverride() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(SecuredEnabledConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
this.context.getBean(OAuth2MethodSecurityConfiguration.class);
|
||||
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||
DelegatingMethodSecurityMetadataSource source = this.context
|
||||
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||
List<MethodSecurityMetadataSource> sources = source
|
||||
.getMethodSecurityMetadataSources();
|
||||
assertThat(sources.size()).isEqualTo(1);
|
||||
assertThat(sources.get(0).getClass().getName())
|
||||
.isEqualTo(SecuredAnnotationSecurityMetadataSource.class.getName());
|
||||
verifyAuthentication(config, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsr250SecurityAnnotationOverride() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(Jsr250EnabledConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
this.context.getBean(OAuth2MethodSecurityConfiguration.class);
|
||||
ClientDetails config = this.context.getBean(ClientDetails.class);
|
||||
DelegatingMethodSecurityMetadataSource source = this.context
|
||||
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||
List<MethodSecurityMetadataSource> sources = source
|
||||
.getMethodSecurityMetadataSources();
|
||||
assertThat(sources.size()).isEqualTo(1);
|
||||
assertThat(sources.get(0).getClass().getName())
|
||||
.isEqualTo(Jsr250MethodSecurityMetadataSource.class.getName());
|
||||
verifyAuthentication(config, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodSecurityBackingOff() {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
this.context.register(CustomMethodSecurity.class, TestSecurityConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
DelegatingMethodSecurityMetadataSource source = this.context
|
||||
.getBean(DelegatingMethodSecurityMetadataSource.class);
|
||||
List<MethodSecurityMetadataSource> sources = source
|
||||
.getMethodSecurityMetadataSources();
|
||||
assertThat(sources.size()).isEqualTo(1);
|
||||
assertThat(sources.get(0).getClass().getName())
|
||||
.isEqualTo(PrePostAnnotationSecurityMetadataSource.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceServerConditionWhenJwkConfigurationPresentShouldMatch()
|
||||
throws Exception {
|
||||
this.context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.jwk.key-set-uri:http://my-auth-server/token_keys")
|
||||
.applyTo(this.context);
|
||||
this.context.register(ResourceServerConfiguration.class,
|
||||
MinimalSecureWebApplication.class);
|
||||
this.context.refresh();
|
||||
assertThat(countBeans(RESOURCE_SERVER_CONFIG)).isEqualTo(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the oauth service, get a token, and then attempt some operations using
|
||||
* it.
|
||||
* @param config the client details.
|
||||
*/
|
||||
private void verifyAuthentication(ClientDetails config) {
|
||||
verifyAuthentication(config, HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
private void verifyAuthentication(ClientDetails config, HttpStatus finalStatus) {
|
||||
String baseUrl = "http://localhost:" + this.context.getWebServer().getPort();
|
||||
TestRestTemplate rest = new TestRestTemplate();
|
||||
// First, verify the web endpoint can't be reached
|
||||
assertEndpointUnauthorized(baseUrl, rest);
|
||||
// Since we can't reach it, need to collect an authorization token
|
||||
HttpHeaders headers = getHeaders(config);
|
||||
String url = baseUrl + "/oauth/token";
|
||||
JsonNode tokenResponse = rest.postForObject(url,
|
||||
new HttpEntity<>(getBody(), headers), JsonNode.class);
|
||||
String authorizationToken = tokenResponse.findValue("access_token").asText();
|
||||
String tokenType = tokenResponse.findValue("token_type").asText();
|
||||
String scope = tokenResponse.findValues("scope").get(0).toString();
|
||||
assertThat(tokenType).isEqualTo("bearer");
|
||||
assertThat(scope).isEqualTo("\"read\"");
|
||||
// Now we should be able to see that endpoint.
|
||||
headers.set("Authorization", "BEARER " + authorizationToken);
|
||||
ResponseEntity<String> securedResponse = rest
|
||||
.exchange(new RequestEntity<Void>(headers, HttpMethod.GET,
|
||||
URI.create(baseUrl + "/securedFind")), String.class);
|
||||
assertThat(securedResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(securedResponse.getBody()).isEqualTo(
|
||||
"You reached an endpoint " + "secured by Spring Security OAuth2");
|
||||
ResponseEntity<String> entity = rest.exchange(new RequestEntity<Void>(headers,
|
||||
HttpMethod.POST, URI.create(baseUrl + "/securedSave")), String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(finalStatus);
|
||||
}
|
||||
|
||||
private HttpHeaders getHeaders(ClientDetails config) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
String token = new String(Base64.getEncoder().encode(
|
||||
(config.getClientId() + ":" + config.getClientSecret()).getBytes()));
|
||||
headers.set("Authorization", "Basic " + token);
|
||||
return headers;
|
||||
}
|
||||
|
||||
private MultiValueMap<String, Object> getBody() {
|
||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
||||
body.set("grant_type", "password");
|
||||
body.set("username", "foo");
|
||||
body.set("password", "bar");
|
||||
body.set("scope", "read");
|
||||
return body;
|
||||
}
|
||||
|
||||
private void assertEndpointUnauthorized(String baseUrl, TestRestTemplate rest) {
|
||||
URI uri = URI.create(baseUrl + "/secured");
|
||||
ResponseEntity<String> entity = rest
|
||||
.exchange(new RequestEntity<Void>(HttpMethod.GET, uri), String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
private int countBeans(Class<?> type) {
|
||||
return countBeans(this.context, type);
|
||||
}
|
||||
|
||||
private int countBeans(ApplicationContext context, Class<?> type) {
|
||||
return context.getBeanNamesForType(type).length;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ UseFreePortEmbeddedContainerConfiguration.class,
|
||||
SecurityAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
|
||||
OAuth2AutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class })
|
||||
protected static class MinimalSecureWebApplication {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ SecurityAutoConfiguration.class, OAuth2AutoConfiguration.class })
|
||||
protected static class MinimalSecureNonWebApplication {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class TestSecurityConfiguration
|
||||
extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication().withUser("foo").password("bar").roles("USER");
|
||||
}
|
||||
|
||||
@Bean
|
||||
TestWebApp testWebApp() {
|
||||
return new TestWebApp();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableOAuth2Client
|
||||
protected static class ClientConfiguration extends TestSecurityConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAuthorizationServer
|
||||
@EnableResourceServer
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
protected static class AuthorizationAndResourceServerConfiguration
|
||||
extends TestSecurityConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAuthorizationServer
|
||||
@EnableResourceServer
|
||||
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||
protected static class SecuredEnabledConfiguration extends TestSecurityConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAuthorizationServer
|
||||
@EnableResourceServer
|
||||
@EnableGlobalMethodSecurity(jsr250Enabled = true)
|
||||
protected static class Jsr250EnabledConfiguration extends TestSecurityConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAuthorizationServer
|
||||
protected static class AuthorizationServerConfiguration
|
||||
extends TestSecurityConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableResourceServer
|
||||
protected static class ResourceServerConfiguration extends TestSecurityConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
protected static class TestWebApp {
|
||||
|
||||
@GetMapping("/securedFind")
|
||||
@PreAuthorize("#oauth2.hasScope('read')")
|
||||
public String secureFind() {
|
||||
return "You reached an endpoint secured by Spring Security OAuth2";
|
||||
}
|
||||
|
||||
@PostMapping("/securedSave")
|
||||
@PreAuthorize("#oauth2.hasScope('write')")
|
||||
public String secureSave() {
|
||||
return "You reached an endpoint secured by Spring Security OAuth2";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class UseFreePortEmbeddedContainerConfiguration {
|
||||
|
||||
@Bean
|
||||
TomcatServletWebServerFactory webServerFactory() {
|
||||
return new TomcatServletWebServerFactory(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableResourceServer
|
||||
protected static class CustomResourceServer extends ResourceServerConfigurerAdapter {
|
||||
|
||||
private final ResourceServerProperties config;
|
||||
|
||||
protected CustomResourceServer(ResourceServerProperties config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ResourceServerSecurityConfigurer resources)
|
||||
throws Exception {
|
||||
if (this.config.getId() != null) {
|
||||
resources.resourceId(this.config.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and()
|
||||
.csrf().disable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAuthorizationServer
|
||||
protected static class CustomAuthorizationServer
|
||||
extends AuthorizationServerConfigurerAdapter {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
protected CustomAuthorizationServer(AuthenticationManager authenticationManager) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenStore tokenStore() {
|
||||
return new InMemoryTokenStore();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ApprovalStore approvalStore(final TokenStore tokenStore) {
|
||||
TokenApprovalStore approvalStore = new TokenApprovalStore();
|
||||
approvalStore.setTokenStore(tokenStore);
|
||||
return approvalStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
|
||||
clients.inMemory().withClient("client").secret("secret")
|
||||
.resourceIds("resource-id").authorizedGrantTypes("password")
|
||||
.authorities("USER").scopes("read")
|
||||
.redirectUris("http://localhost:8080");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
|
||||
throws Exception {
|
||||
endpoints.tokenStore(tokenStore())
|
||||
.authenticationManager(this.authenticationManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
protected static class CustomMethodSecurity
|
||||
extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
@Override
|
||||
protected MethodSecurityExpressionHandler createExpressionHandler() {
|
||||
return new OAuth2MethodSecurityExpressionHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class RoleHierarchyConfiguration {
|
||||
|
||||
@Bean
|
||||
public RoleHierarchy roleHierarchy() {
|
||||
return mock(RoleHierarchy.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class PermissionEvaluatorConfiguration {
|
||||
|
||||
@Bean
|
||||
public PermissionEvaluator permissionEvaluator() {
|
||||
return mock(PermissionEvaluator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* 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}]");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,224 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,417 +0,0 @@
|
|||
/*
|
||||
* 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.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOperationsConfiguration;
|
||||
import org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.MockServletWebServerFactory;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
|
||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
|
||||
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
|
||||
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
|
||||
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;
|
||||
import org.springframework.social.connect.ConnectionFactoryLocator;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceServerTokenServicesConfiguration}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Madhura Bhave
|
||||
* @author Eddú Meléndez
|
||||
*/
|
||||
public class ResourceServerTokenServicesConfigurationTests {
|
||||
|
||||
private static String PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n"
|
||||
+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnGp/Q5lh0P8nPL21oMMrt2RrkT9"
|
||||
+ "AW5jgYwLfSUnJVc9G6uR3cXRRDCjHqWU5WYwivcF180A6CWp/ireQFFBNowgc5XaA0kPpzE"
|
||||
+ "tgsA5YsNX7iSnUibB004iBTfU9hZ2Rbsc8cWqynT0RyN4TP1RYVSeVKvMQk4GT1r7JCEC+T"
|
||||
+ "Nu1ELmbNwMQyzKjsfBXyIOCFU/E94ktvsTZUHF4Oq44DBylCDsS1k7/sfZC2G5EU7Oz0mhG"
|
||||
+ "8+Uz6MSEQHtoIi6mc8u64Rwi3Z3tscuWG2ShtsUFuNSAFNkY7LkLn+/hxLCu2bNISMaESa8"
|
||||
+ "dG22CIMuIeRLVcAmEWEWH5EEforTg+QIDAQAB\n-----END PUBLIC KEY-----";
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useRemoteTokenServices() {
|
||||
TestPropertyValues.of("security.oauth2.resource.tokenInfoUri:http://example.com")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
RemoteTokenServices services = this.context.getBean(RemoteTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchToUserInfo() {
|
||||
TestPropertyValues.of("security.oauth2.resource.userInfoUri:http://example.com")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
UserInfoTokenServices services = this.context
|
||||
.getBean(UserInfoTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoWithAuthorities() {
|
||||
TestPropertyValues.of("security.oauth2.resource.userInfoUri:http://example.com")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(AuthoritiesConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
UserInfoTokenServices services = this.context
|
||||
.getBean(UserInfoTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
assertThat(services).extracting("authoritiesExtractor")
|
||||
.containsExactly(this.context.getBean(AuthoritiesExtractor.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoWithPrincipal() {
|
||||
TestPropertyValues.of("security.oauth2.resource.userInfoUri:http://example.com")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(PrincipalConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
UserInfoTokenServices services = this.context
|
||||
.getBean(UserInfoTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
assertThat(services).extracting("principalExtractor")
|
||||
.containsExactly(this.context.getBean(PrincipalExtractor.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoWithClient() {
|
||||
TestPropertyValues.of("security.oauth2.client.client-id=acme",
|
||||
"security.oauth2.resource.userInfoUri:http://example.com",
|
||||
"server.port=-1", "debug=true").applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceNoClientConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.SERVLET).run();
|
||||
BeanDefinition bean = ((BeanDefinitionRegistry) this.context)
|
||||
.getBeanDefinition("scopedTarget.oauth2ClientContext");
|
||||
assertThat(bean.getScope()).isEqualTo("request");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preferUserInfo() {
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.userInfoUri:http://example.com",
|
||||
"security.oauth2.resource.tokenInfoUri:http://example.com",
|
||||
"security.oauth2.resource.preferTokenInfo:false")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
UserInfoTokenServices services = this.context
|
||||
.getBean(UserInfoTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoWithCustomizer() {
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.userInfoUri:http://example.com",
|
||||
"security.oauth2.resource.tokenInfoUri:http://example.com",
|
||||
"security.oauth2.resource.preferTokenInfo:false")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class,
|
||||
Customizer.class).environment(this.environment)
|
||||
.web(WebApplicationType.NONE).run();
|
||||
UserInfoTokenServices services = this.context
|
||||
.getBean(UserInfoTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void switchToJwt() {
|
||||
TestPropertyValues.of("security.oauth2.resource.jwt.keyValue=FOOBAR")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(RemoteTokenServices.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asymmetricJwt() {
|
||||
TestPropertyValues.of("security.oauth2.resource.jwt.keyValue=" + PUBLIC_KEY)
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwkConfiguration() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.jwk.key-set-uri=http://my-auth-server/token_keys")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
DefaultTokenServices services = this.context.getBean(DefaultTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(RemoteTokenServices.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void springSocialUserInfo() {
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.userInfoUri:http://example.com",
|
||||
"spring.social.facebook.app-id=foo",
|
||||
"spring.social.facebook.app-secret=bar")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(SocialResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.SERVLET).run();
|
||||
ConnectionFactoryLocator connectionFactory = this.context
|
||||
.getBean(ConnectionFactoryLocator.class);
|
||||
assertThat(connectionFactory).isNotNull();
|
||||
SpringSocialTokenServices services = this.context
|
||||
.getBean(SpringSocialTokenServices.class);
|
||||
assertThat(services).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customUserInfoRestTemplateFactory() {
|
||||
TestPropertyValues.of("security.oauth2.resource.userInfoUri:http://example.com")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(
|
||||
CustomUserInfoRestTemplateFactory.class, ResourceConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
assertThat(this.context.getBeansOfType(UserInfoRestTemplateFactory.class))
|
||||
.hasSize(1);
|
||||
assertThat(this.context.getBean(UserInfoRestTemplateFactory.class))
|
||||
.isInstanceOf(CustomUserInfoRestTemplateFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwtAccessTokenConverterIsConfiguredWhenKeyUriIsProvided() {
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.jwt.key-uri=http://localhost:12345/banana")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(ResourceConfiguration.class,
|
||||
JwtAccessTokenConverterRestTemplateCustomizerConfiguration.class)
|
||||
.environment(this.environment).web(WebApplicationType.NONE).run();
|
||||
assertThat(this.context.getBeansOfType(JwtAccessTokenConverter.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwkTokenStoreShouldBeConditionalOnMissingBean() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("security.oauth2.resource.jwk.key-set-uri=http://my-auth-server/token_keys")
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(JwkTokenStoreConfiguration.class,
|
||||
ResourceConfiguration.class).environment(this.environment)
|
||||
.web(WebApplicationType.NONE).run();
|
||||
assertThat(this.context.getBeansOfType(JwkTokenStore.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwtTokenStoreShouldBeConditionalOnMissingBean() throws Exception {
|
||||
TestPropertyValues.of("security.oauth2.resource.jwt.keyValue=" + PUBLIC_KEY)
|
||||
.applyTo(this.environment);
|
||||
this.context = new SpringApplicationBuilder(JwtTokenStoreConfiguration.class,
|
||||
ResourceConfiguration.class).environment(this.environment)
|
||||
.web(WebApplicationType.NONE).run();
|
||||
assertThat(this.context.getBeansOfType(JwtTokenStore.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ ResourceServerTokenServicesConfiguration.class,
|
||||
ResourceServerPropertiesConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class })
|
||||
@EnableConfigurationProperties(OAuth2ClientProperties.class)
|
||||
protected static class ResourceConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class AuthoritiesConfiguration extends ResourceConfiguration {
|
||||
|
||||
@Bean
|
||||
AuthoritiesExtractor authoritiesExtractor() {
|
||||
return (map) -> AuthorityUtils
|
||||
.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class PrincipalConfiguration extends ResourceConfiguration {
|
||||
|
||||
@Bean
|
||||
PrincipalExtractor principalExtractor() {
|
||||
return (map) -> "boot";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Import({ OAuth2RestOperationsConfiguration.class })
|
||||
protected static class ResourceNoClientConfiguration extends ResourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public MockServletWebServerFactory webServerFactory() {
|
||||
return new MockServletWebServerFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class ResourceServerPropertiesConfiguration {
|
||||
|
||||
private OAuth2ClientProperties credentials;
|
||||
|
||||
public ResourceServerPropertiesConfiguration(OAuth2ClientProperties credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceServerProperties resourceServerProperties() {
|
||||
return new ResourceServerProperties(this.credentials.getClientId(),
|
||||
this.credentials.getClientSecret());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Import({ FacebookAutoConfiguration.class, SocialWebAutoConfiguration.class })
|
||||
protected static class SocialResourceConfiguration extends ResourceConfiguration {
|
||||
|
||||
@Bean
|
||||
public ServletWebServerFactory webServerFactory() {
|
||||
return mock(ServletWebServerFactory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
protected static class Customizer implements UserInfoRestTemplateCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(OAuth2RestTemplate template) {
|
||||
template.getInterceptors()
|
||||
.add((request, body, execution) -> execution.execute(request, body));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
protected static class CustomUserInfoRestTemplateFactory
|
||||
implements UserInfoRestTemplateFactory {
|
||||
|
||||
private final OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(
|
||||
new AuthorizationCodeResourceDetails());
|
||||
|
||||
@Override
|
||||
public OAuth2RestTemplate getUserInfoRestTemplate() {
|
||||
return this.restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class JwtAccessTokenConverterRestTemplateCustomizerConfiguration {
|
||||
|
||||
@Bean
|
||||
public JwtAccessTokenConverterRestTemplateCustomizer restTemplateCustomizer() {
|
||||
return new MockRestCallCustomizer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class JwtTokenStoreConfiguration {
|
||||
|
||||
@Bean
|
||||
public TokenStore tokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
|
||||
return new JwtTokenStore(jwtTokenEnhancer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class JwkTokenStoreConfiguration {
|
||||
|
||||
@Bean
|
||||
public TokenStore tokenStore() {
|
||||
return new JwkTokenStore("http://my.key-set.uri");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockRestCallCustomizer
|
||||
implements JwtAccessTokenConverterRestTemplateCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(RestTemplate template) {
|
||||
template.getInterceptors().add((request, body, execution) -> {
|
||||
String payload = "{\"value\":\"FOO\"}";
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(
|
||||
payload.getBytes(), HttpStatus.OK);
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
/*
|
||||
* 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.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.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
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.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
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.content;
|
||||
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 custom configuration.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
@SpringBootTest
|
||||
@TestPropertySource(properties = { "security.oauth2.client.clientId=client",
|
||||
"security.oauth2.client.clientSecret=secret",
|
||||
"security.oauth2.client.authorizationUri=http://example.com/oauth/authorize",
|
||||
"security.oauth2.client.tokenUri=http://example.com/oauth/token",
|
||||
"security.oauth2.resource.jwt.keyValue=SSSSHHH" })
|
||||
public class CustomOAuth2SsoConfigurationTests {
|
||||
|
||||
@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 uiPageIsSecure() throws Exception {
|
||||
this.mvc.perform(get("/ui/")).andExpect(status().isFound())
|
||||
.andExpect(header().string("location", "http://localhost/login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uiPageSends401ToXhr() throws Exception {
|
||||
this.mvc.perform(get("/ui/").header("X-Requested-With", "XMLHttpRequest"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uiTestPageIsAccessible() throws Exception {
|
||||
this.mvc.perform(get("/ui/test")).andExpect(status().isOk())
|
||||
.andExpect(content().string("test"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableOAuth2Sso
|
||||
@Import(OAuth2AutoConfiguration.class)
|
||||
@MinimalSecureWebConfiguration
|
||||
protected static class TestConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http.antMatcher("/ui/**").authorizeRequests().antMatchers("/ui/test")
|
||||
.permitAll().anyRequest().authenticated();
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class TestController {
|
||||
|
||||
@RequestMapping("/ui/test")
|
||||
public String test() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* 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.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.http.HttpStatus;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
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.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
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.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AutoConfiguration} with custom configuration.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
@SpringBootTest
|
||||
@TestPropertySource(properties = { "security.oauth2.client.clientId=client",
|
||||
"security.oauth2.client.clientSecret=secret",
|
||||
"security.oauth2.client.authorizationUri=http://example.com/oauth/authorize",
|
||||
"security.oauth2.client.tokenUri=http://example.com/oauth/token",
|
||||
"security.oauth2.resource.jwt.keyValue=SSSSHHH" })
|
||||
public class CustomOAuth2SsoWithAuthenticationEntryPointConfigurationTests {
|
||||
|
||||
@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 uiPageIsSecure() throws Exception {
|
||||
this.mvc.perform(get("/ui/")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uiTestPageIsAccessible() throws Exception {
|
||||
this.mvc.perform(get("/ui/test")).andExpect(status().isOk())
|
||||
.andExpect(content().string("test"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableOAuth2Sso
|
||||
@Import(OAuth2AutoConfiguration.class)
|
||||
@MinimalSecureWebConfiguration
|
||||
protected static class TestConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http.antMatcher("/ui/**").authorizeRequests().antMatchers("/ui/test")
|
||||
.permitAll().anyRequest().authenticated().and().exceptionHandling()
|
||||
.authenticationEntryPoint(
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class TestController {
|
||||
|
||||
@RequestMapping("/ui/test")
|
||||
public String test() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* 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.sso;
|
||||
|
||||
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.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
|
||||
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.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({ ServletWebServerFactoryAutoConfiguration.class,
|
||||
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
|
||||
PropertyPlaceholderAutoConfiguration.class, SecurityAutoConfiguration.class })
|
||||
public @interface MinimalSecureWebConfiguration {
|
||||
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package org.test
|
||||
|
||||
@EnableAuthorizationServer
|
||||
@EnableResourceServer
|
||||
@RestController
|
||||
class SampleController {
|
||||
|
||||
@RequestMapping("/")
|
||||
def hello() {
|
||||
[message: "Hello World!"]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.cli.compiler.autoconfigure;
|
||||
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.control.CompilationFailedException;
|
||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||
|
||||
import org.springframework.boot.cli.compiler.AstUtils;
|
||||
import org.springframework.boot.cli.compiler.CompilerAutoConfiguration;
|
||||
import org.springframework.boot.cli.compiler.DependencyCustomizer;
|
||||
|
||||
/**
|
||||
* {@link CompilerAutoConfiguration} for Spring Security OAuth2.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Dave Syer
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class SpringSecurityOAuth2CompilerAutoConfiguration
|
||||
extends CompilerAutoConfiguration {
|
||||
|
||||
@Override
|
||||
public boolean matches(ClassNode classNode) {
|
||||
return AstUtils.hasAtLeastOneAnnotation(classNode, "EnableAuthorizationServer",
|
||||
"EnableResourceServer", "EnableOAuth2Client", "EnableOAuth2Sso");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyDependencies(DependencyCustomizer dependencies)
|
||||
throws CompilationFailedException {
|
||||
dependencies.add("spring-security-oauth2", "spring-boot-starter-web",
|
||||
"spring-boot-starter-security");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyImports(ImportCustomizer imports) throws CompilationFailedException {
|
||||
imports.addImports(
|
||||
"org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso");
|
||||
imports.addStarImports(
|
||||
"org.springframework.security.oauth2.config.annotation.web.configuration",
|
||||
"org.springframework.security.access.prepost");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -71,13 +71,6 @@ public class SampleIntegrationTests {
|
|||
assertThat(output).contains("completed with the following parameters");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauth2Sample() throws Exception {
|
||||
String output = this.cli.run("oauth2.groovy");
|
||||
assertThat(output).contains("security.oauth2.client.client-id");
|
||||
assertThat(output).contains("security.oauth2.client.client-secret =");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jobWebSample() throws Exception {
|
||||
String output = this.cli.run("job.groovy", "web.groovy", "foo=bar");
|
||||
|
|
|
|||
|
|
@ -173,8 +173,6 @@
|
|||
<spring-restdocs.version>1.2.1.RELEASE</spring-restdocs.version>
|
||||
<spring-retry.version>1.2.1.RELEASE</spring-retry.version>
|
||||
<spring-security.version>5.0.0.M4</spring-security.version>
|
||||
<spring-security-jwt.version>1.0.8.RELEASE</spring-security-jwt.version>
|
||||
<spring-security-oauth.version>2.2.0.RELEASE</spring-security-oauth.version>
|
||||
<spring-session.version>2.0.0.M4</spring-session.version>
|
||||
<spring-social.version>2.0.0.M4</spring-social.version>
|
||||
<spring-social-facebook.version>3.0.0.M3</spring-social-facebook.version>
|
||||
|
|
@ -2358,21 +2356,6 @@
|
|||
<scope>import</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-jwt</artifactId>
|
||||
<version>${spring-security-jwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth</artifactId>
|
||||
<version>${spring-security-oauth.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
<version>${spring-security-oauth.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-core</artifactId>
|
||||
|
|
|
|||
|
|
@ -772,11 +772,6 @@
|
|||
<artifactId>spring-security-web</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.social</groupId>
|
||||
<artifactId>spring-social-config</artifactId>
|
||||
|
|
|
|||
|
|
@ -476,27 +476,8 @@ content into your application; rather pick only the properties that you need.
|
|||
# SECURITY PROPERTIES
|
||||
# ----------------------------------------
|
||||
# SECURITY ({sc-spring-boot-autoconfigure}/security/SecurityProperties.{sc-ext}[SecurityProperties])
|
||||
spring.security.filter.order=0 # Security filter chain order.
|
||||
spring.security.filter.dispatcher-types=ASYNC,ERROR,REQUEST # Security filter chain dispatcher types.
|
||||
|
||||
# SECURITY OAUTH2 CLIENT ({sc-spring-boot-autoconfigure}/security/oauth2/OAuth2ClientProperties.{sc-ext}[OAuth2ClientProperties])
|
||||
security.oauth2.client.client-id= # OAuth2 client id.
|
||||
security.oauth2.client.client-secret= # OAuth2 client secret. A random secret is generated by default
|
||||
|
||||
# SECURITY OAUTH2 RESOURCES ({sc-spring-boot-autoconfigure}/security/oauth2/resource/ResourceServerProperties.{sc-ext}[ResourceServerProperties])
|
||||
security.oauth2.resource.id= # Identifier of the resource.
|
||||
security.oauth2.resource.jwt.key-uri= # The URI of the JWT token. Can be set if the value is not available and the key is public.
|
||||
security.oauth2.resource.jwt.key-value= # The verification key of the JWT token. Can either be a symmetric secret or PEM-encoded RSA public key.
|
||||
security.oauth2.resource.jwk.key-set-uri= # The URI for getting the set of keys that can be used to validate the token.
|
||||
security.oauth2.resource.prefer-token-info=true # Use the token info, can be set to false to use the user info.
|
||||
security.oauth2.resource.service-id=resource #
|
||||
security.oauth2.resource.token-info-uri= # URI of the token decoding endpoint.
|
||||
security.oauth2.resource.token-type= # The token type to send when using the userInfoUri.
|
||||
security.oauth2.resource.user-info-uri= # URI of the user endpoint.
|
||||
|
||||
# SECURITY OAUTH2 SSO ({sc-spring-boot-autoconfigure}/security/oauth2/client/OAuth2SsoProperties.{sc-ext}[OAuth2SsoProperties])
|
||||
security.oauth2.sso.login-path=/login # Path to the login page, i.e. the one that triggers the redirect to the OAuth2 Authorization Server
|
||||
|
||||
spring.security.filter.order=0 # Security filter chain order.
|
||||
spring.security.filter.dispatcher-types=ASYNC,ERROR,REQUEST # Security filter chain dispatcher types.
|
||||
|
||||
# ----------------------------------------
|
||||
# DATA PROPERTIES
|
||||
|
|
|
|||
|
|
@ -2752,238 +2752,6 @@ explicitly configure the paths that you do want to override.
|
|||
|
||||
|
||||
|
||||
[[boot-features-security-oauth2]]
|
||||
=== OAuth2
|
||||
If you have `spring-security-oauth2` on your classpath you can take advantage of some
|
||||
auto-configuration to make it easy to set up Authorization or Resource Server. For full
|
||||
details, see the {spring-security-oauth2-reference}[Spring Security OAuth 2 Developers
|
||||
Guide].
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-oauth2-authorization-server]]
|
||||
==== Authorization Server
|
||||
To create an Authorization Server and grant access tokens you need to use
|
||||
`@EnableAuthorizationServer` and provide `security.oauth2.client.client-id` and
|
||||
`security.oauth2.client.client-secret]` properties. The client will be registered for you
|
||||
in an in-memory repository.
|
||||
|
||||
Having done that you will be able to use the client credentials to create an access token,
|
||||
for example:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ curl client:secret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=pwd
|
||||
----
|
||||
|
||||
The basic auth credentials for the `/token` endpoint are the `client-id` and
|
||||
`client-secret`. The user credentials are the normal Spring Security user details (which
|
||||
default in Spring Boot to "`user`" and a random password).
|
||||
|
||||
To switch off the auto-configuration and configure the Authorization Server features
|
||||
yourself just add a `@Bean` of type `AuthorizationServerConfigurer`.
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-oauth2-resource-server]]
|
||||
==== Resource Server
|
||||
To use the access token you need a Resource Server (which can be the same as the
|
||||
Authorization Server). Creating a Resource Server is easy, just add
|
||||
`@EnableResourceServer` and provide some configuration to allow the server to decode
|
||||
access tokens. If your application is also an Authorization Server it already knows how
|
||||
to decode tokens, so there is nothing else to do. If your app is a standalone service then you
|
||||
need to give it some more configuration, one of the following options:
|
||||
|
||||
* `security.oauth2.resource.user-info-uri` to use the `/me` resource (e.g.
|
||||
`\https://uaa.run.pivotal.io/userinfo` on Pivotal Web Services (PWS))
|
||||
|
||||
* `security.oauth2.resource.token-info-uri` to use the token decoding endpoint (e.g.
|
||||
`\https://uaa.run.pivotal.io/check_token` on PWS).
|
||||
|
||||
If you specify both the `user-info-uri` and the `token-info-uri` then you can set a flag
|
||||
to say that one is preferred over the other (`prefer-token-info=true` is the default).
|
||||
|
||||
Alternatively (instead of `user-info-uri` or `token-info-uri`) if the tokens are JWTs you
|
||||
can configure a `security.oauth2.resource.jwt.key-value` to decode them locally (where the
|
||||
key is a verification key). The verification key value is either a symmetric secret or
|
||||
PEM-encoded RSA public key. If you don't have the key and it's public you can provide a
|
||||
URI where it can be downloaded (as a JSON object with a "`value`" field) with
|
||||
`security.oauth2.resource.jwt.key-uri`. E.g. on PWS:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ curl https://uaa.run.pivotal.io/token_key
|
||||
{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}
|
||||
----
|
||||
|
||||
Additionally, if your authorization server has an endpoint that returns a set of JSON Web Keys(JWKs),
|
||||
you can configure `security.oauth2.resource.jwk.key-set-uri`. E.g. on PWS:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ curl https://uaa.run.pivotal.io/token_keys
|
||||
{"keys":[{"kid":"key-1","alg":"RS256","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"]}
|
||||
----
|
||||
|
||||
NOTE: Configuring both JWT and JWK properties will cause an error. Only one of `security.oauth2.resource.jwt.key-uri`
|
||||
(or `security.oauth2.resource.jwt.key-value`) and `security.oauth2.resource.jwk.key-set-uri` should be configured.
|
||||
|
||||
WARNING: If you use the `security.oauth2.resource.jwt.key-uri` or `security.oauth2.resource.jwk.key-set-uri`,
|
||||
the authorization server needs to be running when your application starts up. It will log a warning if it can't
|
||||
find the key, and tell you what to do to fix it.
|
||||
|
||||
OAuth2 resources are protected by a filter chain with order
|
||||
`security.oauth2.resource.filter-order` and the default is after the filter protecting the
|
||||
actuator endpoints by default (so actuator endpoints will stay on HTTP Basic unless you
|
||||
change the order).
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-oauth2-token-type]]
|
||||
=== Token Type in User Info
|
||||
Google, and certain other 3rd party identity providers, are more strict about the token
|
||||
type name that is sent in the headers to the user info endpoint. The default is "`Bearer`"
|
||||
which suits most providers and matches the spec, but if you need to change it you can set
|
||||
`security.oauth2.resource.token-type`.
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-custom-user-info]]
|
||||
=== Customizing the User Info RestTemplate
|
||||
If you have a `user-info-uri`, the resource server features use an `OAuth2RestTemplate`
|
||||
internally to fetch user details for authentication. This is provided as a `@Bean` of
|
||||
type `UserInfoRestTemplateFactory`. 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). To add a
|
||||
customization just create a bean of type `UserInfoRestTemplateCustomizer` - it has a
|
||||
single method that will be called after the bean is created but before it is initialized.
|
||||
The rest template that is being customized here is _only_ used internally to carry out
|
||||
authentication. Alternatively, you could define your own `UserInfoRestTemplateFactory`
|
||||
`@Bean` to take full control.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
To set an RSA key value in YAML use the "`pipe`" continuation marker to split it over
|
||||
multiple lines ("`|`") and remember to indent the key value (it's a standard YAML
|
||||
language feature). Example:
|
||||
|
||||
[source,yaml,indent=0]
|
||||
----
|
||||
security:
|
||||
oauth2:
|
||||
resource:
|
||||
jwt:
|
||||
keyValue: |
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...
|
||||
-----END PUBLIC KEY-----
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-custom-user-info-client]]
|
||||
==== Client
|
||||
To make your web-app into an OAuth2 client you can simply add `@EnableOAuth2Client` and
|
||||
Spring Boot will create an `OAuth2ClientContext` and `OAuth2ProtectedResourceDetails` that
|
||||
are necessary to create an `OAuth2RestOperations`. Spring Boot does not automatically
|
||||
create such bean but you can easily create your own:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
||||
@Bean
|
||||
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
|
||||
OAuth2ProtectedResourceDetails details) {
|
||||
return new OAuth2RestTemplate(details, oauth2ClientContext);
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: You may want to add a qualifier and review your configuration as more than one
|
||||
`RestTemplate` may be defined in your application.
|
||||
|
||||
This configuration uses `security.oauth2.client.*` as credentials (the same as you might
|
||||
be using in the Authorization Server), but in addition it will need to know the
|
||||
authorization and token URIs in the Authorization Server. For example:
|
||||
|
||||
.application.yml
|
||||
[source,yaml,indent=0]
|
||||
----
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
clientId: bd1c0a783ccdd1c9b9e4
|
||||
clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
|
||||
accessTokenUri: https://github.com/login/oauth/access_token
|
||||
userAuthorizationUri: https://github.com/login/oauth/authorize
|
||||
clientAuthenticationScheme: form
|
||||
----
|
||||
|
||||
An application with this configuration will redirect to Github for authorization when you
|
||||
attempt to use the `OAuth2RestTemplate`. If you are already signed into Github you won't
|
||||
even notice that it has authenticated. These specific credentials will only work if your
|
||||
application is running on port 8080 (register your own client app in Github or other
|
||||
provider for more flexibility).
|
||||
|
||||
To limit the scope that the client asks for when it obtains an access token you can set
|
||||
`security.oauth2.client.scope` (comma separated or an array in YAML). By default the scope
|
||||
is empty and it is up to Authorization Server to decide what the defaults should be,
|
||||
usually depending on the settings in the client registration that it holds.
|
||||
|
||||
NOTE: There is also a setting for `security.oauth2.client.client-authentication-scheme`
|
||||
which defaults to "`header`" (but you might need to set it to "`form`" if, like Github for
|
||||
instance, your OAuth2 provider doesn't like header authentication). In fact, the
|
||||
`security.oauth2.client.*` properties are bound to an instance of
|
||||
`AuthorizationCodeResourceDetails` so all its properties can be specified.
|
||||
|
||||
TIP: In a non-web application you can still create an `OAuth2RestOperations` and it
|
||||
is still wired into the `security.oauth2.client.*` configuration. In this case it is a
|
||||
"`client credentials token grant`" you will be asking for if you use it (and there is no
|
||||
need to use `@EnableOAuth2Client` or `@EnableOAuth2Sso`). To prevent that infrastructure
|
||||
to be defined, just remove the `security.oauth2.client.client-id` from your configuration
|
||||
(or make it the empty string).
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-oauth2-single-sign-on]]
|
||||
==== Single Sign On
|
||||
An OAuth2 Client can be used to fetch user details from the provider (if such features are
|
||||
available) and then convert them into an `Authentication` token for Spring Security.
|
||||
The Resource Server above support this via the `user-info-uri` property This is the basis
|
||||
for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to
|
||||
participate by providing an annotation `@EnableOAuth2Sso`. The Github client above can
|
||||
protect all its resources and authenticate using the Github `/user/` endpoint, by adding
|
||||
that annotation and declaring where to find the endpoint (in addition to the
|
||||
`security.oauth2.client.*` configuration already listed above):
|
||||
|
||||
.application.yml
|
||||
[source,yaml,indent=0]]
|
||||
----
|
||||
security:
|
||||
oauth2:
|
||||
...
|
||||
resource:
|
||||
userInfoUri: https://api.github.com/user
|
||||
preferTokenInfo: false
|
||||
----
|
||||
|
||||
Since all paths are secure by default, there is no "`home`" page that you can show to
|
||||
unauthenticated users and invite them to login (by visiting the `/login` path, or the
|
||||
path specified by `security.oauth2.sso.login-path`).
|
||||
|
||||
To customize the access rules or paths to protect, so you can add a "`home`" page for
|
||||
instance, `@EnableOAuth2Sso` can be added to a `WebSecurityConfigurerAdapter` and the
|
||||
annotation will cause it to be decorated and enhanced with the necessary pieces to get
|
||||
the `/login` path working. For example, here we simply allow unauthenticated access
|
||||
to the home page at "/" and keep the default for everything else:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::{code-examples}/web/security/UnauthenticatedAccessExample.java[tag=configuration]
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[boot-features-security-actuator]]
|
||||
=== Actuator Security
|
||||
If the Actuator is also in use, you will find:
|
||||
|
|
|
|||
|
|
@ -146,15 +146,6 @@ The following sample applications are provided:
|
|||
| link:spring-boot-sample-secure[spring-boot-sample-secure]
|
||||
| Non-web application that uses Spring Security
|
||||
|
||||
| link:spring-boot-sample-secure-oauth2-actuator[spring-boot-sample-secure-oauth2-actuator]
|
||||
| RESTful service secured using OAuth2 and Actuator
|
||||
|
||||
| link:spring-boot-sample-secure-oauth2[spring-boot-sample-secure-oauth2]
|
||||
| RESTful service secured using OAuth2
|
||||
|
||||
| link:spring-boot-sample-secure-oauth2-resource[spring-boot-sample-secure-oauth2-resource]
|
||||
| OAuth2 resource server
|
||||
|
||||
| link:spring-boot-sample-servlet[spring-boot-sample-servlet]
|
||||
| Web application with a "raw" `Servlet` returning plain text content
|
||||
|
||||
|
|
@ -215,9 +206,6 @@ The following sample applications are provided:
|
|||
| link:spring-boot-sample-web-secure-custom[spring-boot-sample-web-secure-custom]
|
||||
| Web application with custom Spring Security configuration
|
||||
|
||||
| link:spring-boot-sample-web-secure-github[spring-boot-sample-web-secure-github]
|
||||
| Web application with Spring Security configured to authenticate with GitHub using OAuth2
|
||||
|
||||
| link:spring-boot-sample-web-secure-jdbc[spring-boot-sample-web-secure-jdbc]
|
||||
| Web application with Spring Security configured to use JDBC authentication
|
||||
|
||||
|
|
|
|||
|
|
@ -67,9 +67,6 @@
|
|||
<module>spring-boot-sample-property-validation</module>
|
||||
<module>spring-boot-sample-quartz</module>
|
||||
<module>spring-boot-sample-secure</module>
|
||||
<module>spring-boot-sample-secure-oauth2</module>
|
||||
<module>spring-boot-sample-secure-oauth2-actuator</module>
|
||||
<module>spring-boot-sample-secure-oauth2-resource</module>
|
||||
<module>spring-boot-sample-servlet</module>
|
||||
<module>spring-boot-sample-session</module>
|
||||
<module>spring-boot-sample-simple</module>
|
||||
|
|
@ -91,7 +88,6 @@
|
|||
<module>spring-boot-sample-web-mustache</module>
|
||||
<module>spring-boot-sample-web-secure</module>
|
||||
<module>spring-boot-sample-web-secure-custom</module>
|
||||
<module>spring-boot-sample-web-secure-github</module>
|
||||
<module>spring-boot-sample-web-secure-jdbc</module>
|
||||
<module>spring-boot-sample-web-static</module>
|
||||
<module>spring-boot-sample-web-ui</module>
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-secure-oauth2-actuator</artifactId>
|
||||
<name>spring-boot-sample-secure-oauth2-actuator</name>
|
||||
<description>Spring Boot Security OAuth2 Actuator Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package sample.secure.oauth2.actuator;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.security.EndpointRequest;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* Basic auth security for actuator endpoints.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
@Order(2) // before the resource server configuration
|
||||
public class ActuatorSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests()
|
||||
.antMatchers("/**").authenticated()
|
||||
.and()
|
||||
.httpBasic();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2.actuator;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableResourceServer
|
||||
@RestController
|
||||
public class SampleSecureOAuth2ActuatorApplication {
|
||||
|
||||
@GetMapping("/")
|
||||
public Message home() {
|
||||
return new Message("Hello World");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDetailsService userDetailsService() throws Exception {
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager.createUser(
|
||||
User.withUsername("user").password("password").roles("USER").build());
|
||||
return manager;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleSecureOAuth2ActuatorApplication.class, args);
|
||||
}
|
||||
|
||||
class Message {
|
||||
|
||||
private String id = UUID.randomUUID().toString();
|
||||
|
||||
private String value;
|
||||
|
||||
public Message(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2.actuator;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
|
||||
|
||||
/**
|
||||
* Series of automated integration tests to verify proper behavior of auto-configured,
|
||||
* OAuth2-secured system
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class SampleSecureOAuth2ActuatorApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private FilterChainProxy filterChain;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mvc = webAppContextSetup(this.context).addFilters(this.filterChain).build();
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homePageSecuredByDefault() throws Exception {
|
||||
this.mvc.perform(get("/")).andExpect(status().isUnauthorized())
|
||||
.andExpect(header().string("WWW-Authenticate", containsString("Bearer")))
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void healthSecured() throws Exception {
|
||||
this.mvc.perform(get("/application/health")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void healthWithBasicAuthorization() throws Exception {
|
||||
this.mvc.perform(get("/application/health").header("Authorization",
|
||||
"Basic " + Base64Utils.encodeToString("user:password".getBytes())))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void envSecured() throws Exception {
|
||||
this.mvc.perform(get("/application/env")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void envWithBasicAuthorization() throws Exception {
|
||||
this.mvc.perform(get("/application/env").header("Authorization",
|
||||
"Basic " + Base64Utils.encodeToString("user:password".getBytes())))
|
||||
.andExpect(status().isOk()).andDo(print());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-secure-oauth2-resource</artifactId>
|
||||
<name>spring-boot-sample-secure-oauth2-resource</name>
|
||||
<description>Spring Boot Security OAuth2 Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2.resource;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/**
|
||||
* Domain object for tracking flights
|
||||
*
|
||||
* @author Craig Walls
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Entity
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Flight {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String origin;
|
||||
|
||||
private String destination;
|
||||
|
||||
private String airline;
|
||||
|
||||
private String flightNumber;
|
||||
|
||||
private Date date;
|
||||
|
||||
private String traveler;
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOrigin() {
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
public void setOrigin(String origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public String getAirline() {
|
||||
return this.airline;
|
||||
}
|
||||
|
||||
public void setAirline(String airline) {
|
||||
this.airline = airline;
|
||||
}
|
||||
|
||||
public String getFlightNumber() {
|
||||
return this.flightNumber;
|
||||
}
|
||||
|
||||
public void setFlightNumber(String flightNumber) {
|
||||
this.flightNumber = flightNumber;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getTraveler() {
|
||||
return this.traveler;
|
||||
}
|
||||
|
||||
public void setTraveler(String traveler) {
|
||||
this.traveler = traveler;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2.resource;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* Spring Data interface with secured methods
|
||||
*
|
||||
* @author Craig Walls
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface FlightRepository extends CrudRepository<Flight, Long> {
|
||||
|
||||
@Override
|
||||
Iterable<Flight> findAll();
|
||||
|
||||
@Override
|
||||
Optional<Flight> findById(Long aLong);
|
||||
|
||||
@Override
|
||||
<S extends Flight> S save(S entity);
|
||||
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2.resource;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableResourceServer
|
||||
public class SampleSecureOAuth2ResourceApplication
|
||||
extends ResourceServerConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http.antMatcher("/flights/**").authorizeRequests().anyRequest().authenticated();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleSecureOAuth2ResourceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
insert into FLIGHT
|
||||
(id, origin, destination, airline, flight_number, traveler)
|
||||
values
|
||||
(1, 'Nashville', 'Dallas', 'Spring Ways', 'OAUTH2', 'Greg Turnquist');
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2.resource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
|
||||
|
||||
/**
|
||||
* Series of automated integration tests to verify proper behavior of auto-configured,
|
||||
* OAuth2-secured system
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class SampleSecureOAuth2ResourceApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private FilterChainProxy filterChain;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mvc = webAppContextSetup(this.context).addFilters(this.filterChain).build();
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void homePageAvailable() throws Exception {
|
||||
this.mvc.perform(get("/").accept(MediaTypes.HAL_JSON)).andExpect(status().isOk())
|
||||
.andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flightsSecuredByDefault() throws Exception {
|
||||
this.mvc.perform(get("/flights").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isUnauthorized()).andDo(print());
|
||||
this.mvc.perform(get("/flights/1").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isUnauthorized()).andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void profileAvailable() throws Exception {
|
||||
this.mvc.perform(get("/profile").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isOk()).andDo(print());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-secure-oauth2</artifactId>
|
||||
<name>Spring Boot Security OAuth2 Sample</name>
|
||||
<description>Spring Boot Security OAuth2 Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package sample.secure.oauth2;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Configuration
|
||||
public class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void init(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication().withUser("greg").password("turnquist")
|
||||
.roles("read");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package sample.secure.oauth2;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/**
|
||||
* Domain object for tracking flights
|
||||
*
|
||||
* @author Craig Walls
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@Entity
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Flight {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String origin;
|
||||
|
||||
private String destination;
|
||||
|
||||
private String airline;
|
||||
|
||||
private String flightNumber;
|
||||
|
||||
private Date date;
|
||||
|
||||
private String traveler;
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getOrigin() {
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
public void setOrigin(String origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return this.destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public String getAirline() {
|
||||
return this.airline;
|
||||
}
|
||||
|
||||
public void setAirline(String airline) {
|
||||
this.airline = airline;
|
||||
}
|
||||
|
||||
public String getFlightNumber() {
|
||||
return this.flightNumber;
|
||||
}
|
||||
|
||||
public void setFlightNumber(String flightNumber) {
|
||||
this.flightNumber = flightNumber;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getTraveler() {
|
||||
return this.traveler;
|
||||
}
|
||||
|
||||
public void setTraveler(String traveler) {
|
||||
this.traveler = traveler;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
/**
|
||||
* Spring Data interface with secured methods
|
||||
*
|
||||
* @author Craig Walls
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface FlightRepository extends CrudRepository<Flight, Long> {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("#oauth2.hasScope('read')")
|
||||
Iterable<Flight> findAll();
|
||||
|
||||
@Override
|
||||
@PreAuthorize("#oauth2.hasScope('read')")
|
||||
Optional<Flight> findById(Long aLong);
|
||||
|
||||
@Override
|
||||
@PreAuthorize("#oauth2.hasScope('write')")
|
||||
<S extends Flight> S save(S entity);
|
||||
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
|
||||
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* After you launch the app, you can seek a bearer token like this:
|
||||
*
|
||||
* <pre>
|
||||
* curl localhost:8080/oauth/token -d "grant_type=password&scope=read&username=greg&password=turnquist" -u foo:bar
|
||||
* </pre>
|
||||
*
|
||||
* <ul>
|
||||
* <li>grant_type=password (user credentials will be supplied)</li>
|
||||
* <li>scope=read (read only scope)</li>
|
||||
* <li>username=greg (username checked against user details service)</li>
|
||||
* <li>password=turnquist (password checked against user details service)</li>
|
||||
* <li>-u foo:bar (clientid:secret)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Response should be similar to this:
|
||||
* <code>{"access_token":"533de99b-5a0f-4175-8afd-1a64feb952d5","token_type":"bearer","expires_in":43199,"scope":"read"}</code>
|
||||
*
|
||||
* With the token value, you can now interrogate the RESTful interface like this:
|
||||
*
|
||||
* <pre>
|
||||
* curl -H "Authorization: bearer [access_token]" localhost:8080/flights/1
|
||||
* </pre>
|
||||
*
|
||||
* You should then see the pre-loaded data like this:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "origin" : "Nashville",
|
||||
* "destination" : "Dallas",
|
||||
* "airline" : "Spring Ways",
|
||||
* "flightNumber" : "OAUTH2",
|
||||
* "date" : null,
|
||||
* "traveler" : "Greg Turnquist",
|
||||
* "_links" : {
|
||||
* "self" : {
|
||||
* "href" : "http://localhost:8080/flights/1"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Test creating a new entry:
|
||||
*
|
||||
* <pre>
|
||||
* curl -i -H "Authorization: bearer [access token]" -H "Content-Type:application/json" localhost:8080/flights -X POST -d @flight.json
|
||||
* </pre>
|
||||
*
|
||||
* Insufficient scope? (read not write) Ask for a new token!
|
||||
*
|
||||
* <pre>
|
||||
* curl localhost:8080/oauth/token -d "grant_type=password&scope=write&username=greg&password=turnquist" -u foo:bar
|
||||
*
|
||||
* {"access_token":"cfa69736-e2aa-4ae7-abbb-3085acda560e","token_type":"bearer","expires_in":43200,"scope":"write"}
|
||||
* </pre>
|
||||
*
|
||||
* Retry with the new token. There should be a Location header.
|
||||
*
|
||||
* <pre>
|
||||
* Location: http://localhost:8080/flights/2
|
||||
*
|
||||
* curl -H "Authorization: bearer [access token]" localhost:8080/flights/2
|
||||
* </pre>
|
||||
*
|
||||
* @author Craig Walls
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableAuthorizationServer
|
||||
@EnableResourceServer
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@RestController
|
||||
public class SampleSecureOAuth2Application {
|
||||
|
||||
@GetMapping("/user")
|
||||
public Principal user(Principal user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleSecureOAuth2Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
spring.datasource.platform=h2
|
||||
|
||||
security.oauth2.client.client-id=foo
|
||||
security.oauth2.client.client-secret=bar
|
||||
security.oauth2.authorization.checkTokenAccess=isAuthenticated()
|
||||
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
insert into FLIGHT
|
||||
(id, origin, destination, airline, flight_number, traveler)
|
||||
values
|
||||
(1, 'Nashville', 'Dallas', 'Spring Ways', 'OAUTH2', 'Greg Turnquist');
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* 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 sample.secure.oauth2;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
|
||||
|
||||
/**
|
||||
* Series of automated integration tests to verify proper behavior of auto-configured,
|
||||
* OAuth2-secured system
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class SampleSecureOAuth2ApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
private FilterChainProxy filterChain;
|
||||
|
||||
private MockMvc mvc;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.mvc = webAppContextSetup(this.context).addFilters(this.filterChain).build();
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void everythingIsSecuredByDefault() throws Exception {
|
||||
this.mvc.perform(get("/").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isUnauthorized()).andDo(print());
|
||||
this.mvc.perform(get("/flights").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isUnauthorized()).andDo(print());
|
||||
this.mvc.perform(get("/flights/1").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isUnauthorized()).andDo(print());
|
||||
this.mvc.perform(get("/alps").accept(MediaTypes.HAL_JSON))
|
||||
.andExpect(status().isUnauthorized()).andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void accessingRootUriPossibleWithUserAccount() throws Exception {
|
||||
String header = "Basic "
|
||||
+ new String(Base64.getEncoder().encode("greg:turnquist".getBytes()));
|
||||
this.mvc.perform(
|
||||
get("/").accept(MediaTypes.HAL_JSON).header("Authorization", header))
|
||||
.andExpect(
|
||||
header().string("Content-Type", MediaTypes.HAL_JSON.toString()))
|
||||
.andExpect(status().isOk()).andDo(print());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useAppSecretsPlusUserAccountToGetBearerToken() throws Exception {
|
||||
String header = "Basic "
|
||||
+ new String(Base64.getEncoder().encode("foo:bar".getBytes()));
|
||||
MvcResult result = this.mvc
|
||||
.perform(post("/oauth/token").header("Authorization", header)
|
||||
.param("grant_type", "password").param("scope", "read")
|
||||
.param("username", "greg").param("password", "turnquist"))
|
||||
.andExpect(status().isOk()).andDo(print()).andReturn();
|
||||
Object accessToken = this.objectMapper
|
||||
.readValue(result.getResponse().getContentAsString(), Map.class)
|
||||
.get("access_token");
|
||||
MvcResult flightsAction = this.mvc
|
||||
.perform(get("/flights/1").accept(MediaTypes.HAL_JSON)
|
||||
.header("Authorization", "Bearer " + accessToken))
|
||||
.andExpect(header().string("Content-Type",
|
||||
MediaTypes.HAL_JSON.toString() + ";charset=UTF-8"))
|
||||
.andExpect(status().isOk()).andDo(print()).andReturn();
|
||||
|
||||
Flight flight = this.objectMapper.readValue(
|
||||
flightsAction.getResponse().getContentAsString(), Flight.class);
|
||||
|
||||
assertThat(flight.getOrigin()).isEqualTo("Nashville");
|
||||
assertThat(flight.getDestination()).isEqualTo("Dallas");
|
||||
assertThat(flight.getAirline()).isEqualTo("Spring Ways");
|
||||
assertThat(flight.getFlightNumber()).isEqualTo("OAUTH2");
|
||||
assertThat(flight.getTraveler()).isEqualTo("Greg Turnquist");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-samples</artifactId>
|
||||
<version>2.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-sample-web-secure-github</artifactId>
|
||||
<name>Spring Boot Web Secure GitHub Sample</name>
|
||||
<description>Spring Boot Web Secure GitHub Sample</description>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Compile -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth</groupId>
|
||||
<artifactId>spring-security-oauth2</artifactId>
|
||||
</dependency>
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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 sample.web.secure.github;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableOAuth2Sso
|
||||
public class SampleGithubSecureApplication implements WebMvcConfigurer {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(SampleGithubSecureApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
security:
|
||||
oauth2:
|
||||
client:
|
||||
clientId: bd1c0a783ccdd1c9b9e4
|
||||
clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
|
||||
accessTokenUri: https://github.com/login/oauth/access_token
|
||||
userAuthorizationUri: https://github.com/login/oauth/authorize
|
||||
clientAuthenticationScheme: form
|
||||
resource:
|
||||
userInfoUri: https://api.github.com/user
|
||||
preferTokenInfo: false
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<a class="brand" href="http://spring.io"> Spring </a>
|
||||
<ul class="nav">
|
||||
<li><a href="/"> Home </a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Super Special Greeting</h1>
|
||||
<div>Hello World</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* 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 sample.web.secure.github;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Basic integration tests for GitHub SSO application.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
public class SampleGithubApplicationTests {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void everythingIsSecuredByDefault() throws Exception {
|
||||
ResponseEntity<Void> entity = this.restTemplate.getForEntity("/", Void.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
|
||||
assertThat(entity.getHeaders().getLocation())
|
||||
.isEqualTo(URI.create("http://localhost:" + this.port + "/login"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginRedirectsToGithub() throws Exception {
|
||||
ResponseEntity<Void> entity = this.restTemplate.getForEntity("/login",
|
||||
Void.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND);
|
||||
assertThat(entity.getHeaders().getLocation().toString())
|
||||
.startsWith("https://github.com/login/oauth");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue