From 91aee30906976782853dd24d38c14987f1bb25ea Mon Sep 17 00:00:00 2001 From: Robert Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:46:40 -0600 Subject: [PATCH] Null safety via JSpecify spring-security-kerberos-client Closes gh-18552 --- .../spring-security-kerberos-client.gradle | 1 + .../kerberos/client/KerberosRestTemplate.java | 72 +++++++++++-------- .../client/config/SunJaasKrb5LoginConfig.java | 7 +- .../kerberos/client/config/package-info.java | 20 ++++++ .../ldap/KerberosLdapContextSource.java | 11 ++- .../kerberos/client/ldap/package-info.java | 20 ++++++ .../kerberos/client/package-info.java | 20 ++++++ 7 files changed, 114 insertions(+), 37 deletions(-) create mode 100644 kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/package-info.java create mode 100644 kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/package-info.java create mode 100644 kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/package-info.java diff --git a/kerberos/kerberos-client/spring-security-kerberos-client.gradle b/kerberos/kerberos-client/spring-security-kerberos-client.gradle index fef7c551ab..5b2cc4501c 100644 --- a/kerberos/kerberos-client/spring-security-kerberos-client.gradle +++ b/kerberos/kerberos-client/spring-security-kerberos-client.gradle @@ -1,4 +1,5 @@ plugins { + id 'security-nullability' id 'io.spring.convention.spring-module' id 'javadoc-warnings-error' } 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 a4fe210123..4c8cb9094c 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 @@ -51,6 +51,7 @@ import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.RegistryBuilder; +import org.jspecify.annotations.Nullable; import org.springframework.http.HttpMethod; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -82,13 +83,13 @@ public class KerberosRestTemplate extends RestTemplate { private static final Credentials credentials = new NullCredentials(); - private final String keyTabLocation; + private final @Nullable String keyTabLocation; - private final String userPrincipal; + private final @Nullable String userPrincipal; - private final String password; + private final @Nullable String password; - private final Map loginOptions; + private final @Nullable Map loginOptions; /** * Instantiates a new kerberos rest template. @@ -110,7 +111,7 @@ public class KerberosRestTemplate extends RestTemplate { * @param keyTabLocation the key tab location * @param userPrincipal the user principal */ - public KerberosRestTemplate(String keyTabLocation, String userPrincipal) { + public KerberosRestTemplate(@Nullable String keyTabLocation, @Nullable String userPrincipal) { this(keyTabLocation, userPrincipal, buildHttpClient()); } @@ -120,7 +121,8 @@ public class KerberosRestTemplate extends RestTemplate { * @param userPrincipal the user principal * @param httpClient the http client */ - public KerberosRestTemplate(String keyTabLocation, String userPrincipal, HttpClient httpClient) { + public KerberosRestTemplate(@Nullable String keyTabLocation, @Nullable String userPrincipal, + HttpClient httpClient) { this(keyTabLocation, userPrincipal, null, null, httpClient); } @@ -128,7 +130,7 @@ public class KerberosRestTemplate extends RestTemplate { * Instantiates a new kerberos rest template. * @param loginOptions the login options */ - public KerberosRestTemplate(Map loginOptions) { + public KerberosRestTemplate(@Nullable Map loginOptions) { this(null, null, null, loginOptions, buildHttpClient()); } @@ -137,7 +139,7 @@ public class KerberosRestTemplate extends RestTemplate { * @param loginOptions the login options * @param httpClient the http client */ - public KerberosRestTemplate(Map loginOptions, HttpClient httpClient) { + public KerberosRestTemplate(@Nullable Map loginOptions, HttpClient httpClient) { this(null, null, null, loginOptions, httpClient); } @@ -147,7 +149,8 @@ public class KerberosRestTemplate extends RestTemplate { * @param userPrincipal the user principal * @param loginOptions the login options */ - public KerberosRestTemplate(String keyTabLocation, String userPrincipal, Map loginOptions) { + public KerberosRestTemplate(@Nullable String keyTabLocation, @Nullable String userPrincipal, + @Nullable Map loginOptions) { this(keyTabLocation, userPrincipal, null, loginOptions, buildHttpClient()); } @@ -158,8 +161,8 @@ public class KerberosRestTemplate extends RestTemplate { * @param password the password * @param loginOptions the login options */ - public KerberosRestTemplate(String keyTabLocation, String userPrincipal, String password, - Map loginOptions) { + public KerberosRestTemplate(@Nullable String keyTabLocation, @Nullable String userPrincipal, + @Nullable String password, @Nullable Map loginOptions) { this(keyTabLocation, userPrincipal, password, loginOptions, buildHttpClient()); } @@ -171,8 +174,8 @@ public class KerberosRestTemplate extends RestTemplate { * @param loginOptions the login options * @param httpClient the http client */ - private KerberosRestTemplate(String keyTabLocation, String userPrincipal, String password, - Map loginOptions, HttpClient httpClient) { + private KerberosRestTemplate(@Nullable String keyTabLocation, @Nullable String userPrincipal, + @Nullable String password, @Nullable Map loginOptions, HttpClient httpClient) { super(new HttpComponentsClientHttpRequestFactory(httpClient)); this.keyTabLocation = keyTabLocation; this.userPrincipal = userPrincipal; @@ -226,9 +229,9 @@ public class KerberosRestTemplate extends RestTemplate { } @Override - protected final T doExecute(final URI url, final String uriTemplate, final HttpMethod method, - final RequestCallback requestCallback, final ResponseExtractor responseExtractor) - throws RestClientException { + protected final T doExecute(final URI url, final @Nullable String uriTemplate, + final @Nullable HttpMethod method, final @Nullable RequestCallback requestCallback, + final @Nullable ResponseExtractor responseExtractor) throws RestClientException { try { LoginContext lc = buildLoginContext(); @@ -249,23 +252,28 @@ public class KerberosRestTemplate extends RestTemplate { } } - private T doExecuteSubject(URI url, String uriTemplate, HttpMethod method, RequestCallback requestCallback, - ResponseExtractor responseExtractor) throws RestClientException { - return super.doExecute(url, uriTemplate, method, requestCallback, responseExtractor); + private 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; } private static final class ClientLoginConfig extends Configuration { - private final String keyTabLocation; + private final @Nullable String keyTabLocation; - private final String userPrincipal; + private final @Nullable String userPrincipal; - private final String password; + private final @Nullable String password; - private final Map loginOptions; + private final @Nullable Map loginOptions; - private ClientLoginConfig(String keyTabLocation, String userPrincipal, String password, - Map loginOptions) { + private ClientLoginConfig(@Nullable String keyTabLocation, @Nullable String userPrincipal, + @Nullable String password, @Nullable Map loginOptions) { super(); this.keyTabLocation = keyTabLocation; this.userPrincipal = userPrincipal; @@ -309,12 +317,12 @@ public class KerberosRestTemplate extends RestTemplate { private static class NullCredentials implements Credentials { @Override - public Principal getUserPrincipal() { + public @Nullable Principal getUserPrincipal() { return null; } @Override - public char[] getPassword() { + public char @Nullable [] getPassword() { return null; } @@ -322,11 +330,11 @@ public class KerberosRestTemplate extends RestTemplate { private static final class CallbackHandlerImpl implements CallbackHandler { - private final String userPrincipal; + private final @Nullable String userPrincipal; - private final String password; + private final @Nullable String password; - private CallbackHandlerImpl(String userPrincipal, String password) { + private CallbackHandlerImpl(@Nullable String userPrincipal, @Nullable String password) { super(); this.userPrincipal = userPrincipal; this.password = password; @@ -342,7 +350,9 @@ public class KerberosRestTemplate extends RestTemplate { } else if (callback instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) callback; - pc.setPassword(this.password.toCharArray()); + if (this.password != null) { + pc.setPassword(this.password.toCharArray()); + } } else { throw new UnsupportedCallbackException(callback, "Unknown Callback"); diff --git a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/SunJaasKrb5LoginConfig.java b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/SunJaasKrb5LoginConfig.java index 92b3daca03..836dc07ee8 100644 --- a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/SunJaasKrb5LoginConfig.java +++ b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/SunJaasKrb5LoginConfig.java @@ -23,6 +23,7 @@ import javax.security.auth.login.Configuration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.ClassPathResource; @@ -40,9 +41,9 @@ public class SunJaasKrb5LoginConfig extends Configuration implements Initializin private static final Log LOG = LogFactory.getLog(SunJaasKrb5LoginConfig.class); - private String servicePrincipal; + private @Nullable String servicePrincipal; - private Resource keyTabLocation; + private @Nullable Resource keyTabLocation; private Boolean useTicketCache = false; @@ -50,7 +51,7 @@ public class SunJaasKrb5LoginConfig extends Configuration implements Initializin private Boolean debug = false; - private String keyTabLocationAsString; + private @Nullable String keyTabLocationAsString; public void setServicePrincipal(String servicePrincipal) { this.servicePrincipal = servicePrincipal; diff --git a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/package-info.java b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/package-info.java new file mode 100644 index 0000000000..52c43b3481 --- /dev/null +++ b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/config/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NullMarked +package org.springframework.security.kerberos.client.config; + +import org.jspecify.annotations.NullMarked; diff --git a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/KerberosLdapContextSource.java b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/KerberosLdapContextSource.java index 1ec332a442..93e7b78f09 100644 --- a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/KerberosLdapContextSource.java +++ b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/KerberosLdapContextSource.java @@ -29,6 +29,8 @@ import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.InitializingBean; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig; @@ -65,7 +67,7 @@ import org.springframework.util.Assert; */ public class KerberosLdapContextSource extends DefaultSpringSecurityContextSource implements InitializingBean { - private Configuration loginConfig; + private @Nullable Configuration loginConfig; /** * Instantiates a new kerberos ldap context source. @@ -108,10 +110,10 @@ public class KerberosLdapContextSource extends DefaultSpringSecurityContextSourc Subject serviceSubject = login(); final NamingException[] suppressedException = new NamingException[] { null }; - DirContext dirContext = Subject.doAs(serviceSubject, new PrivilegedAction<>() { + DirContext dirContext = Subject.doAs(serviceSubject, new PrivilegedAction<@Nullable DirContext>() { @Override - public DirContext run() { + public @Nullable DirContext run() { try { return KerberosLdapContextSource.super.getDirContextInstance(environment); } @@ -125,6 +127,9 @@ public class KerberosLdapContextSource extends DefaultSpringSecurityContextSourc if (suppressedException[0] != null) { throw suppressedException[0]; } + if (dirContext == null) { + throw new NamingException("Failed to obtain DirContext"); + } return dirContext; } diff --git a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/package-info.java b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/package-info.java new file mode 100644 index 0000000000..9f5bd7f337 --- /dev/null +++ b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/ldap/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NullMarked +package org.springframework.security.kerberos.client.ldap; + +import org.jspecify.annotations.NullMarked; diff --git a/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/package-info.java b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/package-info.java new file mode 100644 index 0000000000..a2042405af --- /dev/null +++ b/kerberos/kerberos-client/src/main/java/org/springframework/security/kerberos/client/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NullMarked +package org.springframework.security.kerberos.client; + +import org.jspecify.annotations.NullMarked;