From 9e21586c906d6a19dfb27499a032b4dfb687079b Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Wed, 20 May 2026 20:59:36 +0200 Subject: [PATCH 1/2] Upgrade to Spring Boot 4.0 The new ["modular design"](https://spring.io/blog/2025/10/28/modularizing-spring-boot/) required a few changes in the starter Maven dependencies. It also caused some minor Java import changes. The main problem that arose was that the spring-boot-web-server dependency got scoped to "runtime" in Maven. This broke the embedded Docker build since it was excluded from dependency lists. Therefore, it had to be manually added back in the correct "provided" scope. spring-boot-starter-web, JTE, and Testcontainers had new artifact names. Jackson had to be added as an explicit dependency (for testing only) since it's no longer included by default in Spring Boot. ## Spring Boot OAuth 2.0 Authorization Server There were some internal changes that broke the "custom" developer authorization. The `OAuth2AuthorizationEndpointFilter` got split into two separate ones (for MFA purposes), and the split off one is executed much much earlier in the chain. Therefore, the old method of changing the HTTP method of the request to trick the regular filter from the custom one no longer works. Luckily an unrelated change had added POST support to the `OAuth2AuthorizationEndpointFilter` which meant we could stop changing the HTTP method and change the form to submit everything as form fields instead of query parameters. A lot of mechanical changes in the tests were required for this. MFA support also meant that OIDC ID tokens require the authenticated principal to have a `FactorGrantedAuthority` which was added to the Shibboleth authentication. --- pom.xml | 34 +++++++++++++---- .../se/su/dsv/oauth2/AuthorizationServer.java | 4 +- .../su/dsv/oauth2/dev/DevConfiguration.java | 6 +-- ...ShibbolethAuthenticationDetailsSource.java | 3 ++ .../CustomAuthorizationEndpointFilter.java | 25 ++++-------- src/main/resources/templates/authorize.jte | 12 ++++++ .../oauth2/AbstractMetadataCodeFlowTest.java | 30 ++++++++++----- .../su/dsv/oauth2/AbstractMetadataTest.java | 18 ++++----- .../dsv/oauth2/AuthorizationCodeFlowTest.java | 14 +++---- .../dsv/oauth2/AuthorizationServerTest.java | 4 +- .../ConsentFlowCustomAuthorizationTest.java | 12 +++--- .../se/su/dsv/oauth2/ConsentFlowTest.java | 5 ++- .../su/dsv/oauth2/EmbeddedContainerTest.java | 8 ++-- .../dsv/oauth2/PublicClientCodeFlowTest.java | 6 +-- .../ResourceServerRegisteredClientTest.java | 2 +- .../oauth2/ShibbolethRequestProcessor.java | 2 +- .../se/su/dsv/oauth2/StagingProfileTest.java | 38 +++++++++---------- .../TestRegisteredClientConfiguration.java | 4 ++ .../su/dsv/oauth2/UserInfoEndpointTest.java | 4 +- .../dsv/oauth2/web/AdminControllerTest.java | 8 ++-- .../web/client/ClientAdminControllerTest.java | 6 +-- 21 files changed, 142 insertions(+), 103 deletions(-) diff --git a/pom.xml b/pom.xml index bf38b3e..adf2696 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.11 + 4.0.6 @@ -19,7 +19,8 @@ 17 - 3.1.12 + 3.2.4 + 4.0.6 @@ -29,12 +30,12 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-starter-webmvc gg.jte - jte-spring-boot-starter-3 + jte-spring-boot-starter-4 ${jte.version} @@ -66,6 +67,11 @@ spring-boot-starter-tomcat provided + + org.springframework.boot + spring-boot-web-server + provided + @@ -73,6 +79,15 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-webmvc-test + + + org.springframework.boot + spring-boot-starter-jackson-test + test + org.springframework.boot spring-boot-testcontainers @@ -85,12 +100,12 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - mariadb + testcontainers-mariadb test @@ -105,7 +120,7 @@ org.springframework.boot spring-boot-configuration-processor - 3.3.0 + ${spring-boot.version} @@ -171,6 +186,11 @@ org.springframework.boot spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-flyway + org.flywaydb flyway-core diff --git a/src/main/java/se/su/dsv/oauth2/AuthorizationServer.java b/src/main/java/se/su/dsv/oauth2/AuthorizationServer.java index 0568506..2c9cb56 100644 --- a/src/main/java/se/su/dsv/oauth2/AuthorizationServer.java +++ b/src/main/java/se/su/dsv/oauth2/AuthorizationServer.java @@ -13,6 +13,7 @@ import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -21,7 +22,6 @@ import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; -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.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; @@ -95,7 +95,7 @@ public class AuthorizationServer extends SpringBootServletInitializer { throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - OAuth2AuthorizationServerConfigurer.authorizationServer(); + new OAuth2AuthorizationServerConfigurer(); String tokenEndpoint = authorizationServerSettings.getTokenEndpoint(); RequestMatcher corsEnabledMatcher = new OrRequestMatcher( diff --git a/src/main/java/se/su/dsv/oauth2/dev/DevConfiguration.java b/src/main/java/se/su/dsv/oauth2/dev/DevConfiguration.java index 914d585..d36aec5 100644 --- a/src/main/java/se/su/dsv/oauth2/dev/DevConfiguration.java +++ b/src/main/java/se/su/dsv/oauth2/dev/DevConfiguration.java @@ -1,7 +1,7 @@ package se.su.dsv.oauth2.dev; import jakarta.servlet.http.HttpFilter; -import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,9 +12,9 @@ import se.su.dsv.oauth2.Config; @Profile("dev") public class DevConfiguration { @Bean - public FilterRegistrationBean fakeSSO(SecurityProperties securityProperties, Config config) { + public FilterRegistrationBean fakeSSO(SecurityFilterProperties securityProperties, Config config) { var filter = new FilterRegistrationBean(new FakeSSOFilter(config.developerEntitlement())); - filter.setOrder(securityProperties.getFilter().getOrder() - 1); + filter.setOrder(securityProperties.getOrder() - 1); return filter; } } diff --git a/src/main/java/se/su/dsv/oauth2/shibboleth/ShibbolethAuthenticationDetailsSource.java b/src/main/java/se/su/dsv/oauth2/shibboleth/ShibbolethAuthenticationDetailsSource.java index ecae400..a55870e 100644 --- a/src/main/java/se/su/dsv/oauth2/shibboleth/ShibbolethAuthenticationDetailsSource.java +++ b/src/main/java/se/su/dsv/oauth2/shibboleth/ShibbolethAuthenticationDetailsSource.java @@ -3,6 +3,7 @@ package se.su.dsv.oauth2.shibboleth; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.FactorGrantedAuthority; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -46,6 +47,8 @@ public class ShibbolethAuthenticationDetailsSource implements authorities.add(entitlement); } } + // The authentication performed by Shibboleth is password based. + authorities.add(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY)); return authorities; } } diff --git a/src/main/java/se/su/dsv/oauth2/staging/CustomAuthorizationEndpointFilter.java b/src/main/java/se/su/dsv/oauth2/staging/CustomAuthorizationEndpointFilter.java index 2089b79..e1c6d0c 100644 --- a/src/main/java/se/su/dsv/oauth2/staging/CustomAuthorizationEndpointFilter.java +++ b/src/main/java/se/su/dsv/oauth2/staging/CustomAuthorizationEndpointFilter.java @@ -7,7 +7,6 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpFilter; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; @@ -34,10 +33,7 @@ import se.su.dsv.oauth2.shibboleth.ShibbolethAuthenticationDetails; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -100,7 +96,8 @@ public class CustomAuthorizationEndpointFilter extends HttpFilter { authenticationConverter.convert(request); String authorizationUrl = getAuthorizationUrl(request); - JteModel view = templates.authorize(authorizationUrl, loggedInUser.getName(), (ShibbolethAuthenticationDetails) loggedInUser.getDetails()); + Map parameters = request.getParameterMap(); + JteModel view = templates.authorize(authorizationUrl, parameters, loggedInUser.getName(), (ShibbolethAuthenticationDetails) loggedInUser.getDetails()); respondWithTemplate(response, view); } else if (Objects.equals(request.getMethod(), "POST")) { handleIncomingCustomAuthorizationRequest(request, response, loggedInUser); @@ -119,14 +116,15 @@ public class CustomAuthorizationEndpointFilter extends HttpFilter { principal.setDetails(buildShibbolethDetails(request)); Authentication authenticatedPrincipal = authenticationManager.authenticate(principal); - Authentication normalCodeRequest = authenticationConverter.convert(withGetMethod(request)); + Authentication normalCodeRequest = authenticationConverter.convert(request); OAuth2AuthorizationCodeRequestAuthenticationToken codeRequestAuthenticationToken = (OAuth2AuthorizationCodeRequestAuthenticationToken) normalCodeRequest; Authentication codeRequest = overridePrincipal(authenticatedPrincipal, codeRequestAuthenticationToken); Authentication authenticatedCodeRequest = authenticationManager.authenticate(codeRequest); if (!authenticatedCodeRequest.isAuthenticated()) { String authorizationUrl = getAuthorizationUrl(request); - respondWithTemplate(response, templates.authorize(authorizationUrl, loggedInUser.getName(), (ShibbolethAuthenticationDetails) authenticatedPrincipal)); + Map parameters = request.getParameterMap(); + respondWithTemplate(response, templates.authorize(authorizationUrl, parameters, loggedInUser.getName(), (ShibbolethAuthenticationDetails) authenticatedPrincipal)); } else { if (authenticatedCodeRequest instanceof OAuth2AuthorizationCodeRequestAuthenticationToken authenticatedCodeRequestAuthenticationToken) { sendAuthorizationResponse(request, response, authenticatedCodeRequestAuthenticationToken); @@ -139,16 +137,7 @@ public class CustomAuthorizationEndpointFilter extends HttpFilter { } private static String getAuthorizationUrl(final HttpServletRequest request) { - return request.getRequestURL().append('?').append(request.getQueryString()).toString(); - } - - private HttpServletRequest withGetMethod(final HttpServletRequest request) { - return new HttpServletRequestWrapper(request) { - @Override - public String getMethod() { - return "GET"; - } - }; + return request.getRequestURL().toString(); } private Authentication overridePrincipal( diff --git a/src/main/resources/templates/authorize.jte b/src/main/resources/templates/authorize.jte index 3974f92..7081991 100644 --- a/src/main/resources/templates/authorize.jte +++ b/src/main/resources/templates/authorize.jte @@ -1,8 +1,10 @@ @import se.su.dsv.oauth2.shibboleth.Entitlement @import se.su.dsv.oauth2.shibboleth.ShibbolethAuthenticationDetails +@import java.util.Map @import java.util.stream.Collectors @param String authorizationUrl +@param Map parameters @param String principalName @param ShibbolethAuthenticationDetails shibbolethDetails @@ -15,6 +17,11 @@

+ @for (var entry : parameters.entrySet()) + @for (var value : entry.getValue()) + + @endfor + @endfor

Issue custom token

@@ -51,6 +58,11 @@

+ @for (var entry : parameters.entrySet()) + @for (var value : entry.getValue()) + + @endfor + @endfor

Continue as yourself

Principal
diff --git a/src/test/java/se/su/dsv/oauth2/AbstractMetadataCodeFlowTest.java b/src/test/java/se/su/dsv/oauth2/AbstractMetadataCodeFlowTest.java index 0da2d84..ec962cb 100644 --- a/src/test/java/se/su/dsv/oauth2/AbstractMetadataCodeFlowTest.java +++ b/src/test/java/se/su/dsv/oauth2/AbstractMetadataCodeFlowTest.java @@ -1,11 +1,11 @@ package se.su.dsv.oauth2; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; import java.util.function.Consumer; import java.util.regex.Matcher; @@ -24,25 +24,35 @@ import static se.su.dsv.oauth2.TestRegisteredClientConfiguration.REDIRECT_URI; public abstract class AbstractMetadataCodeFlowTest extends AbstractMetadataTest { @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); protected TokenResponse authorize(Consumer consumer) throws Exception { - return getTokenResponse(get(getAuthorizationEndpoint()), consumer); + return getTokenResponse(get(getAuthorizationEndpoint()), consumer, true); } protected TokenResponse authorizePost(Consumer consumer) throws Exception { - return getTokenResponse(post(getAuthorizationEndpoint()), consumer); + return getTokenResponse(post(getAuthorizationEndpoint()), consumer, false); } private TokenResponse getTokenResponse( final MockHttpServletRequestBuilder builder, - final Consumer customizer) + final Consumer customizer, + final boolean isGet) throws Exception { - MockHttpServletRequestBuilder requestBuilder = builder - .queryParam("response_type", "code") - .queryParam("client_id", CLIENT_ID) - .queryParam("redirect_uri", REDIRECT_URI); + MockHttpServletRequestBuilder requestBuilder; + if (isGet) { + requestBuilder = builder + .queryParam("response_type", "code") + .queryParam("client_id", CLIENT_ID) + .queryParam("redirect_uri", REDIRECT_URI); + } + else { + requestBuilder = builder + .formField("response_type", "code") + .formField("client_id", CLIENT_ID) + .formField("redirect_uri", REDIRECT_URI); + } customizer.accept(requestBuilder); MvcResult authorizationResult = mockMvc.perform(requestBuilder) diff --git a/src/test/java/se/su/dsv/oauth2/AbstractMetadataTest.java b/src/test/java/se/su/dsv/oauth2/AbstractMetadataTest.java index 998ffa7..05b98a4 100644 --- a/src/test/java/se/su/dsv/oauth2/AbstractMetadataTest.java +++ b/src/test/java/se/su/dsv/oauth2/AbstractMetadataTest.java @@ -1,7 +1,5 @@ package se.su.dsv.oauth2; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSourceBuilder; @@ -12,10 +10,12 @@ import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; import com.nimbusds.jwt.proc.DefaultJWTProcessor; import org.junit.jupiter.api.BeforeEach; 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.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.json.JsonMapper; import java.net.URI; import java.net.URL; @@ -29,7 +29,7 @@ public class AbstractMetadataTest { @Autowired protected MockMvc mockMvc; @Autowired - protected ObjectMapper objectMapper; + protected JsonMapper objectMapper; private JsonNode metadata; private ConfigurableJWTProcessor processor; @@ -45,7 +45,7 @@ public class AbstractMetadataTest { metadata = objectMapper.readTree(responseBody); // 2. Get JWKS - URL jwksUri = URI.create(metadata.required("jwks_uri").asText()).toURL(); + URL jwksUri = URI.create(metadata.required("jwks_uri").asString()).toURL(); JWKSource jwkSource = JWKSourceBuilder .create(jwksUri, new MockMvcResourceRetriever(mockMvc)) .build(); @@ -58,19 +58,19 @@ public class AbstractMetadataTest { } protected String getAuthorizationEndpoint() { - return metadata.get("authorization_endpoint").asText(); + return metadata.get("authorization_endpoint").asString(); } protected String getTokenEndpoint() { - return metadata.get("token_endpoint").asText(); + return metadata.get("token_endpoint").asString(); } protected String getIntrospectionEndpoint() { - return metadata.get("introspection_endpoint").asText(); + return metadata.get("introspection_endpoint").asString(); } protected String getUserInfoEndpoint() { - return metadata.get("userinfo_endpoint").asText(); + return metadata.get("userinfo_endpoint").asString(); } protected JWTClaimsSet verifyToken(String token) throws Exception { diff --git a/src/test/java/se/su/dsv/oauth2/AuthorizationCodeFlowTest.java b/src/test/java/se/su/dsv/oauth2/AuthorizationCodeFlowTest.java index 3095d21..18490d4 100644 --- a/src/test/java/se/su/dsv/oauth2/AuthorizationCodeFlowTest.java +++ b/src/test/java/se/su/dsv/oauth2/AuthorizationCodeFlowTest.java @@ -1,15 +1,15 @@ package se.su.dsv.oauth2; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; 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.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.json.JsonMapper; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,13 +26,13 @@ import static se.su.dsv.oauth2.TestRegisteredClientConfiguration.*; public class AuthorizationCodeFlowTest { @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Autowired MockMvc mockMvc; @Autowired - ObjectMapper objectMapper; + JsonMapper objectMapper; @Test public void authorize_code_exchange_introspect() throws Exception { @@ -72,7 +72,7 @@ public class AuthorizationCodeFlowTest { String content = codeExchangeResult.getResponse().getContentAsString(); JsonNode jsonNode = objectMapper.readTree(content); - String accessToken = jsonNode.get("access_token").asText(); + String accessToken = jsonNode.get("access_token").asString(); // 3. Introspect mockMvc.perform(post("/oauth2/introspect") diff --git a/src/test/java/se/su/dsv/oauth2/AuthorizationServerTest.java b/src/test/java/se/su/dsv/oauth2/AuthorizationServerTest.java index 597ea63..79f9cd4 100644 --- a/src/test/java/se/su/dsv/oauth2/AuthorizationServerTest.java +++ b/src/test/java/se/su/dsv/oauth2/AuthorizationServerTest.java @@ -3,7 +3,7 @@ package se.su.dsv.oauth2; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -13,7 +13,7 @@ class AuthorizationServerTest { @Container @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Test void contextLoads() { diff --git a/src/test/java/se/su/dsv/oauth2/ConsentFlowCustomAuthorizationTest.java b/src/test/java/se/su/dsv/oauth2/ConsentFlowCustomAuthorizationTest.java index 4386a38..1b1a4a0 100644 --- a/src/test/java/se/su/dsv/oauth2/ConsentFlowCustomAuthorizationTest.java +++ b/src/test/java/se/su/dsv/oauth2/ConsentFlowCustomAuthorizationTest.java @@ -1,6 +1,5 @@ package se.su.dsv.oauth2; -import com.fasterxml.jackson.databind.JsonNode; import com.nimbusds.jwt.JWTClaimsSet; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -9,7 +8,8 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriComponentsBuilder; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; +import tools.jackson.databind.JsonNode; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -32,7 +32,7 @@ public class ConsentFlowCustomAuthorizationTest extends AbstractMetadataTest { public static final String DEVELOPER_ENTITLEMENT = "developer"; @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Test public void consent_is_disabled_for_developers() throws Exception { @@ -42,8 +42,8 @@ public class ConsentFlowCustomAuthorizationTest extends AbstractMetadataTest { MvcResult authorizationResult = mockMvc.perform(post(getAuthorizationEndpoint()) .with(remoteUser(developerPrincipal) .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("client_id", ConsentFlowTest.TestConfig.CLIENT_ID) - .queryParam("response_type", "code") + .formField("client_id", ConsentFlowTest.TestConfig.CLIENT_ID) + .formField("response_type", "code") .formField("principal", customPrincipal)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrlPattern(ConsentFlowTest.TestConfig.REDIRECT_URI + "?*")) @@ -66,7 +66,7 @@ public class ConsentFlowCustomAuthorizationTest extends AbstractMetadataTest { .andReturn(); JsonNode tokenResponse = objectMapper.readTree(tokenResult.getResponse().getContentAsString()); - JWTClaimsSet claims = verifyToken(tokenResponse.required("access_token").asText()); + JWTClaimsSet claims = verifyToken(tokenResponse.required("access_token").asString()); assertEquals(customPrincipal, claims.getSubject()); } diff --git a/src/test/java/se/su/dsv/oauth2/ConsentFlowTest.java b/src/test/java/se/su/dsv/oauth2/ConsentFlowTest.java index 712f200..d799c8b 100644 --- a/src/test/java/se/su/dsv/oauth2/ConsentFlowTest.java +++ b/src/test/java/se/su/dsv/oauth2/ConsentFlowTest.java @@ -15,7 +15,7 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -35,7 +35,7 @@ import static se.su.dsv.oauth2.ShibbolethRequestProcessor.remoteUser; public class ConsentFlowTest extends AbstractMetadataTest { @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Test public void asks_end_user_for_consent() throws Exception { @@ -195,6 +195,7 @@ public class ConsentFlowTest extends AbstractMetadataTest { .scope(OidcScopes.EMAIL) .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(true) + .requireProofKey(false) .build()) .build(); diff --git a/src/test/java/se/su/dsv/oauth2/EmbeddedContainerTest.java b/src/test/java/se/su/dsv/oauth2/EmbeddedContainerTest.java index fb27112..6ec2c96 100644 --- a/src/test/java/se/su/dsv/oauth2/EmbeddedContainerTest.java +++ b/src/test/java/se/su/dsv/oauth2/EmbeddedContainerTest.java @@ -104,13 +104,13 @@ public class EmbeddedContainerTest { final MultiValueMap form = new LinkedMultiValueMap<>(); form.put("principal", List.of("test")); + form.put("response_type", List.of("code")); + form.put("client_id", List.of(CLIENT_ID)); + form.put("redirect_uri", List.of(CLIENT_REDIRECT_URI)); + form.put("scope", List.of("openid profile email")); String authorizationEndpoint = parsedMetadata.required("authorization_endpoint").asText(); String authorizeUri = UriComponentsBuilder.fromUriString(authorizationEndpoint) - .queryParam("response_type", "code") - .queryParam("client_id", CLIENT_ID) - .queryParam("redirect_uri", CLIENT_REDIRECT_URI) - .queryParam("scope", "openid profile email") .build() .toUriString(); diff --git a/src/test/java/se/su/dsv/oauth2/PublicClientCodeFlowTest.java b/src/test/java/se/su/dsv/oauth2/PublicClientCodeFlowTest.java index fa92270..852afcb 100644 --- a/src/test/java/se/su/dsv/oauth2/PublicClientCodeFlowTest.java +++ b/src/test/java/se/su/dsv/oauth2/PublicClientCodeFlowTest.java @@ -3,11 +3,11 @@ package se.su.dsv.oauth2; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; import se.su.dsv.oauth2.admin.ClientData; import se.su.dsv.oauth2.admin.ClientManagementService; import se.su.dsv.oauth2.admin.NewClient; @@ -34,7 +34,7 @@ public class PublicClientCodeFlowTest extends AbstractMetadataCodeFlowTest { private static final String REDIRECT_URI = "http://localhost/public"; @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Autowired private ClientManagementService clientManagementService; diff --git a/src/test/java/se/su/dsv/oauth2/ResourceServerRegisteredClientTest.java b/src/test/java/se/su/dsv/oauth2/ResourceServerRegisteredClientTest.java index 76d6bd6..a4235d9 100644 --- a/src/test/java/se/su/dsv/oauth2/ResourceServerRegisteredClientTest.java +++ b/src/test/java/se/su/dsv/oauth2/ResourceServerRegisteredClientTest.java @@ -2,8 +2,8 @@ package se.su.dsv.oauth2; import org.junit.jupiter.api.Test; 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.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.http.HttpHeaders; import org.springframework.test.web.servlet.MvcResult; import se.su.dsv.oauth2.admin.ClientData; diff --git a/src/test/java/se/su/dsv/oauth2/ShibbolethRequestProcessor.java b/src/test/java/se/su/dsv/oauth2/ShibbolethRequestProcessor.java index 78f88fc..5c3df50 100644 --- a/src/test/java/se/su/dsv/oauth2/ShibbolethRequestProcessor.java +++ b/src/test/java/se/su/dsv/oauth2/ShibbolethRequestProcessor.java @@ -1,7 +1,7 @@ package se.su.dsv.oauth2; import org.apache.catalina.realm.GenericPrincipal; -import org.springframework.lang.NonNull; +import org.jspecify.annotations.NonNull; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.web.servlet.request.RequestPostProcessor; diff --git a/src/test/java/se/su/dsv/oauth2/StagingProfileTest.java b/src/test/java/se/su/dsv/oauth2/StagingProfileTest.java index 6da5cf4..90b6f66 100644 --- a/src/test/java/se/su/dsv/oauth2/StagingProfileTest.java +++ b/src/test/java/se/su/dsv/oauth2/StagingProfileTest.java @@ -64,7 +64,7 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { TokenResponse tokenResponse = authorizePost(request -> request .with(remoteUser(principal) .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("scope", "openid profile") + .formField("scope", "openid profile") .formField("principal", principal) .formField("displayName", customDisplayName)); @@ -85,7 +85,7 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { TokenResponse tokenResponse = authorizePost(request -> request .with(remoteUser(principal) .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("scope", "openid email") + .formField("scope", "openid email") .formField("principal", principal) .formField("mail", customMail)); @@ -106,7 +106,7 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { TokenResponse tokenResponse = authorizePost(request -> request .with(remoteUser(principal) .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("scope", "openid profile") + .formField("scope", "openid profile") .formField("principal", principal) .formField("givenName", customGivenName)); @@ -127,7 +127,7 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { TokenResponse tokenResponse = authorizePost(request -> request .with(remoteUser(principal) .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("scope", "openid profile") + .formField("scope", "openid profile") .formField("principal", principal) .formField("sn", customFamilyName)); @@ -148,7 +148,7 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { TokenResponse tokenResponse = authorizePost(request -> request .with(remoteUser(principal) .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("scope", "openid profile") + .formField("scope", "openid profile") .formField("principal", principal) .formField("entitlements", customEntitlement)); @@ -168,8 +168,8 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { mockMvc.perform(post(getAuthorizationEndpoint()) .with(remoteUser("developer") .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("client_id", CLIENT_ID) - .queryParam("redirect_uri", REDIRECT_URI) + .formField("client_id", CLIENT_ID) + .formField("redirect_uri", REDIRECT_URI) .formField("principal", "developer")) .andExpect(status().isBadRequest()) .andExpect(status().reason(containsString("response_type"))); @@ -180,10 +180,10 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { mockMvc.perform(post(getAuthorizationEndpoint()) .with(remoteUser("developer") .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("response_type", "code") - .queryParam("client_id", CLIENT_ID) - .queryParam("redirect_uri", REDIRECT_URI) - .queryParam("scope", "invalid") + .formField("response_type", "code") + .formField("client_id", CLIENT_ID) + .formField("redirect_uri", REDIRECT_URI) + .formField("scope", "invalid") .formField("principal", "developer")) .andExpect(status().is3xxRedirection()) .andExpect(result -> { @@ -197,9 +197,9 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { mockMvc.perform(post(getAuthorizationEndpoint()) .with(remoteUser("developer") .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("response_type", "code") - .queryParam("client_id", "invalid-client-id") - .queryParam("redirect_uri", REDIRECT_URI) + .formField("response_type", "code") + .formField("client_id", "invalid-client-id") + .formField("redirect_uri", REDIRECT_URI) .formField("principal", "developer")) .andExpect(status().isBadRequest()) .andExpect(status().reason(containsString("client_id"))); @@ -212,11 +212,11 @@ public class StagingProfileTest extends AbstractMetadataCodeFlowTest { mockMvc.perform(post(getAuthorizationEndpoint()) .with(remoteUser("developer") .entitlement(DEVELOPER_ENTITLEMENT)) - .queryParam("response_type", "code") - .queryParam("client_id", CLIENT_ID) - .queryParam("redirect_uri", REDIRECT_URI) - .queryParam("state", state) - .queryParam("scope", "invalid") + .formField("response_type", "code") + .formField("client_id", CLIENT_ID) + .formField("redirect_uri", REDIRECT_URI) + .formField("state", state) + .formField("scope", "invalid") .formField("principal", "developer")) .andExpect(status().is3xxRedirection()) .andExpect(result -> { diff --git a/src/test/java/se/su/dsv/oauth2/TestRegisteredClientConfiguration.java b/src/test/java/se/su/dsv/oauth2/TestRegisteredClientConfiguration.java index 38d2c6a..148a06f 100644 --- a/src/test/java/se/su/dsv/oauth2/TestRegisteredClientConfiguration.java +++ b/src/test/java/se/su/dsv/oauth2/TestRegisteredClientConfiguration.java @@ -8,6 +8,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.oidc.OidcScopes; 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.settings.ClientSettings; @TestConfiguration public class TestRegisteredClientConfiguration { @@ -21,6 +22,9 @@ public class TestRegisteredClientConfiguration { InMemoryRegisteredClientRepository testRegisteredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId("id") .clientId(CLIENT_ID) + .clientSettings(ClientSettings.builder() + .requireProofKey(false) + .build()) .clientSecret("{noop}" + CLIENT_SECRET) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri(REDIRECT_URI) diff --git a/src/test/java/se/su/dsv/oauth2/UserInfoEndpointTest.java b/src/test/java/se/su/dsv/oauth2/UserInfoEndpointTest.java index bc2a672..6d87325 100644 --- a/src/test/java/se/su/dsv/oauth2/UserInfoEndpointTest.java +++ b/src/test/java/se/su/dsv/oauth2/UserInfoEndpointTest.java @@ -1,10 +1,10 @@ package se.su.dsv.oauth2; -import com.fasterxml.jackson.databind.JsonNode; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.test.web.servlet.MvcResult; +import tools.jackson.databind.JsonNode; import java.net.URI; @@ -26,7 +26,7 @@ public class UserInfoEndpointTest extends AbstractMetadataCodeFlowTest { JsonNode openidConfiguration = objectMapper.readTree(mvcResult.getResponse().getContentAsString()); - String userinfoEndpoint = openidConfiguration.required("userinfo_endpoint").asText(); + String userinfoEndpoint = openidConfiguration.required("userinfo_endpoint").asString(); URI userInfoUri = URI.create(userinfoEndpoint); assertEquals("/oidc/userinfo", userInfoUri.getPath()); diff --git a/src/test/java/se/su/dsv/oauth2/web/AdminControllerTest.java b/src/test/java/se/su/dsv/oauth2/web/AdminControllerTest.java index ddc9337..552f984 100644 --- a/src/test/java/se/su/dsv/oauth2/web/AdminControllerTest.java +++ b/src/test/java/se/su/dsv/oauth2/web/AdminControllerTest.java @@ -2,11 +2,11 @@ package se.su.dsv.oauth2.web; import org.junit.jupiter.api.Test; 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.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -20,7 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class AdminControllerTest { @Container @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Autowired MockMvc mockMvc; @@ -28,7 +28,7 @@ class AdminControllerTest { @Test void is_protected() throws Exception { mockMvc.perform(get("/admin")) - .andExpect(redirectedUrl("http://localhost/login")); + .andExpect(redirectedUrl("/login")); } @Test diff --git a/src/test/java/se/su/dsv/oauth2/web/client/ClientAdminControllerTest.java b/src/test/java/se/su/dsv/oauth2/web/client/ClientAdminControllerTest.java index 3ab26bd..ddf5526 100644 --- a/src/test/java/se/su/dsv/oauth2/web/client/ClientAdminControllerTest.java +++ b/src/test/java/se/su/dsv/oauth2/web/client/ClientAdminControllerTest.java @@ -2,14 +2,14 @@ package se.su.dsv.oauth2.web.client; import org.junit.jupiter.api.Test; 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.testcontainers.service.connection.ServiceConnection; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.annotation.Rollback; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.transaction.annotation.Transactional; -import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.mariadb.MariaDBContainer; import se.su.dsv.oauth2.admin.Client; import se.su.dsv.oauth2.admin.ClientManagementService; @@ -35,7 +35,7 @@ public class ClientAdminControllerTest { public static final String DEVELOPER_ENTITLEMENT = "developer"; @ServiceConnection - static MariaDBContainer mariaDBContainer = new MariaDBContainer<>("mariadb:10.11"); + static MariaDBContainer mariaDBContainer = new MariaDBContainer("mariadb:10.11"); @Autowired MockMvc mockMvc; -- 2.39.5 From dc7eb44c072d9d92294b2ee60b785ff60edead42 Mon Sep 17 00:00:00 2001 From: Andreas Svanberg Date: Fri, 22 May 2026 16:23:24 +0200 Subject: [PATCH 2/2] Add missing test scope --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index adf2696..37dce0f 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,7 @@ org.springframework.boot spring-boot-starter-webmvc-test + test org.springframework.boot -- 2.39.5