diff --git a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/KerberosRestTemplate.java b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/KerberosRestTemplate.java index c73b6c19778..1b00277062e 100644 --- a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/KerberosRestTemplate.java +++ b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/KerberosRestTemplate.java @@ -230,7 +230,7 @@ private LoginContext buildLoginContext() throws LoginException { } @Override - protected final T doExecute(final URI url, final @Nullable String uriTemplate, + protected final @Nullable T doExecute(final URI url, final @Nullable String uriTemplate, final @Nullable HttpMethod method, final @Nullable RequestCallback requestCallback, final @Nullable ResponseExtractor responseExtractor) throws RestClientException { @@ -238,10 +238,10 @@ protected final T doExecute(final URI url, final @Nullable String uriTemplat LoginContext lc = buildLoginContext(); lc.login(); Subject serviceSubject = lc.getSubject(); - return Subject.doAs(serviceSubject, new PrivilegedAction() { + return Subject.doAs(serviceSubject, new PrivilegedAction<@Nullable T>() { @Override - public T run() { + public @Nullable T run() { return KerberosRestTemplate.this.doExecuteSubject(url, uriTemplate, method, requestCallback, responseExtractor); } @@ -256,14 +256,14 @@ public T run() { } } - private T doExecuteSubject(URI url, @Nullable String uriTemplate, @Nullable HttpMethod method, + private @Nullable T doExecuteSubject(URI url, @Nullable String uriTemplate, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor responseExtractor) throws RestClientException { - T result = super.doExecute(url, uriTemplate, method, requestCallback, responseExtractor); - if (result == null) { - throw new RestClientException("doExecute returned null"); - } - return result; + // RestTemplate.doExecute() legitimately returns null, e.g. when there is + // no ResponseExtractor (delete(), put()) or when the extractor maps a + // response such as 204 No Content to null. Let the null pass through + // instead of failing a successful request. + return super.doExecute(url, uriTemplate, method, requestCallback, responseExtractor); } private static final class ClientLoginConfig extends Configuration { diff --git a/kerberos/kerberos-client/src/test/java/org/springframework/security/kerberos/client/KerberosRestTemplateTests.java b/kerberos/kerberos-client/src/test/java/org/springframework/security/kerberos/client/KerberosRestTemplateTests.java index a0b0103b55f..d52a3f65130 100644 --- a/kerberos/kerberos-client/src/test/java/org/springframework/security/kerberos/client/KerberosRestTemplateTests.java +++ b/kerberos/kerberos-client/src/test/java/org/springframework/security/kerberos/client/KerberosRestTemplateTests.java @@ -97,6 +97,13 @@ void throwsOriginalException() { .isThrownBy(() -> this.restTemplate.getForObject(this.baseUrl + "/notfound", String.class)); } + @Test + void deleteWithNoContentSucceeds() { + // gh-19935: RestTemplate.doExecute() legitimately returns null for delete(); + // a successful 204 must not surface as RestClientException. + this.restTemplate.delete(this.baseUrl + "/delete"); + } + private void setUpClient() { Map loginOptions = new HashMap<>(); loginOptions.put("refreshKrb5Config", "true"); @@ -136,6 +143,16 @@ else if (header.startsWith("Negotiate ")) { return getRequest(request, helloWorldBytes, textContentType.toString()); } } + if (request.getPath().equals("/delete")) { + String header = request.getHeader(HttpHeaders.AUTHORIZATION); + if (header == null) { + return new MockResponse().setResponseCode(401) + .addHeader(HttpHeaders.WWW_AUTHENTICATE, "Negotiate"); + } + else if (header.startsWith("Negotiate ")) { + return new MockResponse().setResponseCode(204); + } + } return new MockResponse().setResponseCode(404); } catch (Throwable ex) {