Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Configurable OAuth2 login page #5350

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpSecurity>.ExpressionInterceptUrlRegistry security =
//this.authorizationProperties.getAuthenticatedPaths().add("/");
//this.authorizationProperties.getAuthenticatedPaths().add(this.authorizationProperties.getDashboardUrl());
//this.authorizationProperties.getAuthenticatedPaths().add(dashboard(authorizationProperties, "/**"));
klopfdreh marked this conversation as resolved.
Show resolved Hide resolved

this.authorizationProperties.getPermitAllPaths().add(this.authorizationProperties.getDashboardUrl());
this.authorizationProperties.getPermitAllPaths().add(dashboard(authorizationProperties, "/**"));
this.authorizationProperties.getPermitAllPaths().add(authorizationProperties.getLoginUrl());

ExpressionUrlAuthorizationConfigurer<HttpSecurity>.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)
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public class SecurityInfo {

private List<String> roles = new ArrayList<>(0);

private List<String> clientRegistrations = new ArrayList<>(0);

/**
* Default constructor for serialization frameworks.
*/
Expand Down Expand Up @@ -85,6 +87,18 @@ public void setRoles(List<String> roles) {
this.roles = roles;
}

/**
*
* @return List of all available client registrations
*/
public List<String> getClientRegistrations() {
return clientRegistrations;
}

public void setClientRegistrations(List<String> clientRegistrations) {
this.clientRegistrations = clientRegistrations;
}

/**
* @param role Adds the role to {@link #roles}
* @return the security related meta-information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class SecurityInfoResource extends RepresentationModel {

private List<String> roles = new ArrayList<>(0);

private List<String> clientRegistrations = new ArrayList<>(0);

/**
* Default constructor for serialization frameworks.
*/
Expand Down Expand Up @@ -87,6 +89,18 @@ public void setRoles(List<String> roles) {
this.roles = roles;
}

/**
*
* @return List of all available client registrations
*/
public List<String> getClientRegistrations() {
return clientRegistrations;
}

public void setClientRegistrations(List<String> clientRegistrations) {
this.clientRegistrations = clientRegistrations;
}

/**
* @param role Adds the role to {@link #roles}
* @return the resource with an additional role
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -198,9 +200,11 @@ public AboutController aboutController(ObjectProvider<StreamDeployer> 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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

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

/**
Expand Down Expand Up @@ -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<String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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<String> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}


Expand Down