From e05a6aa68aaed67697c0cac972d2088a9d46afc4 Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Fri, 19 May 2023 12:45:47 +0200 Subject: [PATCH 1/6] feat: Configurable OAuth2 login page --- .../security/AuthorizationProperties.java | 12 +++---- .../security/OAuthSecurityConfiguration.java | 31 +++++++++---------- .../rest/resource/about/SecurityInfo.java | 14 +++++++++ .../security/SecurityInfoResource.java | 14 +++++++++ .../DataFlowControllerAutoConfiguration.java | 15 ++++++--- .../server/controller/AboutController.java | 22 ++++++++++++- .../security/SecurityController.java | 24 ++++++++++++-- .../configuration/TestDependencies.java | 8 +++-- 8 files changed, 106 insertions(+), 34 deletions(-) diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java index 8efea5f00e..d74f24d5d3 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java @@ -36,9 +36,9 @@ public class AuthorizationProperties { private String dashboardUrl = "/dashboard"; - private String loginUrl = "/#/login"; + private String loginUrl = "/login"; - private String loginProcessingUrl = "/login"; + private String loginSuccessUrl = dashboardUrl; private String logoutUrl = "/logout"; @@ -91,12 +91,12 @@ public void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } - public String getLoginProcessingUrl() { - return loginProcessingUrl; + public String getLoginSuccessUrl() { + return loginSuccessUrl; } - public void setLoginProcessingUrl(String loginProcessingUrl) { - this.loginProcessingUrl = loginProcessingUrl; + public void setLoginSuccessUrl(String loginSuccessUrl) { + this.loginSuccessUrl = loginSuccessUrl; } public String getLogoutUrl() { diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java index d10b25a9cd..87d0bd6a7f 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java @@ -216,28 +216,23 @@ protected void configure(HttpSecurity http) throws Exception { http.addFilter(basicAuthenticationFilter); } - this.authorizationProperties.getAuthenticatedPaths().add("/"); - this.authorizationProperties.getAuthenticatedPaths() - .add(dashboard(authorizationProperties, "/**")); - this.authorizationProperties.getAuthenticatedPaths() - .add(this.authorizationProperties.getDashboardUrl()); - this.authorizationProperties.getPermitAllPaths() - .add(this.authorizationProperties.getDashboardUrl()); - this.authorizationProperties.getPermitAllPaths() - .add(dashboard(authorizationProperties, "/**")); - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = + //this.authorizationProperties.getAuthenticatedPaths().add("/"); + //this.authorizationProperties.getAuthenticatedPaths().add(this.authorizationProperties.getDashboardUrl()); + //this.authorizationProperties.getAuthenticatedPaths().add(dashboard(authorizationProperties, "/**")); + + this.authorizationProperties.getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl()); + this.authorizationProperties.getPermitAllPaths().add(dashboard(authorizationProperties, "/**")); + this.authorizationProperties.getPermitAllPaths().add(authorizationProperties.getLoginUrl()); + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = http.authorizeRequests() - .antMatchers(this.authorizationProperties.getPermitAllPaths() - .toArray(new String[0])) + .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) .permitAll() - .antMatchers(this.authorizationProperties.getAuthenticatedPaths() - .toArray(new String[0])) + .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) .authenticated(); security = SecurityConfigUtils.configureSimpleSecurity(security, this.authorizationProperties); security.anyRequest().denyAll(); - http.httpBasic().and() .logout() .logoutSuccessHandler(logoutSuccessHandler) @@ -248,11 +243,13 @@ protected void configure(HttpSecurity http) throws Exception { new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest")) .defaultAuthenticationEntryPointFor( - new LoginUrlAuthenticationEntryPoint(this.authorizationProperties.getLoginProcessingUrl()), + new LoginUrlAuthenticationEntryPoint(this.authorizationProperties.getLoginUrl()), textHtmlMatcher) .defaultAuthenticationEntryPointFor(basicAuthenticationEntryPoint, AnyRequestMatcher.INSTANCE); - http.oauth2Login().userInfoEndpoint() + http.oauth2Login() + .defaultSuccessUrl(authorizationProperties.getLoginSuccessUrl()) + .userInfoEndpoint() .userService(this.plainOauth2UserService) .oidcUserService(this.oidcUserService); diff --git a/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/about/SecurityInfo.java b/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/about/SecurityInfo.java index 42fa84f9e9..81c9b1dae2 100644 --- a/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/about/SecurityInfo.java +++ b/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/about/SecurityInfo.java @@ -35,6 +35,8 @@ public class SecurityInfo { private List roles = new ArrayList<>(0); + private List clientRegistrations = new ArrayList<>(0); + /** * Default constructor for serialization frameworks. */ @@ -85,6 +87,18 @@ public void setRoles(List roles) { this.roles = roles; } + /** + * + * @return List of all available client registrations + */ + public List getClientRegistrations() { + return clientRegistrations; + } + + public void setClientRegistrations(List clientRegistrations) { + this.clientRegistrations = clientRegistrations; + } + /** * @param role Adds the role to {@link #roles} * @return the security related meta-information diff --git a/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/security/SecurityInfoResource.java b/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/security/SecurityInfoResource.java index f9e36d621a..e3329abb08 100644 --- a/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/security/SecurityInfoResource.java +++ b/spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/resource/security/SecurityInfoResource.java @@ -37,6 +37,8 @@ public class SecurityInfoResource extends RepresentationModel { private List roles = new ArrayList<>(0); + private List clientRegistrations = new ArrayList<>(0); + /** * Default constructor for serialization frameworks. */ @@ -87,6 +89,18 @@ public void setRoles(List roles) { this.roles = roles; } + /** + * + * @return List of all available client registrations + */ + public List getClientRegistrations() { + return clientRegistrations; + } + + public void setClientRegistrations(List clientRegistrations) { + this.clientRegistrations = clientRegistrations; + } + /** * @param role Adds the role to {@link #roles} * @return the resource with an additional role diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java index ea7a9e90ce..48958983cd 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java @@ -21,6 +21,8 @@ import java.util.Optional; import java.util.concurrent.ForkJoinPool; +import javax.annotation.Nullable; + import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; @@ -31,6 +33,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -140,7 +143,6 @@ import org.springframework.hateoas.server.core.AnnotationLinkRelationProvider; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.lang.Nullable; import org.springframework.scheduling.concurrent.ForkJoinPoolFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.client.RestTemplate; @@ -198,9 +200,11 @@ public AboutController aboutController(ObjectProvider streamDepl FeaturesProperties featuresProperties, VersionInfoProperties versionInfoProperties, SecurityStateBean securityStateBean, - DataflowMetricsProperties monitoringDashboardInfoProperties) { + DataflowMetricsProperties monitoringDashboardInfoProperties, + @Nullable OAuth2ClientProperties oAuth2ClientProperties) { return new AboutController(streamDeployer.getIfAvailable(), launcherRepository.getIfAvailable(), - featuresProperties, versionInfoProperties, securityStateBean, monitoringDashboardInfoProperties); + featuresProperties, versionInfoProperties, securityStateBean, monitoringDashboardInfoProperties, + oAuth2ClientProperties); } @Bean @@ -542,8 +546,9 @@ public SecurityStateBean securityStateBean() { } @Bean - public SecurityController securityController(SecurityStateBean securityStateBean) { - return new SecurityController(securityStateBean); + public SecurityController securityController(SecurityStateBean securityStateBean, + @Nullable OAuth2ClientProperties oAuth2ClientProperties) { + return new SecurityController(securityStateBean, oAuth2ClientProperties); } @Bean diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java index 5906bcda8d..1d35d4784b 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java @@ -17,6 +17,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; @@ -25,6 +29,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.common.security.support.SecurityStateBean; import org.springframework.cloud.dataflow.core.Launcher; @@ -53,6 +58,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -88,6 +94,8 @@ public class AboutController { private final SecurityStateBean securityStateBean; + private final OAuth2ClientProperties oAuth2ClientProperties; + @Value("${security.oauth2.client.client-id:#{null}}") private String oauthClientId; @@ -102,13 +110,15 @@ public class AboutController { private DataflowMetricsProperties dataflowMetricsProperties; public AboutController(StreamDeployer streamDeployer, LauncherRepository launcherRepository, FeaturesProperties featuresProperties, - VersionInfoProperties versionInfoProperties, SecurityStateBean securityStateBean, DataflowMetricsProperties monitoringProperties) { + VersionInfoProperties versionInfoProperties, SecurityStateBean securityStateBean, DataflowMetricsProperties monitoringProperties, + @Nullable OAuth2ClientProperties oAuth2ClientProperties) { this.streamDeployer = streamDeployer; this.launcherRepository = launcherRepository; this.featuresProperties = featuresProperties; this.versionInfoProperties = versionInfoProperties; this.securityStateBean = securityStateBean; this.dataflowMetricsProperties = monitoringProperties; + this.oAuth2ClientProperties = oAuth2ClientProperties; } /** @@ -147,6 +157,16 @@ public AboutResource getAboutResource() { securityInfo.addRole(grantedAuthority.getAuthority()); } } + + // Apply all client registrations to security infos which are based on authorization_code + if(oAuth2ClientProperties != null) { + List authorizationCodeBasedClientRegistrations = oAuth2ClientProperties.getRegistration() + .entrySet() + .stream() + .filter(entry -> AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(entry.getValue().getAuthorizationGrantType())) + .map(Map.Entry::getKey).collect(Collectors.toList()); + securityInfo.setClientRegistrations(authorizationCodeBasedClientRegistrations); + } } aboutResource.setSecurityInfo(securityInfo); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java index fc5019f5ab..a03d03c293 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java @@ -16,7 +16,14 @@ package org.springframework.cloud.dataflow.server.controller.security; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.cloud.common.security.support.SecurityStateBean; import org.springframework.cloud.dataflow.rest.resource.security.SecurityInfoResource; import org.springframework.hateoas.server.ExposesResourceFor; @@ -25,6 +32,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @@ -52,8 +60,11 @@ public class SecurityController { @Value("${security.oauth2.client.client-id:#{null}}") private String oauthClientId; - public SecurityController(SecurityStateBean securityStateBean) { + private OAuth2ClientProperties oAuth2ClientProperties; + + public SecurityController(SecurityStateBean securityStateBean, @Nullable OAuth2ClientProperties oAuth2ClientProperties) { this.securityStateBean = securityStateBean; + this.oAuth2ClientProperties = oAuth2ClientProperties; } /** @@ -82,11 +93,18 @@ public SecurityInfoResource getSecurityInfo() { final GrantedAuthority grantedAuthority = (GrantedAuthority) authority; securityInfo.addRole(grantedAuthority.getAuthority()); } + } + // Apply all client registrations to security infos which are based on authorization_code + if(oAuth2ClientProperties != null) { + List authorizationCodeBasedClientRegistrations = oAuth2ClientProperties.getRegistration() + .entrySet() + .stream() + .filter(entry -> AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(entry.getValue().getAuthorizationGrantType())) + .map(Map.Entry::getKey).collect(Collectors.toList()); + securityInfo.setClientRegistrations(authorizationCodeBasedClientRegistrations); } } - return securityInfo; } - } diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java index c9fe2709a0..ebc78aa820 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java @@ -26,6 +26,7 @@ import java.util.Optional; import java.util.concurrent.ForkJoinPool; +import javax.annotation.Nullable; import javax.sql.DataSource; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -44,6 +45,7 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -684,7 +686,8 @@ public TaskPlatform taskPlatform(Scheduler scheduler) { @Bean public AboutController aboutController(VersionInfoProperties versionInfoProperties, FeaturesProperties featuresProperties, StreamDeployer streamDeployer, - DataflowMetricsProperties monitoringDashboardInfoProperties) { + DataflowMetricsProperties monitoringDashboardInfoProperties, + @Nullable OAuth2ClientProperties oAuth2ClientProperties) { Launcher launcher = mock(Launcher.class); TaskLauncher taskLauncher = mock(TaskLauncher.class); @@ -705,7 +708,8 @@ public AboutController aboutController(VersionInfoProperties versionInfoProperti return new AboutController(streamDeployer, launcherRepository, featuresProperties, versionInfoProperties, - mock(SecurityStateBean.class), monitoringDashboardInfoProperties); + mock(SecurityStateBean.class), monitoringDashboardInfoProperties, + oAuth2ClientProperties); } From 6df335e2243078318203c87912b19b5882938dbb Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Sat, 20 May 2023 22:49:03 +0200 Subject: [PATCH 2/6] fix: Configurable OAuth2 login page security --- .../common/security/AuthorizationProperties.java | 10 ++++++++++ .../security/OAuthSecurityConfiguration.java | 14 ++++++++++---- .../controller/security/SecurityController.java | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java index d74f24d5d3..e722ea9835 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java @@ -48,6 +48,8 @@ public class AuthorizationProperties { private List authenticatedPaths = new ArrayList<>(); + private List anonymousPaths = new ArrayList<>(0); + /** * Role-mapping configuration per OAuth2 provider. */ @@ -131,6 +133,14 @@ public void setAuthenticatedPaths(List authenticatedPaths) { this.authenticatedPaths = authenticatedPaths; } + public List getAnonymousPaths() { + return anonymousPaths; + } + + public void setAnonymousPaths(List anonymousPaths) { + this.anonymousPaths = anonymousPaths; + } + public void setDefaultProviderId(String defaultProviderId) { this.defaultProviderId = defaultProviderId; } diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java index 87d0bd6a7f..ab824674a4 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java @@ -216,16 +216,22 @@ protected void configure(HttpSecurity http) throws Exception { http.addFilter(basicAuthenticationFilter); } - //this.authorizationProperties.getAuthenticatedPaths().add("/"); - //this.authorizationProperties.getAuthenticatedPaths().add(this.authorizationProperties.getDashboardUrl()); - //this.authorizationProperties.getAuthenticatedPaths().add(dashboard(authorizationProperties, "/**")); + // Anonymous paths for the login page + this.authorizationProperties.getAnonymousPaths().add(authorizationProperties.getLoginUrl()); + // All paths should be available only for authenticated users + this.authorizationProperties.getAuthenticatedPaths().add("/"); + this.authorizationProperties.getAuthenticatedPaths().add(this.authorizationProperties.getDashboardUrl()); + this.authorizationProperties.getAuthenticatedPaths().add(dashboard(authorizationProperties, "/*/**")); + + // Permit for all users as the visibility is managed through roles this.authorizationProperties.getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl()); this.authorizationProperties.getPermitAllPaths().add(dashboard(authorizationProperties, "/**")); - this.authorizationProperties.getPermitAllPaths().add(authorizationProperties.getLoginUrl()); ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = http.authorizeRequests() + .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) + .anonymous() .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) .permitAll() .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java index a03d03c293..4c3dfff972 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/security/SecurityController.java @@ -85,7 +85,7 @@ public SecurityInfoResource getSecurityInfo() { if (authenticationEnabled && SecurityContextHolder.getContext() != null) { final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (!(authentication instanceof AnonymousAuthenticationToken)) { + if (!(authentication instanceof AnonymousAuthenticationToken) && authentication != null) { securityInfo.setAuthenticated(authentication.isAuthenticated()); securityInfo.setUsername(authentication.getName()); From 7db4af25cedb7e13bef1d7243aae2ff8eb8c2419 Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Sun, 21 May 2023 12:23:59 +0200 Subject: [PATCH 3/6] fix: fixed order and pattern --- .../common/security/OAuthSecurityConfiguration.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java index ab824674a4..1ad6fe553a 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java @@ -222,7 +222,7 @@ protected void configure(HttpSecurity http) throws Exception { // All paths should be available only for authenticated users this.authorizationProperties.getAuthenticatedPaths().add("/"); this.authorizationProperties.getAuthenticatedPaths().add(this.authorizationProperties.getDashboardUrl()); - this.authorizationProperties.getAuthenticatedPaths().add(dashboard(authorizationProperties, "/*/**")); + this.authorizationProperties.getAuthenticatedPaths().add(dashboard(authorizationProperties, "/**")); // Permit for all users as the visibility is managed through roles this.authorizationProperties.getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl()); @@ -230,12 +230,12 @@ protected void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = http.authorizeRequests() - .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) - .anonymous() + .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) + .authenticated() .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) .permitAll() - .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) - .authenticated(); + .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) + .anonymous(); security = SecurityConfigUtils.configureSimpleSecurity(security, this.authorizationProperties); security.anyRequest().denyAll(); From adbee78955b355ac3fb449666988d0ad8a43f7d2 Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Sun, 21 May 2023 20:45:53 +0200 Subject: [PATCH 4/6] fix: unit test shell core --- .../cloud/dataflow/server/controller/AboutController.java | 2 +- .../dataflow/shell/command/ConfigCommandTests-testInfo.txt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java index 1d35d4784b..d0f8692fc8 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/controller/AboutController.java @@ -148,7 +148,7 @@ public AboutResource getAboutResource() { if (authenticationEnabled && SecurityContextHolder.getContext() != null) { final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (!(authentication instanceof AnonymousAuthenticationToken)) { + if (!(authentication instanceof AnonymousAuthenticationToken) && authentication != null) { securityInfo.setAuthenticated(authentication.isAuthenticated()); securityInfo.setUsername(authentication.getName()); diff --git a/spring-cloud-dataflow-shell-core/src/test/resources/org/springframework/cloud/dataflow/shell/command/ConfigCommandTests-testInfo.txt b/spring-cloud-dataflow-shell-core/src/test/resources/org/springframework/cloud/dataflow/shell/command/ConfigCommandTests-testInfo.txt index 2260f15994..e902017439 100644 --- a/spring-cloud-dataflow-shell-core/src/test/resources/org/springframework/cloud/dataflow/shell/command/ConfigCommandTests-testInfo.txt +++ b/spring-cloud-dataflow-shell-core/src/test/resources/org/springframework/cloud/dataflow/shell/command/ConfigCommandTests-testInfo.txt @@ -9,8 +9,9 @@ ╟─────────────────┼─────────────────────────────────────╢ ║ Versions │Foo Core: 1.2.3.BUILD-SNAPSHOT ║ ╟─────────────────┼─────────────────────────────────────╢ -║ Security │ Authenticated: false ║ -║ │Authentication Enabled: true ║ +║ │ Authenticated: false ║ +║ Security │Authentication Enabled: true ║ +║ │ Client Registrations: [] ║ ╟─────────────────┼─────────────────────────────────────╢ ║ │Deployer Implementation Version: null║ ║ │ Deployer Name: null║ From 96e4c754096c25b584ef7f694b13cff28304aac0 Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Sun, 21 May 2023 21:09:49 +0200 Subject: [PATCH 5/6] fix: use configured logout url --- .../security/AuthorizationProperties.java | 2 +- .../security/OAuthSecurityConfiguration.java | 2 +- .../SkipperOAuthSecurityConfiguration.java | 27 +++++++++++-------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java index e722ea9835..676eae0df4 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java @@ -42,7 +42,7 @@ public class AuthorizationProperties { private String logoutUrl = "/logout"; - private String logoutSuccessUrl = "/logout-success.html"; + private String logoutSuccessUrl = dashboardUrl + "/logout-success-oauth.html"; private List permitAllPaths = new ArrayList<>(); diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java index 1ad6fe553a..92973093fd 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java @@ -404,7 +404,7 @@ protected LogoutSuccessHandler logoutSuccessHandler(AuthorizationProperties auth OAuth2TokenUtilsService oauth2TokenUtilsService) { AccessTokenClearingLogoutSuccessHandler logoutSuccessHandler = new AccessTokenClearingLogoutSuccessHandler(oauth2TokenUtilsService); - logoutSuccessHandler.setDefaultTargetUrl(dashboard(authorizationProperties, "/logout-success-oauth.html")); + logoutSuccessHandler.setDefaultTargetUrl(authorizationProperties.getLogoutSuccessUrl()); return logoutSuccessHandler; } } diff --git a/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java b/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java index 1aadf32bb9..39b982d33a 100644 --- a/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java +++ b/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java @@ -62,26 +62,31 @@ protected void configure(HttpSecurity http) throws Exception { http.addFilter(basicAuthenticationFilter); } - getAuthorizationProperties().getAuthenticatedPaths() - .add(dashboard(getAuthorizationProperties(), "/**")); - getAuthorizationProperties().getAuthenticatedPaths() - .add(dashboard(getAuthorizationProperties(), "")); + getAuthorizationProperties().getAnonymousPaths().add(authorizationProperties.getLoginUrl()); + + // All paths should be available only for authenticated users + getAuthorizationProperties().getAuthenticatedPaths().add("/"); + getAuthorizationProperties().getAuthenticatedPaths().add(this.authorizationProperties.getDashboardUrl()); + getAuthorizationProperties().getAuthenticatedPaths().add(dashboard(authorizationProperties, "/**")); + + // Permit for all users as the visibility is managed through roles + getAuthorizationProperties().getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl()); + getAuthorizationProperties().getPermitAllPaths().add(dashboard(authorizationProperties, "/**")); ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = http.authorizeRequests() - .antMatchers(getAuthorizationProperties().getPermitAllPaths() - .toArray(new String[0])) + .antMatchers(getAuthorizationProperties().getAuthenticatedPaths().toArray(new String[0])) + .authenticated() + .antMatchers(getAuthorizationProperties().getPermitAllPaths().toArray(new String[0])) .permitAll() - .antMatchers(getAuthorizationProperties().getAuthenticatedPaths() - .toArray(new String[0])) - .authenticated(); - + .antMatchers(getAuthorizationProperties().getAnonymousPaths().toArray(new String[0])) + .anonymous(); security = SecurityConfigUtils.configureSimpleSecurity(security, getAuthorizationProperties()); security.anyRequest().denyAll(); http.httpBasic().and() .logout() - .logoutSuccessUrl(dashboard(getAuthorizationProperties(), "/logout-success-oauth.html")) + .logoutSuccessUrl(getAuthorizationProperties().getLogoutSuccessUrl()) .and().csrf().disable() .exceptionHandling() .defaultAuthenticationEntryPointFor(basicAuthenticationEntryPoint, new AntPathRequestMatcher("/api/**")) From 9673b3c7a6e399489329c89e9256ccb6e96b497b Mon Sep 17 00:00:00 2001 From: Tobias Soloschenko Date: Sun, 21 May 2023 22:20:32 +0200 Subject: [PATCH 6/6] fix: toggle between oauth2 login and angular login --- .../security/AuthorizationProperties.java | 1 + .../security/OAuthSecurityConfiguration.java | 34 ++++++++++++++----- .../SkipperOAuthSecurityConfiguration.java | 31 ++++++++++++----- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java index 676eae0df4..380334db28 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/AuthorizationProperties.java @@ -30,6 +30,7 @@ */ public class AuthorizationProperties { + public static final String FRONTEND_LOGIN_URL = "/dashboard/index.html#/authentication-required"; private String externalAuthoritiesUrl; private List rules = new ArrayList<>(); diff --git a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java index 92973093fd..062da219ce 100644 --- a/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java +++ b/spring-cloud-common-security-config/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/OAuthSecurityConfiguration.java @@ -218,6 +218,7 @@ protected void configure(HttpSecurity http) throws Exception { // Anonymous paths for the login page this.authorizationProperties.getAnonymousPaths().add(authorizationProperties.getLoginUrl()); + this.authorizationProperties.getAnonymousPaths().add("/login"); // All paths should be available only for authenticated users this.authorizationProperties.getAuthenticatedPaths().add("/"); @@ -228,14 +229,29 @@ protected void configure(HttpSecurity http) throws Exception { this.authorizationProperties.getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl()); this.authorizationProperties.getPermitAllPaths().add(dashboard(authorizationProperties, "/**")); - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = - http.authorizeRequests() - .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) - .authenticated() - .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) - .permitAll() - .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) - .anonymous(); + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security; + + if (AuthorizationProperties.FRONTEND_LOGIN_URL.equals(this.authorizationProperties.getLoginUrl())) { + security = + http.authorizeRequests() + .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) + .authenticated() + .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) + .permitAll() + .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) + .anonymous(); + + } else { + security = + http.authorizeRequests() + .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) + .permitAll() + .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) + .authenticated() + .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) + .anonymous(); + } + security = SecurityConfigUtils.configureSimpleSecurity(security, this.authorizationProperties); security.anyRequest().denyAll(); @@ -249,7 +265,7 @@ protected void configure(HttpSecurity http) throws Exception { new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest")) .defaultAuthenticationEntryPointFor( - new LoginUrlAuthenticationEntryPoint(this.authorizationProperties.getLoginUrl()), + new LoginUrlAuthenticationEntryPoint(authorizationProperties.getLoginUrl()), textHtmlMatcher) .defaultAuthenticationEntryPointFor(basicAuthenticationEntryPoint, AnyRequestMatcher.INSTANCE); diff --git a/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java b/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java index 39b982d33a..8973e923df 100644 --- a/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java +++ b/spring-cloud-skipper/spring-cloud-skipper-server-core/src/main/java/org/springframework/cloud/skipper/server/config/security/SkipperOAuthSecurityConfiguration.java @@ -73,14 +73,29 @@ protected void configure(HttpSecurity http) throws Exception { getAuthorizationProperties().getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl()); getAuthorizationProperties().getPermitAllPaths().add(dashboard(authorizationProperties, "/**")); - ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = - http.authorizeRequests() - .antMatchers(getAuthorizationProperties().getAuthenticatedPaths().toArray(new String[0])) - .authenticated() - .antMatchers(getAuthorizationProperties().getPermitAllPaths().toArray(new String[0])) - .permitAll() - .antMatchers(getAuthorizationProperties().getAnonymousPaths().toArray(new String[0])) - .anonymous(); + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security; + + if (AuthorizationProperties.FRONTEND_LOGIN_URL.equals(this.authorizationProperties.getLoginUrl())) { + security = + http.authorizeRequests() + .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) + .authenticated() + .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) + .permitAll() + .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) + .anonymous(); + + } else { + security = + http.authorizeRequests() + .antMatchers(this.authorizationProperties.getPermitAllPaths().toArray(new String[0])) + .permitAll() + .antMatchers(this.authorizationProperties.getAuthenticatedPaths().toArray(new String[0])) + .authenticated() + .antMatchers(this.authorizationProperties.getAnonymousPaths().toArray(new String[0])) + .anonymous(); + } + security = SecurityConfigUtils.configureSimpleSecurity(security, getAuthorizationProperties()); security.anyRequest().denyAll();