viglu
viglu

Reputation: 175

Spring webflux: CSP with dynamic nonce

I have a Spring boot 3.4 Webflux App. I need do configure CSP with dynamic nonce Here my code

My securityChain

@Bean public SecurityWebFilterChain otherSecurityWebFilterChain(final ServerHttpSecurity http) {

http
        .securityMatcher(ServerWebExchangeMatchers.pathMatchers("/**"))
        .authorizeExchange(auth -> auth.anyExchange().permitAll())
        .headers(headers -> headers
                .contentSecurityPolicy(csp ->
                        csp.policyDirectives("base-uri 'self'; form-action 'self'; script-src 'nonce-{nonce}' 'strict-dynamic' https:; object-src 'self';")
                )
        )
        .addFilterBefore(new CSPFilter(), SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);

return http.build();

}

My WebFilter

public class CSPFilter implements WebFilter {


    @Override
    public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {

        final var request = exchange.getRequest();
        final var response = exchange.getResponse();

        final var nonce = this.generateNonce();
        
        final var decoratedResponse = new NonceResponseDecorator(response, nonce);

        request.getAttributes().put("nonce", nonce);

        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    private String generateNonce() {

        final var nonceArray = new byte[64];
        new SecureRandom().nextBytes(nonceArray);

        return Base64.getEncoder().encodeToString(nonceArray);
    }

}

My Wrapper

class NonceResponseDecorator extends ServerHttpResponseDecorator {

    private final String nonce;

    public NonceResponseDecorator(final ServerHttpResponse delegate, final String nonce) {

        super(delegate);
        this.nonce = nonce;
    }

    private String getHeaderValue(final String name, final String value) {

        if (name.equals("Content-Security-Policy") && StringUtils.hasText(value)) {
            return value.replace("{nonce}", this.nonce);
        } else {
            return value;
        }
    }

    @Override
    public Mono<Void> writeWith(final Publisher<? extends DataBuffer> body) {

        this.setCspHeader();
        return super.writeWith(body);
    }

    @Override
    public Mono<Void> writeAndFlushWith(final Publisher<? extends Publisher<? extends DataBuffer>> body) {

        this.setCspHeader();
        return super.writeAndFlushWith(body);
    }

    private void setCspHeader() {

        final HttpHeaders headers = this.getDelegate().getHeaders();
        final String cspHeaderValue = headers.getFirst("Content-Security-Policy");
        if (cspHeaderValue != null) {
            headers.set("Content-Security-Policy", this.getHeaderValue("Content-Security-Policy", cspHeaderValue));
        }
    }

}

But in my filter the CSP header is never set. I miss something but cannot find out what

Upvotes: 1

Views: 10

Answers (0)

Related Questions