From a377581951fce551c536b35ff2ba1c06c2f7bf36 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 21 Aug 2019 08:31:30 -0500 Subject: [PATCH] Fix WebClient Memory Leaks WebClient exchange requires that the body is consumed. Before this commit there were places where an Exception was thrown without consuming the body if the status was not successful. There was also the potential for the statusCode invocation to throw an Exception of the status code was not defined which would cause a leak. This commit ensures that before the Exception is thrown the body is consumed. It also uses the http status in a way that will ensure an Exception is not thrown. Fixes gh-7293 --- ...activeClientCredentialsTokenResponseClient.java | 14 ++++++++++---- .../NimbusReactiveOpaqueTokenIntrospector.java | 8 ++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java index 9249722ba5..6acfd38547 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java @@ -15,7 +15,10 @@ */ package org.springframework.security.oauth2.client.endpoint; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -66,15 +69,18 @@ public class WebClientReactiveClientCredentialsTokenResponseClient implements Re .headers(headers(clientRegistration)) .body(body) .exchange() - .flatMap(response ->{ - if (!response.statusCode().is2xxSuccessful()){ + .flatMap(response -> { + HttpStatus status = HttpStatus.resolve(response.rawStatusCode()); + if (status == null || !status.is2xxSuccessful()) { // extract the contents of this into a method named oauth2AccessTokenResponse but has an argument for the response - throw WebClientResponseException.create(response.rawStatusCode(), + return response.bodyToFlux(DataBuffer.class) + .map(DataBufferUtils::release) + .then(Mono.error(WebClientResponseException.create(response.rawStatusCode(), "Cannot get token, expected 2xx HTTP Status code", null, null, null - ); + ))); } return response.body(oauth2AccessTokenResponse()); }) .map(response -> { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java index 01e5b354c4..5fb1646fc7 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java @@ -28,6 +28,8 @@ import com.nimbusds.oauth2.sdk.TokenIntrospectionResponse; import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.id.Audience; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; @@ -116,8 +118,10 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke HTTPResponse response = new HTTPResponse(responseEntity.rawStatusCode()); response.setHeader(HttpHeaders.CONTENT_TYPE, responseEntity.headers().contentType().get().toString()); if (response.getStatusCode() != HTTPResponse.SC_OK) { - throw new OAuth2IntrospectionException( - "Introspection endpoint responded with " + response.getStatusCode()); + return responseEntity.bodyToFlux(DataBuffer.class) + .map(DataBufferUtils::release) + .then(Mono.error(new OAuth2IntrospectionException( + "Introspection endpoint responded with " + response.getStatusCode()))); } return responseEntity.bodyToMono(String.class) .doOnNext(response::setContent)