From 1485135325df152dcaf38015ab2c6d7a8b6f0513 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 27 Apr 2023 08:25:44 -0400 Subject: [PATCH] Merge custom-consent-authorizationserver into featured-authorizationserver Issue gh-1189 --- .../gradle.properties | 1 - ...-custom-consent-authorizationserver.gradle | 26 ---- ...ConsentAuthorizationServerApplication.java | 31 ----- .../config/AuthorizationServerConfig.java | 131 ------------------ .../sample/config/DefaultSecurityConfig.java | 74 ---------- .../src/main/java/sample/jose/Jwks.java | 73 ---------- .../java/sample/jose/KeyGeneratorUtils.java | 84 ----------- .../src/main/resources/application.yml | 10 -- ...CustomConsentAuthorizationServerTests.java | 126 ----------------- ...amples-featured-authorizationserver.gradle | 1 + .../config/AuthorizationServerConfig.java | 5 + .../web/AuthorizationConsentController.java | 3 +- .../src/main/resources/templates/consent.html | 5 - ...aturedAuthorizationServerConsentTests.java | 4 +- 14 files changed, 10 insertions(+), 564 deletions(-) delete mode 100644 samples/custom-consent-authorizationserver/gradle.properties delete mode 100644 samples/custom-consent-authorizationserver/samples-custom-consent-authorizationserver.gradle delete mode 100644 samples/custom-consent-authorizationserver/src/main/java/sample/CustomConsentAuthorizationServerApplication.java delete mode 100644 samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java delete mode 100644 samples/custom-consent-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java delete mode 100644 samples/custom-consent-authorizationserver/src/main/java/sample/jose/Jwks.java delete mode 100644 samples/custom-consent-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java delete mode 100644 samples/custom-consent-authorizationserver/src/main/resources/application.yml delete mode 100644 samples/custom-consent-authorizationserver/src/test/java/sample/CustomConsentAuthorizationServerTests.java rename samples/{custom-consent-authorizationserver => featured-authorizationserver}/src/main/java/sample/web/AuthorizationConsentController.java (98%) rename samples/{custom-consent-authorizationserver => featured-authorizationserver}/src/main/resources/templates/consent.html (97%) diff --git a/samples/custom-consent-authorizationserver/gradle.properties b/samples/custom-consent-authorizationserver/gradle.properties deleted file mode 100644 index 3d071be6..00000000 --- a/samples/custom-consent-authorizationserver/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -spring-security.version=6.1.0-RC1 diff --git a/samples/custom-consent-authorizationserver/samples-custom-consent-authorizationserver.gradle b/samples/custom-consent-authorizationserver/samples-custom-consent-authorizationserver.gradle deleted file mode 100644 index 768ed521..00000000 --- a/samples/custom-consent-authorizationserver/samples-custom-consent-authorizationserver.gradle +++ /dev/null @@ -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" -} diff --git a/samples/custom-consent-authorizationserver/src/main/java/sample/CustomConsentAuthorizationServerApplication.java b/samples/custom-consent-authorizationserver/src/main/java/sample/CustomConsentAuthorizationServerApplication.java deleted file mode 100644 index 85ed2381..00000000 --- a/samples/custom-consent-authorizationserver/src/main/java/sample/CustomConsentAuthorizationServerApplication.java +++ /dev/null @@ -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); - } - -} diff --git a/samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java deleted file mode 100644 index 85b177b1..00000000 --- a/samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ /dev/null @@ -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 jwkSource() { - RSAKey rsaKey = Jwks.generateRsa(); - JWKSet jwkSet = new JWKSet(rsaKey); - return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - } - - @Bean - public JwtDecoder jwtDecoder(JWKSource 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(); - } - -} diff --git a/samples/custom-consent-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java b/samples/custom-consent-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java deleted file mode 100644 index b4370c02..00000000 --- a/samples/custom-consent-authorizationserver/src/main/java/sample/config/DefaultSecurityConfig.java +++ /dev/null @@ -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(); - } - -} diff --git a/samples/custom-consent-authorizationserver/src/main/java/sample/jose/Jwks.java b/samples/custom-consent-authorizationserver/src/main/java/sample/jose/Jwks.java deleted file mode 100644 index 1f936056..00000000 --- a/samples/custom-consent-authorizationserver/src/main/java/sample/jose/Jwks.java +++ /dev/null @@ -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 - } -} diff --git a/samples/custom-consent-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java b/samples/custom-consent-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java deleted file mode 100644 index b101062e..00000000 --- a/samples/custom-consent-authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java +++ /dev/null @@ -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; - } -} diff --git a/samples/custom-consent-authorizationserver/src/main/resources/application.yml b/samples/custom-consent-authorizationserver/src/main/resources/application.yml deleted file mode 100644 index 5e879a67..00000000 --- a/samples/custom-consent-authorizationserver/src/main/resources/application.yml +++ /dev/null @@ -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 diff --git a/samples/custom-consent-authorizationserver/src/test/java/sample/CustomConsentAuthorizationServerTests.java b/samples/custom-consent-authorizationserver/src/test/java/sample/CustomConsentAuthorizationServerTests.java deleted file mode 100644 index 762ccf81..00000000 --- a/samples/custom-consent-authorizationserver/src/test/java/sample/CustomConsentAuthorizationServerTests.java +++ /dev/null @@ -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 scopes = new ArrayList<>(); - consentPage.querySelectorAll("input[name='scope']").forEach(scope -> - scopes.add((HtmlCheckBoxInput) scope)); - for (HtmlCheckBoxInput scope : scopes) { - scope.click(); - } - - List 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"); - } - -} diff --git a/samples/featured-authorizationserver/samples-featured-authorizationserver.gradle b/samples/featured-authorizationserver/samples-featured-authorizationserver.gradle index 61721414..08f4a720 100644 --- a/samples/featured-authorizationserver/samples-featured-authorizationserver.gradle +++ b/samples/featured-authorizationserver/samples-featured-authorizationserver.gradle @@ -15,6 +15,7 @@ repositories { 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 "org.springframework.boot:spring-boot-starter-jdbc" implementation project(":spring-security-oauth2-authorization-server") diff --git a/samples/featured-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/featured-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java index 8e70f28f..f0665679 100644 --- a/samples/featured-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ b/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 Daniel Garnier-Moiroux * @since 1.1.0 */ @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 { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI)) .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 // @formatter:off @@ -113,6 +117,7 @@ public class AuthorizationServerConfig { @Bean public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { + // Will be used by the ConsentController return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); } diff --git a/samples/custom-consent-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java b/samples/featured-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java similarity index 98% rename from samples/custom-consent-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java rename to samples/featured-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java index c98327d7..c1c8f298 100644 --- a/samples/custom-consent-authorizationserver/src/main/java/sample/web/AuthorizationConsentController.java +++ b/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"); * 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); } } + } diff --git a/samples/custom-consent-authorizationserver/src/main/resources/templates/consent.html b/samples/featured-authorizationserver/src/main/resources/templates/consent.html similarity index 97% rename from samples/custom-consent-authorizationserver/src/main/resources/templates/consent.html rename to samples/featured-authorizationserver/src/main/resources/templates/consent.html index 761dfc28..fbfc8a37 100644 --- a/samples/custom-consent-authorizationserver/src/main/resources/templates/consent.html +++ b/samples/featured-authorizationserver/src/main/resources/templates/consent.html @@ -6,11 +6,6 @@ Custom consent page - Consent required -