1
0
mirror of synced 2026-07-08 04:10:03 +00:00

Use FormPostRedirectStrategy

Closes gh-16673

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
Josh Cummings
2026-04-29 14:12:56 -06:00
parent a14c9d66b1
commit 356131b1ea
6 changed files with 49 additions and 157 deletions
@@ -24,17 +24,17 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.FormPostRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
@@ -67,6 +67,8 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = new HttpSessionSaml2AuthenticationRequestRepository();
private final RedirectStrategy formPostRedirectStrategy = new FormPostRedirectStrategy();
/**
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the strategy for
* resolving the {@code AuthnRequest}
@@ -132,54 +134,11 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
private void sendPost(HttpServletRequest request, HttpServletResponse response,
Saml2PostAuthenticationRequest authenticationRequest) throws IOException {
this.authenticationRequestRepository.saveAuthenticationRequest(authenticationRequest, request, response);
String html = createSamlPostRequestFormData(authenticationRequest);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
}
private String createSamlPostRequestFormData(Saml2PostAuthenticationRequest authenticationRequest) {
String authenticationRequestUri = authenticationRequest.getAuthenticationRequestUri();
String relayState = authenticationRequest.getRelayState();
String samlRequest = authenticationRequest.getSamlRequest();
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body>\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(authenticationRequestUri);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"");
html.append(HtmlUtils.htmlEscape(samlRequest));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
}
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" <script>window.onload = function() { document.forms[0].submit(); }</script>\n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
UriComponentsBuilder uriBuilder = UriComponentsBuilder
.fromUriString(authenticationRequest.getAuthenticationRequestUri());
addParameter(Saml2ParameterNames.SAML_REQUEST, authenticationRequest.getSamlRequest(), uriBuilder);
addParameter(Saml2ParameterNames.RELAY_STATE, authenticationRequest.getRelayState(), uriBuilder);
this.formPostRedirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
}
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.provider.service.web.authentication.logout;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -26,7 +27,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -46,6 +46,7 @@ import org.springframework.security.saml2.provider.service.web.RelyingPartyRegis
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.FormPostRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
@@ -54,8 +55,8 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
/**
* A filter for handling logout requests in the form of a &lt;saml2:LogoutRequest&gt; sent
@@ -82,6 +83,8 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private final RedirectStrategy formPostRedirectStrategy = new FormPostRedirectStrategy();
public Saml2LogoutRequestFilter(Saml2LogoutRequestValidatorParametersResolver logoutRequestResolver,
Saml2LogoutRequestValidator logoutRequestValidator, Saml2LogoutResponseResolver logoutResponseResolver,
LogoutHandler... handlers) {
@@ -158,7 +161,7 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
doRedirect(request, response, logoutResponse);
}
else {
doPost(response, logoutResponse);
doPost(request, response, logoutResponse);
}
}
@@ -189,55 +192,21 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private void doPost(HttpServletResponse response, Saml2LogoutResponse logoutResponse) throws IOException {
private void doPost(HttpServletRequest request, HttpServletResponse response, Saml2LogoutResponse logoutResponse)
throws IOException {
String location = logoutResponse.getResponseLocation();
String saml = logoutResponse.getSamlResponse();
String relayState = logoutResponse.getRelayState();
String html = createSamlPostRequestFormData(location, saml, relayState);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location);
addParameter(Saml2ParameterNames.SAML_RESPONSE, logoutResponse.getSamlResponse(), uriBuilder);
addParameter(Saml2ParameterNames.RELAY_STATE, logoutResponse.getRelayState(), uriBuilder);
this.formPostRedirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private String createSamlPostRequestFormData(String location, String saml, String relayState) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body>\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(location);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLResponse\" value=\"");
html.append(HtmlUtils.htmlEscape(saml));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
private void addParameter(String name, String value, UriComponentsBuilder builder) {
Assert.hasText(name, "name cannot be empty or null");
if (StringUtils.hasText(value)) {
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
UriUtils.encode(value, StandardCharsets.ISO_8859_1));
}
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" <script>window.onload = function() { document.forms[0].submit(); }</script>\n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
}
private static class Saml2AssertingPartyLogoutRequestResolver
@@ -17,23 +17,25 @@
package org.springframework.security.saml2.provider.service.web.authentication.logout;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.FormPostRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
/**
* A success handler for issuing a SAML 2.0 Logout Request to the SAML 2.0 Asserting Party
@@ -49,6 +51,8 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private final RedirectStrategy formPostRedirectStrategy = new FormPostRedirectStrategy();
private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository();
/**
@@ -82,7 +86,7 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
doRedirect(request, response, logoutRequest);
}
else {
doPost(response, logoutRequest);
doPost(request, response, logoutRequest);
}
}
@@ -104,55 +108,21 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private void doPost(HttpServletResponse response, Saml2LogoutRequest logoutRequest) throws IOException {
private void doPost(HttpServletRequest request, HttpServletResponse response, Saml2LogoutRequest logoutRequest)
throws IOException {
String location = logoutRequest.getLocation();
String saml = logoutRequest.getSamlRequest();
String relayState = logoutRequest.getRelayState();
String html = createSamlPostRequestFormData(location, saml, relayState);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location);
addParameter(Saml2ParameterNames.SAML_REQUEST, logoutRequest.getSamlRequest(), uriBuilder);
addParameter(Saml2ParameterNames.RELAY_STATE, logoutRequest.getRelayState(), uriBuilder);
this.formPostRedirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private String createSamlPostRequestFormData(String location, String saml, String relayState) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body>\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(location);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"");
html.append(HtmlUtils.htmlEscape(saml));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
private void addParameter(String name, String value, UriComponentsBuilder builder) {
Assert.hasText(name, "name cannot be empty or null");
if (StringUtils.hasText(value)) {
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
UriUtils.encode(value, StandardCharsets.ISO_8859_1));
}
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" <script>window.onload = function() { document.forms[0].submit(); }</script>\n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
}
}
@@ -178,12 +178,10 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
given(this.authenticationRequestResolver.resolve(any())).willReturn(request);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).isNull();
assertThat(this.response.getContentAsString()).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">")
.contains("<script>window.onload = function() { document.forms[0].submit(); }</script>")
.contains("<form action=\"https://sso-url.example.com/IDP/SSO\" method=\"post\">")
.contains("<input type=\"hidden\" name=\"SAMLRequest\"")
assertThat(this.response.getContentAsString()).contains("action=\"https://sso-url.example.com/IDP/SSO\"")
.contains("name=\"SAMLRequest\"")
.contains("value=\"" + relayStateEncoded + "\"");
assertThat(this.response.getHeader("Content-Security-Policy")).matches("script-src 'nonce-.+'");
}
@Test
@@ -120,9 +120,7 @@ public class Saml2LogoutRequestFilterTests {
String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_RESPONSE);
assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation());
assertThat(content).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">");
assertThat(content).contains("<script>window.onload = function() { document.forms[0].submit(); }</script>");
assertThat(response.getHeader("Content-Security-Policy")).matches("script-src 'nonce-.+'");
verify(this.securityContextHolderStrategy).getContext();
}
@@ -100,9 +100,7 @@ public class Saml2RelyingPartyInitiatedLogoutSuccessHandlerTests {
String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_REQUEST);
assertThat(content).contains(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation());
assertThat(content).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">");
assertThat(content).contains("<script>window.onload = function() { document.forms[0].submit(); }</script>");
assertThat(response.getHeader("Content-Security-Policy")).matches("script-src 'nonce-.+'");
}
private Saml2Authentication authentication(RelyingPartyRegistration registration) {