From 7013c6fd7609846ddbffd24231622273f5831801 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 11 May 2018 00:40:44 -0500 Subject: [PATCH] Add OAuth2LoginSpec Issue: gh-4807 --- .../web/reactive/EnableWebFluxSecurity.java | 3 +- .../ReactiveOAuth2ClientImportSelector.java | 80 +++++++++++ .../ServerHttpSecurityConfiguration.java | 17 ++- .../WebFluxSecurityConfiguration.java | 30 +++- .../config/web/server/ServerHttpSecurity.java | 135 +++++++++++++++++- .../spring-security-oauth2-client.gradle | 4 + .../ui/LoginPageGeneratingWebFilter.java | 52 +++++-- 7 files changed, 303 insertions(+), 18 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java index d1f2b2bb0f..3843bf9b92 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurity.java @@ -82,7 +82,8 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented -@Import({ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class}) +@Import({ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class, + ReactiveOAuth2ClientImportSelector.class}) @Configuration public @interface EnableWebFluxSecurity { } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java new file mode 100644 index 0000000000..c5d2ddb6da --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java @@ -0,0 +1,80 @@ +/* + * Copyright 2002-2018 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.security.config.annotation.web.reactive; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2ClientArgumentResolver; +import org.springframework.util.ClassUtils; +import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; + +/** + * {@link Configuration} for OAuth 2.0 Client support. + * + *

+ * This {@code Configuration} is imported by {@link EnableWebFluxSecurity} + * + * @author Rob Winch + * @since 5.1 + */ +final class ReactiveOAuth2ClientImportSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + boolean oauth2ClientPresent = ClassUtils.isPresent( + "org.springframework.security.oauth2.client.registration.ClientRegistration", getClass().getClassLoader()); + + return oauth2ClientPresent ? + new String[] { "org.springframework.security.config.annotation.web.reactive.ReactiveOAuth2ClientImportSelector$OAuth2ClientWebFluxSecurityConfiguration" } : + new String[] {}; + } + + @Configuration + static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer { + private ReactiveClientRegistrationRepository clientRegistrationRepository; + + private ReactiveOAuth2AuthorizedClientService authorizedClientService; + + @Override + public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { + if (this.clientRegistrationRepository != null && this.authorizedClientService != null) { + configurer.addCustomResolver(new OAuth2ClientArgumentResolver(this.clientRegistrationRepository, this.authorizedClientService)); + } + } + + @Autowired(required = false) + public void setClientRegistrationRepository(List clientRegistrationRepository) { + if (clientRegistrationRepository.size() == 1) { + this.clientRegistrationRepository = clientRegistrationRepository.get(0); + } + } + + @Autowired(required = false) + public void setAuthorizedClientService(List authorizedClientService) { + if (authorizedClientService.size() == 1) { + this.authorizedClientService = authorizedClientService.get(0); + } + } + } +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java index 357b43372e..8e06990092 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java @@ -16,8 +16,11 @@ package org.springframework.security.config.annotation.web.reactive; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Scope; import org.springframework.context.expression.BeanFactoryResolver; @@ -31,8 +34,6 @@ import org.springframework.security.web.reactive.result.method.annotation.Authen import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; -import static org.springframework.security.config.web.server.ServerHttpSecurity.http; - /** * @author Rob Winch * @since 5.0 @@ -74,7 +75,8 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer { @Bean(HTTPSECURITY_BEAN_NAME) @Scope("prototype") public ServerHttpSecurity httpSecurity() { - return http() + ContextAwareServerHttpSecurity http = new ContextAwareServerHttpSecurity(); + return http .authenticationManager(authenticationManager()) .headers().and() .logout().and(); @@ -94,4 +96,13 @@ class ServerHttpSecurityConfiguration implements WebFluxConfigurer { } return null; } + + private static class ContextAwareServerHttpSecurity extends ServerHttpSecurity implements + ApplicationContextAware { + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + super.setApplicationContext(applicationContext); + } + } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java index e353582ee6..bed91d4f97 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java @@ -16,6 +16,9 @@ package org.springframework.security.config.annotation.web.reactive; +import java.util.Arrays; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -25,12 +28,10 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.web.reactive.result.view.AbstractView; -import java.util.Arrays; -import java.util.List; - /** * @author Rob Winch * @since 5.0 @@ -43,6 +44,11 @@ class WebFluxSecurityConfiguration { private static final String SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME = BEAN_NAME_PREFIX + "WebFilterChainFilter"; + public static final String REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME = "org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository"; + + private static final boolean isOAuth2Present = ClassUtils.isPresent( + REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, WebFluxSecurityConfiguration.class.getClassLoader()); + @Autowired(required = false) private List securityWebFilterChains; @@ -85,6 +91,22 @@ class WebFluxSecurityConfiguration { .and() .httpBasic().and() .formLogin(); - return http.build(); + + if (isOAuth2Present) { + OAuth2ClasspathGuard.configure(this.context, http); + } + + SecurityWebFilterChain result = http.build(); + return result; + } + + private static class OAuth2ClasspathGuard { + static void configure(ApplicationContext context, ServerHttpSecurity http) { + ClassLoader loader = context.getClassLoader(); + Class reactiveClientRegistrationRepositoryClass = ClassUtils.resolveClassName(REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, loader); + if (context.getBeanNamesForType(reactiveClientRegistrationRepositoryClass).length == 1) { + http.oauth2Login(); + } + } } } diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index de8bd44274..6c8c40622c 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -24,9 +24,14 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; import org.springframework.core.Ordered; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -35,12 +40,23 @@ import org.springframework.security.authorization.AuthenticatedReactiveAuthoriza import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.ReactiveAuthorizationManager; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.authentication.OAuth2LoginReactiveAuthenticationManager; +import org.springframework.security.oauth2.client.endpoint.NimbusReactiveAuthorizationCodeTokenResponseClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectWebFilter; +import org.springframework.security.oauth2.client.web.ServerOAuth2LoginAuthenticationTokenConverter; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint; import org.springframework.security.web.server.MatcherSecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.ServerFormLoginAuthenticationConverter; import org.springframework.security.web.server.ServerHttpBasicAuthenticationConverter; +import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; @@ -79,6 +95,7 @@ import org.springframework.security.web.server.savedrequest.WebSessionServerRequ import org.springframework.security.web.server.ui.LoginPageGeneratingWebFilter; import org.springframework.security.web.server.ui.LogoutPageGeneratingWebFilter; import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; @@ -161,6 +178,8 @@ public class ServerHttpSecurity { private FormLoginSpec formLogin; + private OAuth2LoginSpec oauth2Login; + private LogoutSpec logout = new LogoutSpec(); private ReactiveAuthenticationManager authenticationManager; @@ -175,6 +194,8 @@ public class ServerHttpSecurity { private List webFilters = new ArrayList<>(); + private ApplicationContext context; + private Throwable built; /** @@ -318,6 +339,90 @@ public class ServerHttpSecurity { return this.formLogin; } + public OAuth2LoginSpec oauth2Login() { + if (this.oauth2Login == null) { + this.oauth2Login = new OAuth2LoginSpec(); + } + return this.oauth2Login; + } + + public class OAuth2LoginSpec { + private ReactiveClientRegistrationRepository clientRegistrationRepository; + + private ReactiveOAuth2AuthorizedClientService authorizedClientService; + + public OAuth2LoginSpec clientRegistrationRepository(ReactiveClientRegistrationRepository clientRegistrationRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + return this; + } + + public OAuth2LoginSpec authorizedClientService(ReactiveOAuth2AuthorizedClientService authorizedClientService) { + this.authorizedClientService = authorizedClientService; + return this; + } + + protected void configure(LoginPageGeneratingWebFilter loginPageFilter, ServerHttpSecurity http) { + if (loginPageFilter != null) { + loginPageFilter.setOauth2AuthenticationUrlToClientName(getLinks()); + } + + ReactiveClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository(); + ReactiveOAuth2AuthorizedClientService authorizedClientService = getAuthorizedClientService(); + OAuth2AuthorizationRequestRedirectWebFilter oauthRedirectFilter = new OAuth2AuthorizationRequestRedirectWebFilter(clientRegistrationRepository); + + NimbusReactiveAuthorizationCodeTokenResponseClient client = new NimbusReactiveAuthorizationCodeTokenResponseClient(); + ReactiveOAuth2UserService userService = new DefaultReactiveOAuth2UserService(); + OAuth2LoginReactiveAuthenticationManager manager = new OAuth2LoginReactiveAuthenticationManager(client, userService, + authorizedClientService); + AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(manager); + authenticationFilter.setRequiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}")); + authenticationFilter.setAuthenticationConverter(new ServerOAuth2LoginAuthenticationTokenConverter(clientRegistrationRepository)); + + RedirectServerAuthenticationSuccessHandler redirectHandler = new RedirectServerAuthenticationSuccessHandler(); + + authenticationFilter.setAuthenticationSuccessHandler(redirectHandler); + authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() { + @Override + public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, + AuthenticationException exception) { + return Mono.error(exception); + } + }); + authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository()); + + http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC); + http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION); + } + + private Map getLinks() { + Iterable registrations = getBeanOrNull(ResolvableType.forClassWithGenerics(Iterable.class, ClientRegistration.class)); + if (registrations == null) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + registrations.iterator().forEachRemaining(r -> { + result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName()); + }); + return result; + } + + private ReactiveClientRegistrationRepository getClientRegistrationRepository() { + if (this.clientRegistrationRepository == null) { + this.clientRegistrationRepository = getBeanOrNull(ReactiveClientRegistrationRepository.class); + } + return this.clientRegistrationRepository; + } + + private ReactiveOAuth2AuthorizedClientService getAuthorizedClientService() { + if (this.authorizedClientService == null) { + this.authorizedClientService = getBeanOrNull(ReactiveOAuth2AuthorizedClientService.class); + } + return this.authorizedClientService; + } + + private OAuth2LoginSpec() {} + } + /** * Configures HTTP Response Headers. The default headers are: * @@ -505,17 +610,22 @@ public class ServerHttpSecurity { this.httpBasic.authenticationManager(this.authenticationManager); this.httpBasic.configure(this); } + LoginPageGeneratingWebFilter loginPageFilter = null; if(this.formLogin != null) { this.formLogin.authenticationManager(this.authenticationManager); if(this.securityContextRepository != null) { this.formLogin.securityContextRepository(this.securityContextRepository); } if(this.formLogin.authenticationEntryPoint == null) { - this.webFilters.add(new OrderedWebFilter(new LoginPageGeneratingWebFilter(), SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING.getOrder())); + loginPageFilter = new LoginPageGeneratingWebFilter(); + this.webFilters.add(new OrderedWebFilter(loginPageFilter, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING.getOrder())); this.webFilters.add(new OrderedWebFilter(new LogoutPageGeneratingWebFilter(), SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING.getOrder())); } this.formLogin.configure(this); } + if (this.oauth2Login != null) { + this.oauth2Login.configure(loginPageFilter, this); + } if(this.logout != null) { this.logout.configure(this); } @@ -589,7 +699,7 @@ public class ServerHttpSecurity { return new OrderedWebFilter(result, SecurityWebFiltersOrder.REACTOR_CONTEXT.getOrder()); } - private ServerHttpSecurity() {} + protected ServerHttpSecurity() {} /** * Configures authorization @@ -1402,6 +1512,27 @@ public class ServerHttpSecurity { private LogoutSpec() {} } + private T getBeanOrNull(Class beanClass) { + return getBeanOrNull(ResolvableType.forClass(beanClass)); + } + + + private T getBeanOrNull(ResolvableType type) { + if (this.context == null) { + return null; + } + String[] names = this.context.getBeanNamesForType(type); + if (names.length == 1) { + return (T) this.context.getBean(names[0]); + } + return null; + } + + protected void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.context = applicationContext; + } + private static class OrderedWebFilter implements WebFilter, Ordered { private final WebFilter webFilter; private final int order; diff --git a/oauth2/oauth2-client/spring-security-oauth2-client.gradle b/oauth2/oauth2-client/spring-security-oauth2-client.gradle index f1f3966bb3..94f29ae607 100644 --- a/oauth2/oauth2-client/spring-security-oauth2-client.gradle +++ b/oauth2/oauth2-client/spring-security-oauth2-client.gradle @@ -8,10 +8,14 @@ dependencies { compile 'com.nimbusds:oauth2-oidc-sdk' optional project(':spring-security-oauth2-jose') + optional 'io.projectreactor:reactor-core' + optional 'org.springframework:spring-webflux' testCompile powerMock2Dependencies testCompile 'com.squareup.okhttp3:mockwebserver' testCompile 'com.fasterxml.jackson.core:jackson-databind' + testCompile 'io.projectreactor.ipc:reactor-netty' + testCompile 'io.projectreactor:reactor-test' provided 'javax.servlet:javax.servlet-api' } diff --git a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java index 6be4f8bbca..373ee21e4b 100644 --- a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java @@ -16,6 +16,10 @@ package org.springframework.security.web.server.ui; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpMethod; @@ -25,13 +29,14 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.web.server.csrf.CsrfToken; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; +import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; +import org.springframework.web.util.HtmlUtils; -import java.nio.charset.Charset; +import reactor.core.publisher.Mono; /** * Generates a default log in page used for authenticating users. @@ -43,6 +48,14 @@ public class LoginPageGeneratingWebFilter implements WebFilter { private ServerWebExchangeMatcher matcher = ServerWebExchangeMatchers .pathMatchers(HttpMethod.GET, "/login"); + private Map oauth2AuthenticationUrlToClientName = new HashMap<>(); + + public void setOauth2AuthenticationUrlToClientName( + Map oauth2AuthenticationUrlToClientName) { + Assert.notNull(oauth2AuthenticationUrlToClientName, "oauth2AuthenticationUrlToClientName cannot be null"); + this.oauth2AuthenticationUrlToClientName = oauth2AuthenticationUrlToClientName; + } + @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { return this.matcher.matches(exchange) @@ -59,22 +72,24 @@ public class LoginPageGeneratingWebFilter implements WebFilter { } private Mono createBuffer(ServerWebExchange exchange) { - MultiValueMap queryParams = exchange.getRequest() - .getQueryParams(); + Mono token = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); return token .map(LoginPageGeneratingWebFilter::csrfToken) .defaultIfEmpty("") .map(csrfTokenHtmlInput -> { - boolean isError = queryParams.containsKey("error"); - boolean isLogoutSuccess = queryParams.containsKey("logout"); - byte[] bytes = createPage(isError, isLogoutSuccess, csrfTokenHtmlInput); + byte[] bytes = createPage(exchange, csrfTokenHtmlInput); DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); return bufferFactory.wrap(bytes); }); } - private static byte[] createPage(boolean isError, boolean isLogoutSuccess, String csrfTokenHtmlInput) { + private byte[] createPage(ServerWebExchange exchange, String csrfTokenHtmlInput) { + MultiValueMap queryParams = exchange.getRequest() + .getQueryParams(); + boolean isError = queryParams.containsKey("error"); + boolean isLogoutSuccess = queryParams.containsKey("logout"); + String contextPath = exchange.getRequest().getPath().contextPath().value(); String page = "\n" + "\n" + " \n" @@ -103,6 +118,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter { + csrfTokenHtmlInput + " \n" + " \n" + + oauth2LoginLinks(contextPath, this.oauth2AuthenticationUrlToClientName) + " \n" + " \n" + ""; @@ -110,6 +126,26 @@ public class LoginPageGeneratingWebFilter implements WebFilter { return page.getBytes(Charset.defaultCharset()); } + private static String oauth2LoginLinks(String contextPath, Map oauth2AuthenticationUrlToClientName) { + if (oauth2AuthenticationUrlToClientName.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append("

"); + sb.append("\n"); + for (Map.Entry clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) { + sb.append(" \n"); + } + sb.append("
"); + String url = clientAuthenticationUrlToClientName.getKey(); + sb.append(""); + String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue()); + sb.append(clientName); + sb.append(""); + sb.append("
\n"); + return sb.toString(); + } + private static String csrfToken(CsrfToken token) { return " \n"; }