Переглянути джерело

Merge custom-consent-authorizationserver into featured-authorizationserver

Issue gh-1189
Joe Grandja 2 роки тому
батько
коміт
1485135325
14 змінених файлів з 10 додано та 564 видалено
  1. 0 1
      samples/custom-consent-authorizationserver/gradle.properties
  2. 0 26
      samples/custom-consent-authorizationserver/samples-custom-consent-authorizationserver.gradle
  3. 0 31
      samples/custom-consent-authorizationserver/src/main/java/sample/CustomConsentAuthorizationServerApplication.java
  4. 0 131
      samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
  5. 0 74
      samples/custom-consent-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java
  6. 0 73
      samples/custom-consent-authorizationserver/src/main/java/sample/jose/Jwks.java
  7. 0 84
      samples/custom-consent-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java
  8. 0 10
      samples/custom-consent-authorizationserver/src/main/resources/application.yml
  9. 0 126
      samples/custom-consent-authorizationserver/src/test/java/sample/CustomConsentAuthorizationServerTests.java
  10. 1 0
      samples/featured-authorizationserver/samples-featured-authorizationserver.gradle
  11. 5 0
      samples/featured-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
  12. 2 1
      samples/featured-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java
  13. 0 5
      samples/featured-authorizationserver/src/main/resources/templates/consent.html
  14. 2 2
      samples/featured-authorizationserver/src/test/java/sample/FeaturedAuthorizationServerConsentTests.java

+ 0 - 1
samples/custom-consent-authorizationserver/gradle.properties

@@ -1 +0,0 @@
-spring-security.version=6.1.0-RC1

+ 0 - 26
samples/custom-consent-authorizationserver/samples-custom-consent-authorizationserver.gradle

@@ -1,26 +0,0 @@
-plugins {
-	id "org.springframework.boot" version "3.0.0"
-	id "io.spring.dependency-management" version "1.0.11.RELEASE"
-	id "java"
-}
-
-group = project.rootProject.group
-version = project.rootProject.version
-sourceCompatibility = "17"
-
-repositories {
-	mavenCentral()
-	maven { url "https://repo.spring.io/milestone" }
-}
-
-dependencies {
-	implementation "org.springframework.boot:spring-boot-starter-web"
-	implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
-	implementation "org.springframework.boot:spring-boot-starter-security"
-	implementation project(":spring-security-oauth2-authorization-server")
-
-	testImplementation "org.springframework.boot:spring-boot-starter-test"
-	testImplementation "org.springframework.security:spring-security-test"
-	testImplementation "org.junit.jupiter:junit-jupiter"
-	testImplementation "net.sourceforge.htmlunit:htmlunit"
-}

+ 0 - 31
samples/custom-consent-authorizationserver/src/main/java/sample/CustomConsentAuthorizationServerApplication.java

@@ -1,31 +0,0 @@
-/*
- * Copyright 2020-2021 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
- *
- *      https://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;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-/**
- * @author Daniel Garnier-Moiroux
- */
-@SpringBootApplication
-public class CustomConsentAuthorizationServerApplication {
-
-	public static void main(String[] args) {
-		SpringApplication.run(CustomConsentAuthorizationServerApplication.class, args);
-	}
-
-}

+ 0 - 131
samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -1,131 +0,0 @@
-/*
- * Copyright 2020-2023 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
- *
- *      https://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.config;
-
-import java.util.UUID;
-
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.RSAKey;
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.SecurityContext;
-import sample.jose.Jwks;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.Ordered;
-import org.springframework.core.annotation.Order;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
-import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
-import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
-import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
-import org.springframework.security.web.util.matcher.RequestMatcher;
-
-/**
- * @author Joe Grandja
- * @author Daniel Garnier-Moiroux
- */
-@Configuration(proxyBeanMethods = false)
-public class AuthorizationServerConfig {
-	private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
-
-	@Bean
-	@Order(Ordered.HIGHEST_PRECEDENCE)
-	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
-		OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
-				new OAuth2AuthorizationServerConfigurer();
-		authorizationServerConfigurer
-				.authorizationEndpoint(authorizationEndpoint ->
-						authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
-				.oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
-
-		RequestMatcher endpointsMatcher = authorizationServerConfigurer
-				.getEndpointsMatcher();
-
-		http
-			.securityMatcher(endpointsMatcher)
-			.authorizeHttpRequests(authorize ->
-				authorize.anyRequest().authenticated()
-			)
-			.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
-			.exceptionHandling(exceptions ->
-				exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
-			)
-			.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
-			.apply(authorizationServerConfigurer);
-		return http.build();
-	}
-
-	// @formatter:off
-	@Bean
-	public RegisteredClientRepository registeredClientRepository() {
-		RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
-				.clientId("messaging-client")
-				.clientSecret("{noop}secret")
-				.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
-				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
-				.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
-				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
-				.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
-				.redirectUri("http://127.0.0.1:8080/authorized")
-				.postLogoutRedirectUri("http://127.0.0.1:8080/logged-out")
-				.scope(OidcScopes.OPENID)
-				.scope(OidcScopes.PROFILE)
-				.scope("message.read")
-				.scope("message.write")
-				.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
-				.build();
-		return new InMemoryRegisteredClientRepository(registeredClient);
-	}
-	// @formatter:on
-
-	@Bean
-	public JWKSource<SecurityContext> jwkSource() {
-		RSAKey rsaKey = Jwks.generateRsa();
-		JWKSet jwkSet = new JWKSet(rsaKey);
-		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
-	}
-
-	@Bean
-	public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
-		return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
-	}
-
-	@Bean
-	public AuthorizationServerSettings authorizationServerSettings() {
-		return AuthorizationServerSettings.builder().build();
-	}
-
-	@Bean
-	public OAuth2AuthorizationConsentService authorizationConsentService() {
-		// Will be used by the ConsentController
-		return new InMemoryOAuth2AuthorizationConsentService();
-	}
-
-}

+ 0 - 74
samples/custom-consent-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java

@@ -1,74 +0,0 @@
-/*
- * Copyright 2020-2023 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
- *
- *      https://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.config;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.core.session.SessionRegistry;
-import org.springframework.security.core.session.SessionRegistryImpl;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.session.HttpSessionEventPublisher;
-
-import static org.springframework.security.config.Customizer.withDefaults;
-
-/**
- * @author Joe Grandja
- */
-@EnableWebSecurity
-@Configuration(proxyBeanMethods = false)
-public class DefaultSecurityConfig {
-
-	// @formatter:off
-	@Bean
-	SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
-		http
-			.authorizeHttpRequests(authorize ->
-				authorize.anyRequest().authenticated()
-			)
-			.formLogin(withDefaults());
-		return http.build();
-	}
-	// @formatter:on
-
-	// @formatter:off
-	@Bean
-	UserDetailsService users() {
-		UserDetails user = User.withDefaultPasswordEncoder()
-				.username("user1")
-				.password("password")
-				.roles("USER")
-				.build();
-		return new InMemoryUserDetailsManager(user);
-	}
-	// @formatter:on
-
-	@Bean
-	SessionRegistry sessionRegistry() {
-		return new SessionRegistryImpl();
-	}
-
-	@Bean
-	HttpSessionEventPublisher httpSessionEventPublisher() {
-		return new HttpSessionEventPublisher();
-	}
-
-}

+ 0 - 73
samples/custom-consent-authorizationserver/src/main/java/sample/jose/Jwks.java

@@ -1,73 +0,0 @@
-/*
- * Copyright 2020-2021 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
- *
- *      https://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.jose;
-
-import java.security.KeyPair;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.UUID;
-
-import javax.crypto.SecretKey;
-
-import com.nimbusds.jose.jwk.Curve;
-import com.nimbusds.jose.jwk.ECKey;
-import com.nimbusds.jose.jwk.OctetSequenceKey;
-import com.nimbusds.jose.jwk.RSAKey;
-
-/**
- * @author Joe Grandja
- */
-public final class Jwks {
-
-	private Jwks() {
-	}
-
-	public static RSAKey generateRsa() {
-		KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
-		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
-		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
-		// @formatter:off
-		return new RSAKey.Builder(publicKey)
-				.privateKey(privateKey)
-				.keyID(UUID.randomUUID().toString())
-				.build();
-		// @formatter:on
-	}
-
-	public static ECKey generateEc() {
-		KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
-		ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
-		ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
-		Curve curve = Curve.forECParameterSpec(publicKey.getParams());
-		// @formatter:off
-		return new ECKey.Builder(curve, publicKey)
-				.privateKey(privateKey)
-				.keyID(UUID.randomUUID().toString())
-				.build();
-		// @formatter:on
-	}
-
-	public static OctetSequenceKey generateSecret() {
-		SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
-		// @formatter:off
-		return new OctetSequenceKey.Builder(secretKey)
-				.keyID(UUID.randomUUID().toString())
-				.build();
-		// @formatter:on
-	}
-}

+ 0 - 84
samples/custom-consent-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java

@@ -1,84 +0,0 @@
-/*
- * Copyright 2020-2021 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
- *
- *      https://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.jose;
-
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.spec.ECFieldFp;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.ECPoint;
-import java.security.spec.EllipticCurve;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-/**
- * @author Joe Grandja
- */
-final class KeyGeneratorUtils {
-
-	private KeyGeneratorUtils() {
-	}
-
-	static SecretKey generateSecretKey() {
-		SecretKey hmacKey;
-		try {
-			hmacKey = KeyGenerator.getInstance("HmacSha256").generateKey();
-		} catch (Exception ex) {
-			throw new IllegalStateException(ex);
-		}
-		return hmacKey;
-	}
-
-	static KeyPair generateRsaKey() {
-		KeyPair keyPair;
-		try {
-			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
-			keyPairGenerator.initialize(2048);
-			keyPair = keyPairGenerator.generateKeyPair();
-		} catch (Exception ex) {
-			throw new IllegalStateException(ex);
-		}
-		return keyPair;
-	}
-
-	static KeyPair generateEcKey() {
-		EllipticCurve ellipticCurve = new EllipticCurve(
-				new ECFieldFp(
-						new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853951")),
-				new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
-				new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
-		ECPoint ecPoint = new ECPoint(
-				new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
-				new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
-		ECParameterSpec ecParameterSpec = new ECParameterSpec(
-				ellipticCurve,
-				ecPoint,
-				new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"),
-				1);
-
-		KeyPair keyPair;
-		try {
-			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
-			keyPairGenerator.initialize(ecParameterSpec);
-			keyPair = keyPairGenerator.generateKeyPair();
-		} catch (Exception ex) {
-			throw new IllegalStateException(ex);
-		}
-		return keyPair;
-	}
-}

+ 0 - 10
samples/custom-consent-authorizationserver/src/main/resources/application.yml

@@ -1,10 +0,0 @@
-server:
-  port: 9000
-
-logging:
-  level:
-    root: INFO
-    org.springframework.web: INFO
-    org.springframework.security: INFO
-    org.springframework.security.oauth2: INFO
-#    org.springframework.boot.autoconfigure: DEBUG

+ 0 - 126
samples/custom-consent-authorizationserver/src/test/java/sample/CustomConsentAuthorizationServerTests.java

@@ -1,126 +0,0 @@
-/*
- * Copyright 2020-2022 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
- *
- *      https://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;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.gargoylesoftware.htmlunit.WebClient;
-import com.gargoylesoftware.htmlunit.WebResponse;
-import com.gargoylesoftware.htmlunit.html.DomElement;
-import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
-import com.gargoylesoftware.htmlunit.html.HtmlPage;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
-import org.springframework.security.test.context.support.WithMockUser;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-import org.springframework.web.util.UriComponentsBuilder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-/**
- * Consent page integration tests for the sample Authorization Server serving a custom Consent page.
- *
- * @author Dmitriy Dubson
- */
-@ExtendWith(SpringExtension.class)
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-@AutoConfigureMockMvc
-public class CustomConsentAuthorizationServerTests {
-
-	@Autowired
-	private WebClient webClient;
-
-	@MockBean
-	private OAuth2AuthorizationConsentService authorizationConsentService;
-
-	private final String redirectUri = "http://127.0.0.1/login/oauth2/code/messaging-client-oidc";
-
-	private final String authorizationRequestUri = UriComponentsBuilder
-			.fromPath("/oauth2/authorize")
-			.queryParam("response_type", "code")
-			.queryParam("client_id", "messaging-client")
-			.queryParam("scope", "openid message.read message.write")
-			.queryParam("state", "state")
-			.queryParam("redirect_uri", this.redirectUri)
-			.toUriString();
-
-	@BeforeEach
-	public void setUp() {
-		this.webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
-		this.webClient.getOptions().setRedirectEnabled(true);
-		this.webClient.getCookieManager().clearCookies();
-		when(this.authorizationConsentService.findById(any(), any())).thenReturn(null);
-	}
-
-	@Test
-	@WithMockUser("user1")
-	public void whenUserConsentsToAllScopesThenReturnAuthorizationCode() throws IOException {
-		final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
-		assertThat(consentPage.getTitleText()).isEqualTo("Custom consent page - Consent required");
-
-		List<HtmlCheckBoxInput> scopes = new ArrayList<>();
-		consentPage.querySelectorAll("input[name='scope']").forEach(scope ->
-				scopes.add((HtmlCheckBoxInput) scope));
-		for (HtmlCheckBoxInput scope : scopes) {
-			scope.click();
-		}
-
-		List<String> scopeIds = new ArrayList<>();
-		scopes.forEach(scope -> {
-			assertThat(scope.isChecked()).isTrue();
-			scopeIds.add(scope.getId());
-		});
-		assertThat(scopeIds).containsExactlyInAnyOrder("message.read", "message.write");
-
-		DomElement submitConsentButton = consentPage.querySelector("button[id='submit-consent']");
-		this.webClient.getOptions().setRedirectEnabled(false);
-
-		WebResponse approveConsentResponse = submitConsentButton.click().getWebResponse();
-		assertThat(approveConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
-		String location = approveConsentResponse.getResponseHeaderValue("location");
-		assertThat(location).startsWith(this.redirectUri);
-		assertThat(location).contains("code=");
-	}
-
-	@Test
-	@WithMockUser("user1")
-	public void whenUserCancelsConsentThenReturnAccessDeniedError() throws IOException {
-		final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
-		assertThat(consentPage.getTitleText()).isEqualTo("Custom consent page - Consent required");
-
-		DomElement cancelConsentButton = consentPage.querySelector("button[id='cancel-consent']");
-		this.webClient.getOptions().setRedirectEnabled(false);
-
-		WebResponse cancelConsentResponse = cancelConsentButton.click().getWebResponse();
-		assertThat(cancelConsentResponse.getStatusCode()).isEqualTo(HttpStatus.MOVED_PERMANENTLY.value());
-		String location = cancelConsentResponse.getResponseHeaderValue("location");
-		assertThat(location).startsWith(this.redirectUri);
-		assertThat(location).contains("error=access_denied");
-	}
-
-}

+ 1 - 0
samples/featured-authorizationserver/samples-featured-authorizationserver.gradle

@@ -15,6 +15,7 @@ repositories {
 
 
 dependencies {
 dependencies {
 	implementation "org.springframework.boot:spring-boot-starter-web"
 	implementation "org.springframework.boot:spring-boot-starter-web"
+	implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
 	implementation "org.springframework.boot:spring-boot-starter-security"
 	implementation "org.springframework.boot:spring-boot-starter-security"
 	implementation "org.springframework.boot:spring-boot-starter-jdbc"
 	implementation "org.springframework.boot:spring-boot-starter-jdbc"
 	implementation project(":spring-security-oauth2-authorization-server")
 	implementation project(":spring-security-oauth2-authorization-server")

+ 5 - 0
samples/featured-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -53,16 +53,20 @@ import org.springframework.security.web.authentication.LoginUrlAuthenticationEnt
 
 
 /**
 /**
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Daniel Garnier-Moiroux
  * @since 1.1.0
  * @since 1.1.0
  */
  */
 @Configuration(proxyBeanMethods = false)
 @Configuration(proxyBeanMethods = false)
 public class AuthorizationServerConfig {
 public class AuthorizationServerConfig {
+	private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
 
 
 	@Bean
 	@Bean
 	@Order(Ordered.HIGHEST_PRECEDENCE)
 	@Order(Ordered.HIGHEST_PRECEDENCE)
 	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
 	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
 		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
 		OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
 		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
 		http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
+				.authorizationEndpoint(authorizationEndpoint ->
+						authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
 				.oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
 				.oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
 
 
 		// @formatter:off
 		// @formatter:off
@@ -113,6 +117,7 @@ public class AuthorizationServerConfig {
 	@Bean
 	@Bean
 	public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
 	public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
 			RegisteredClientRepository registeredClientRepository) {
 			RegisteredClientRepository registeredClientRepository) {
+		// Will be used by the ConsentController
 		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
 		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
 	}
 	}
 
 

+ 2 - 1
samples/custom-consent-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java → samples/featured-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2020-2022 the original author or authors.
+ * Copyright 2020-2023 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -125,4 +125,5 @@ public class AuthorizationConsentController {
 			this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
 			this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
 		}
 		}
 	}
 	}
+
 }
 }

+ 0 - 5
samples/custom-consent-authorizationserver/src/main/resources/templates/consent.html → samples/featured-authorizationserver/src/main/resources/templates/consent.html

@@ -6,11 +6,6 @@
     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
           integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
           integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
     <title>Custom consent page - Consent required</title>
     <title>Custom consent page - Consent required</title>
-    <style>
-        body {
-            background-color: aliceblue;
-        }
-    </style>
 	<script>
 	<script>
 		function cancelConsent() {
 		function cancelConsent() {
 			document.consent_form.reset();
 			document.consent_form.reset();

+ 2 - 2
samples/featured-authorizationserver/src/test/java/sample/FeaturedAuthorizationServerConsentTests.java

@@ -81,7 +81,7 @@ public class FeaturedAuthorizationServerConsentTests {
 	@WithMockUser("user1")
 	@WithMockUser("user1")
 	public void whenUserConsentsToAllScopesThenReturnAuthorizationCode() throws IOException {
 	public void whenUserConsentsToAllScopesThenReturnAuthorizationCode() throws IOException {
 		final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
 		final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
-		assertThat(consentPage.getTitleText()).isEqualTo("Consent required");
+		assertThat(consentPage.getTitleText()).isEqualTo("Custom consent page - Consent required");
 
 
 		List<HtmlCheckBoxInput> scopes = new ArrayList<>();
 		List<HtmlCheckBoxInput> scopes = new ArrayList<>();
 		consentPage.querySelectorAll("input[name='scope']").forEach(scope ->
 		consentPage.querySelectorAll("input[name='scope']").forEach(scope ->
@@ -111,7 +111,7 @@ public class FeaturedAuthorizationServerConsentTests {
 	@WithMockUser("user1")
 	@WithMockUser("user1")
 	public void whenUserCancelsConsentThenReturnAccessDeniedError() throws IOException {
 	public void whenUserCancelsConsentThenReturnAccessDeniedError() throws IOException {
 		final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
 		final HtmlPage consentPage = this.webClient.getPage(this.authorizationRequestUri);
-		assertThat(consentPage.getTitleText()).isEqualTo("Consent required");
+		assertThat(consentPage.getTitleText()).isEqualTo("Custom consent page - Consent required");
 
 
 		DomElement cancelConsentButton = consentPage.querySelector("button[id='cancel-consent']");
 		DomElement cancelConsentButton = consentPage.querySelector("button[id='cancel-consent']");
 		this.webClient.getOptions().setRedirectEnabled(false);
 		this.webClient.getOptions().setRedirectEnabled(false);