Description
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
- 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
- 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));
}