spring-security: SecurityContextHolderFilter does not apply to async dispatch

Describe the bug

As mentioned in gitter

My app currently runs on Spring Boot 2.7.4. I was testing compatibility with 3.0.0-M5. All appeared to work well except one aspect of spring-security, as part of presenting a client certificate using WebClient. I end up with “AccessDeniedException: Access is denied” thrown by AffirmativeBased.decide(AffirmativeBased.java:73). The handshake appears to work fine, and the handshake logging looks nearly identical to that under 2.7.4. But I got it to work after downgrading to spring-security:6.0.0-M5 (from M7). So, it seems something broke as of spring-security:6.0.0-M6. Is this a known issue, and will it be fixed in the next spring-security release?

To Reproduce

  1. Under standard configuration of Spring Boot 3.0.0-M5, which includes spring-security:6.0.0-M7 (or even downgrading to M6), run the code below against a URL resource protected by client certificate authorization
  2. Witness AccessDeniedException: Access is denied" thrown by AffirmativeBased.decide(AffirmativeBased.java:73)

Expected behavior

Client certificate authentication succeeds… as it does under both Spring Boot 2.7.4 and 3.0.0-M5 with spring-security downgraded to 6.0.0-M5 (from 6.0.0-M7)

Sample

private StreamingResponseBody streamUrl(final String url) {
    final String keyStoreResourcePath = "[url resource path to key/trust store]", keyStorePass = "[some password]";
    final HttpClient httpClient = HttpClient.create().secure(spec -> {
        try {
            final char[] keyStorePassChars = keyStorePass.toCharArray();
            final URL keyStoreUrl = ResourceUtils.getURL(keyStoreResourcePath);
            final KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(keyStoreUrl.openStream(), keyStorePassChars);
            final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, keyStorePassChars);
            final TrustManagerFactory trustManagerFactory = InsecureTrustManagerFactory.INSTANCE;
            spec.sslContext(Http2SslContextSpec.forClient().configure(sslContextBuilder -> sslContextBuilder.keyManager(keyManagerFactory).trustManager(trustManagerFactory)));
        } catch (final NoSuchAlgorithmException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException e) {
            throw new IllegalStateException("Boom!", e);
        }
    });
    final WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).baseUrl(url).build();
    final Flux<DataBuffer> dataBufferFlux = client.get()
            .accept(MediaType.APPLICATION_PDF)
            .retrieve()
            .bodyToFlux(DataBuffer.class);
    return out -> DataBufferUtils.write(dataBufferFlux, out).doOnNext(DataBufferUtils.releaseConsumer()).blockLast(Duration.ofSeconds(20));
}

@GetMapping(path = "/docs/{docId}", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<StreamingResponseBody> stream(@PathVariable final String docId) {
    final String url = lookupUrlGivenDocId(docId); //Some client cert auth protected document URL being streamed on behalf of the client
    return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).body(streamUrl(url));
}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 27 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Thank you @fkbeys for the detailed solution of yours.

@marcusdacoregio I tried the filter as well, and it works for me too.

@marcusdacoregio So with the filter I no longer have the exception.