Skip to content

WebSessionServerRequestCache only supports saving GET requests #9690

Closed
@stffrdhrn

Description

@stffrdhrn

Expected Behavior

WebSessionServerRequestCache or another ServerRequestCache implementation should support saving POST requests for replaying after authentication is successful.

Current Behavior

WebSessionServerRequestCache only supports saving GET requests. It can be extended to support matching other requests with setSaveRequestMatcher() but it will only be able to save/replay the original GET url.

Context

We have internally implemented SAML in spring security by creating my own AuthenticationWebFilter that is configured to create and validate SAML requests and responses. Our SAML supports configuration as both an IDP and SP. The SAML core is implemented around org.opensaml XML and security api's. This is as requested in issue #7954

Background reading on SAML: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html

See section: SP-Initiated SSO: Redirect/POST Bindings

When our server is configured as an IDP, a POST request may come to our server with a request for SAML verification. Our IDP endpoint looks like this:

@RestController
@RequestMapping("/saml/idp")
public class SamlIdpController {
    @PostMapping(value = "/ls", produces = MediaType.TEXT_HTML_VALUE)
    public Mono<String> loginService(Principal principal, ServerWebExchange exchange) {
        return exchange.getFormData().flatMap(formData -> {
            String samlRequest = formData.getFirst("SAMLRequest");
            String relayState = formData.getFirst("RelayState");
            if (samlRequest != null) {
                return processLoginRequest("POST", principal, samlRequest, relayState);
            }
            logger.info("Authentication did not succeed for SAMLRequest: {} from {}", samlRequest, getRemoteHostAddress(exchange.getRequest()));
            return Mono.empty();
        });
    }
}

Before the POST can be processed the authentication is handled with AuthenticationWebFilter . If the users is not authenticated they will be redirected for authentication. i.e. LoginForm/BasicAuth. Then they "should" be redirected back to the SAML IDP endpoint.

This doesn't doesn't happen because the WebSessionServerRequestCache wired into AuthenticationWebFilter doesn't support replaying the SAML POST request.

Workaround

I have implemented a PostSavingWebSessionRequestCache. The does something like the below, and with another hack it works:

public class PostSavingWebSessionRequestCache implements ServerRequestCache {

    @Override
    public Mono<Void> saveRequest(ServerWebExchange exchange) {
        String requestPath = pathInApplication(exchange.getRequest());
        if (exchange.getRequest().getMethod() == POST) {
            return this.postMatcher.matches(exchange).filter(ServerWebExchangeMatcher.MatchResult::isMatch)
                    .flatMap((m) -> exchange.getSession()).map(WebSession::getAttributes).doOnNext((attrs) -> {
                        attrs.put(SAVED_PATH_ATTR, requestPath);
                        attrs.put(SAVED_REQUEST_ATTR, serializePost(exchange));
                        logger.info("POST Request added to WebSession: {}", requestPath);
                    }).then();
        } else {
            return this.getMatcher.matches(exchange).filter(ServerWebExchangeMatcher.MatchResult::isMatch)
                    .flatMap((m) -> exchange.getSession()).map(WebSession::getAttributes).doOnNext((attrs) -> {
                        attrs.put(SAVED_PATH_ATTR, requestPath);
                        logger.info("{} Request added to WebSession: {}", exchange.getRequest().getMethod(),
                                requestPath);
                    }).then();
        }
    }

    @Override
    public Mono<ServerHttpRequest> removeMatchingRequest(ServerWebExchange exchange) {
        return exchange.getSession().map(WebSession::getAttributes).flatMap((attributes) -> {
            String requestPath = pathInApplication(exchange.getRequest());
            boolean removed = attributes.remove(SAVED_PATH_ATTR, requestPath);
            if (removed) {
                logger.debug(LogMessage.format("Request removed from WebSession: '%s'", requestPath));
            }
            if (removed) {
                ServerHttpRequest request = (ServerHttpRequest) attributes.remove(SAVED_REQUEST_ATTR);
                if (request == null) {
                    logger.info("{} Request continuing: {}", exchange.getRequest().getMethod(), requestPath);
                    return Mono.just(exchange.getRequest());
                } else {
                    logger.info("POST Request replaying from cache: {}", requestPath);
                    return Mono.just(request);
                }
            } else {
                return Mono.empty();
            }
        });
    }
}

It would be nice if something like this existed out of the box in spring security.

Possible Concerns

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: webAn issue in web modules (web, webmvc)type: enhancementA general enhancement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions