diff --git a/samples/java/login-auth-code/pom.xml b/samples/java/login-auth-code/pom.xml index 6b2d646..b5400d6 100644 --- a/samples/java/login-auth-code/pom.xml +++ b/samples/java/login-auth-code/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.1 + 4.0.6 @@ -35,8 +35,8 @@ me.paulschwarz - spring-dotenv - 4.0.0 + springboot4-dotenv + 5.1.0 @@ -44,6 +44,13 @@ spring-boot-starter-test test + + + org.springframework.boot + spring-boot-webmvc-test + test + org.springframework.security spring-security-test @@ -60,7 +67,7 @@ org.codehaus.mojo keytool-maven-plugin - 1.7 + 2.0.2 generate-dev-keystore diff --git a/samples/java/login-auth-code/src/test/java/com/secureauth/quickstart/ApplicationTests.java b/samples/java/login-auth-code/src/test/java/com/secureauth/quickstart/ApplicationTests.java index ffbcbce..11bc787 100644 --- a/samples/java/login-auth-code/src/test/java/com/secureauth/quickstart/ApplicationTests.java +++ b/samples/java/login-auth-code/src/test/java/com/secureauth/quickstart/ApplicationTests.java @@ -6,7 +6,7 @@ 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.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; diff --git a/samples/java/saml-sp-login/pom.xml b/samples/java/saml-sp-login/pom.xml index 51f2a8c..a09a06c 100644 --- a/samples/java/saml-sp-login/pom.xml +++ b/samples/java/saml-sp-login/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.1 + 4.0.6 @@ -44,8 +44,8 @@ me.paulschwarz - spring-dotenv - 4.0.0 + springboot4-dotenv + 5.1.0 @@ -53,6 +53,13 @@ spring-boot-starter-test test + + + org.springframework.boot + spring-boot-webmvc-test + test + org.springframework.security spring-security-test @@ -69,7 +76,7 @@ org.codehaus.mojo keytool-maven-plugin - 1.7 + 2.0.2 generate-dev-keystore diff --git a/samples/java/saml-sp-login/src/main/java/com/secureauth/quickstart/Application.java b/samples/java/saml-sp-login/src/main/java/com/secureauth/quickstart/Application.java index 4d701b6..fdb0292 100644 --- a/samples/java/saml-sp-login/src/main/java/com/secureauth/quickstart/Application.java +++ b/samples/java/saml-sp-login/src/main/java/com/secureauth/quickstart/Application.java @@ -23,7 +23,7 @@ import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; +import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; @@ -101,9 +101,9 @@ static class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); - Converter defaultValidator = - OpenSaml4AuthenticationProvider.createDefaultResponseValidator(); + OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider(); + Converter defaultValidator = + OpenSaml5AuthenticationProvider.createDefaultResponseValidator(); provider.setResponseValidator(token -> { Saml2ResponseValidatorResult result = defaultValidator.convert(token); List filtered = result.getErrors().stream() @@ -138,7 +138,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } } final String expectedInResponseTo = assertionInResponseTo; - return OpenSaml4AuthenticationProvider.createDefaultAssertionValidatorWithParameters( + return OpenSaml5AuthenticationProvider.createDefaultAssertionValidatorWithParameters( params -> { params.put( org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_CHECK_ADDRESS, @@ -162,7 +162,9 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // requires POST + CSRF; for a quickstart we let a simple // link work directly). .logout(l -> l - .logoutRequestMatcher(new org.springframework.security.web.util.matcher.AntPathRequestMatcher("/logout")) + // Spring Security 7 dropped AntPathRequestMatcher; PathPatternRequestMatcher + // is the replacement. `.matcher(path)` (no HTTP method) matches any method. + .logoutRequestMatcher(org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.withDefaults().matcher("/logout")) .logoutSuccessUrl("/")) // Disable CSRF for the demo. SAML2 ACS endpoint is already exempt by the // framework; this just removes the requirement on /logout. Production apps diff --git a/samples/java/saml-sp-login/src/test/java/com/secureauth/quickstart/ApplicationTests.java b/samples/java/saml-sp-login/src/test/java/com/secureauth/quickstart/ApplicationTests.java index c90e231..7e7d6bc 100644 --- a/samples/java/saml-sp-login/src/test/java/com/secureauth/quickstart/ApplicationTests.java +++ b/samples/java/saml-sp-login/src/test/java/com/secureauth/quickstart/ApplicationTests.java @@ -10,7 +10,7 @@ import org.hamcrest.Matchers; 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.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; diff --git a/samples/java/token-refresh/pom.xml b/samples/java/token-refresh/pom.xml index 51050fc..a9c6ab1 100644 --- a/samples/java/token-refresh/pom.xml +++ b/samples/java/token-refresh/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.1 + 4.0.6 @@ -35,8 +35,8 @@ me.paulschwarz - spring-dotenv - 4.0.0 + springboot4-dotenv + 5.1.0 @@ -44,6 +44,13 @@ spring-boot-starter-test test + + + org.springframework.boot + spring-boot-webmvc-test + test + org.springframework.security spring-security-test @@ -60,7 +67,7 @@ org.codehaus.mojo keytool-maven-plugin - 1.7 + 2.0.2 generate-dev-keystore diff --git a/samples/java/token-refresh/src/test/java/com/secureauth/quickstart/ApplicationTests.java b/samples/java/token-refresh/src/test/java/com/secureauth/quickstart/ApplicationTests.java index 1966555..30bb700 100644 --- a/samples/java/token-refresh/src/test/java/com/secureauth/quickstart/ApplicationTests.java +++ b/samples/java/token-refresh/src/test/java/com/secureauth/quickstart/ApplicationTests.java @@ -10,7 +10,7 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; diff --git a/snippets.json b/snippets.json index 2ba8a79..fb5c9e6 100644 --- a/snippets.json +++ b/snippets.json @@ -617,7 +617,7 @@ ], "framework": "java", "lib": "spring-boot-starter-oauth2-client", - "lib_version": "3.4.1", + "lib_version": "4.0.6", "docs_url": "https://docs.spring.io/spring-security/reference/servlet/oauth2/login/index.html", "install": "", "repo_path": "samples/java/login-auth-code", @@ -775,7 +775,7 @@ ], "framework": "java", "lib": "spring-boot-starter-oauth2-client", - "lib_version": "3.4.1", + "lib_version": "4.0.6", "docs_url": "https://docs.spring.io/spring-security/reference/servlet/oauth2/login/index.html", "install": "", "repo_path": "samples/java/token-refresh", @@ -885,10 +885,10 @@ { "step": 2, "description": "Enable saml2Login and accept unsolicited (IdP-initiated) responses", - "code": " @Configuration\n static class SecurityConfig {\n\n @Bean\n SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();\n Converter defaultValidator =\n OpenSaml4AuthenticationProvider.createDefaultResponseValidator();\n provider.setResponseValidator(token -> {\n Saml2ResponseValidatorResult result = defaultValidator.convert(token);\n List filtered = result.getErrors().stream()\n .filter(e -> !Saml2ErrorCodes.INVALID_IN_RESPONSE_TO.equals(e.getErrorCode()))\n .toList();\n return filtered.isEmpty()\n ? Saml2ResponseValidatorResult.success()\n : Saml2ResponseValidatorResult.failure(filtered);\n });\n // CIAM's SubjectConfirmation interacts badly with Spring Security's strict bearer\n // validator in two ways:\n // 1. The Address attribute is host:port form (e.g. \"[::1]:57370\") rather than\n // IP-only. Disabled via SC_CHECK_ADDRESS=false.\n // 2. The session-stored AuthnRequest used to validate InResponseTo is unreliable\n // across the cross-site POST from CIAM (cookie/session loss). Worked around by\n // feeding the assertion's own InResponseTo back as the expected value, which\n // makes the comparison pass vacuously.\n // Production-grade SPs should fix the upstream Address format and rely on session\n // state for InResponseTo. For a CIAM quickstart, this is acceptable: response-level\n // signature validation (against the IdP signing cert) still runs at the response\n // validator above, so we still confirm the assertion came from CIAM.\n provider.setAssertionValidator(assertionToken -> {\n String assertionInResponseTo = null;\n var subject = assertionToken.getAssertion().getSubject();\n if (subject != null) {\n for (var sc : subject.getSubjectConfirmations()) {\n var data = sc.getSubjectConfirmationData();\n if (data != null && data.getInResponseTo() != null) {\n assertionInResponseTo = data.getInResponseTo();\n break;\n }\n }\n }\n final String expectedInResponseTo = assertionInResponseTo;\n return OpenSaml4AuthenticationProvider.createDefaultAssertionValidatorWithParameters(\n params -> {\n params.put(\n org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_CHECK_ADDRESS,\n Boolean.FALSE);\n if (expectedInResponseTo != null) {\n params.put(\n org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO,\n expectedInResponseTo);\n }\n })\n .convert(assertionToken);\n });\n return http\n .authorizeHttpRequests(auth -> auth\n .requestMatchers(\"/\").permitAll()\n .anyRequest().authenticated())\n .saml2Login(saml -> saml\n .authenticationManager(new ProviderManager(provider))\n .defaultSuccessUrl(\"/\", true))\n // Accept GET on /logout (Spring Security 6.4's default confirmation page\n // requires POST + CSRF; for a quickstart we let a simple \n // link work directly).\n .logout(l -> l\n .logoutRequestMatcher(new org.springframework.security.web.util.matcher.AntPathRequestMatcher(\"/logout\"))\n .logoutSuccessUrl(\"/\"))\n // Disable CSRF for the demo. SAML2 ACS endpoint is already exempt by the\n // framework; this just removes the requirement on /logout. Production apps\n // with state-changing endpoints should re-enable CSRF.\n .csrf(c -> c.disable())\n .build();\n }\n }", + "code": " @Configuration\n static class SecurityConfig {\n\n @Bean\n SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();\n Converter defaultValidator =\n OpenSaml5AuthenticationProvider.createDefaultResponseValidator();\n provider.setResponseValidator(token -> {\n Saml2ResponseValidatorResult result = defaultValidator.convert(token);\n List filtered = result.getErrors().stream()\n .filter(e -> !Saml2ErrorCodes.INVALID_IN_RESPONSE_TO.equals(e.getErrorCode()))\n .toList();\n return filtered.isEmpty()\n ? Saml2ResponseValidatorResult.success()\n : Saml2ResponseValidatorResult.failure(filtered);\n });\n // CIAM's SubjectConfirmation interacts badly with Spring Security's strict bearer\n // validator in two ways:\n // 1. The Address attribute is host:port form (e.g. \"[::1]:57370\") rather than\n // IP-only. Disabled via SC_CHECK_ADDRESS=false.\n // 2. The session-stored AuthnRequest used to validate InResponseTo is unreliable\n // across the cross-site POST from CIAM (cookie/session loss). Worked around by\n // feeding the assertion's own InResponseTo back as the expected value, which\n // makes the comparison pass vacuously.\n // Production-grade SPs should fix the upstream Address format and rely on session\n // state for InResponseTo. For a CIAM quickstart, this is acceptable: response-level\n // signature validation (against the IdP signing cert) still runs at the response\n // validator above, so we still confirm the assertion came from CIAM.\n provider.setAssertionValidator(assertionToken -> {\n String assertionInResponseTo = null;\n var subject = assertionToken.getAssertion().getSubject();\n if (subject != null) {\n for (var sc : subject.getSubjectConfirmations()) {\n var data = sc.getSubjectConfirmationData();\n if (data != null && data.getInResponseTo() != null) {\n assertionInResponseTo = data.getInResponseTo();\n break;\n }\n }\n }\n final String expectedInResponseTo = assertionInResponseTo;\n return OpenSaml5AuthenticationProvider.createDefaultAssertionValidatorWithParameters(\n params -> {\n params.put(\n org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_CHECK_ADDRESS,\n Boolean.FALSE);\n if (expectedInResponseTo != null) {\n params.put(\n org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO,\n expectedInResponseTo);\n }\n })\n .convert(assertionToken);\n });\n return http\n .authorizeHttpRequests(auth -> auth\n .requestMatchers(\"/\").permitAll()\n .anyRequest().authenticated())\n .saml2Login(saml -> saml\n .authenticationManager(new ProviderManager(provider))\n .defaultSuccessUrl(\"/\", true))\n // Accept GET on /logout (Spring Security 6.4's default confirmation page\n // requires POST + CSRF; for a quickstart we let a simple \n // link work directly).\n .logout(l -> l\n // Spring Security 7 dropped AntPathRequestMatcher; PathPatternRequestMatcher\n // is the replacement. `.matcher(path)` (no HTTP method) matches any method.\n .logoutRequestMatcher(org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.withDefaults().matcher(\"/logout\"))\n .logoutSuccessUrl(\"/\"))\n // Disable CSRF for the demo. SAML2 ACS endpoint is already exempt by the\n // framework; this just removes the requirement on /logout. Production apps\n // with state-changing endpoints should re-enable CSRF.\n .csrf(c -> c.disable())\n .build();\n }\n }", "file": "src/main/java/com/secureauth/quickstart/Application.java", "lang": "java", - "lines": "97-173" + "lines": "97-175" }, { "step": 3, @@ -896,12 +896,12 @@ "code": " @Controller\n static class HomeController {\n\n @GetMapping(\"/\")\n @ResponseBody\n String home(@AuthenticationPrincipal Saml2AuthenticatedPrincipal user) {\n if (user == null) {\n return \"\"\"\n \n SecureAuth Java SAML Demo\n \n

SecureAuth Java SAML Demo

\n

Sign in

\n \n \"\"\";\n }\n return \"\"\"\n \n SecureAuth Java SAML Demo\n \n

SecureAuth Java SAML Demo

\n

Welcome, %s

\n

Sign out

\n \n \"\"\".formatted(esc(user.getName()));\n }\n\n private static String esc(String s) {\n if (s == null) return \"\";\n return s\n .replace(\"&\", \"&\")\n .replace(\"<\", \"<\")\n .replace(\">\", \">\")\n .replace(\"\\\"\", \""\")\n .replace(\"'\", \"'\");\n }\n }", "file": "src/main/java/com/secureauth/quickstart/Application.java", "lang": "java", - "lines": "176-214" + "lines": "178-216" } ], "framework": "java", "lib": "spring-security-saml2-service-provider", - "lib_version": "3.4.1", + "lib_version": "4.0.6", "docs_url": "https://docs.spring.io/spring-security/reference/servlet/saml2/login/index.html", "install": "", "repo_path": "samples/java/saml-sp-login",