diff --git a/kerberos/kerberos-core/spring-security-kerberos-core.gradle b/kerberos/kerberos-core/spring-security-kerberos-core.gradle index e989c20097..1aa30ee3d6 100644 --- a/kerberos/kerberos-core/spring-security-kerberos-core.gradle +++ b/kerberos/kerberos-core/spring-security-kerberos-core.gradle @@ -1,4 +1,5 @@ plugins { + id 'security-nullability' id 'io.spring.convention.spring-module' id 'javadoc-warnings-error' } diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java index 173b6b0944..73f9792dd7 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/JaasSubjectHolder.java @@ -22,6 +22,8 @@ import java.util.Map; import javax.security.auth.Subject; +import org.jspecify.annotations.Nullable; + import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient; /** @@ -40,7 +42,7 @@ public class JaasSubjectHolder implements Serializable { private Subject jaasSubject; - private String username; + private @Nullable String username; private Map savedTokens = new HashMap(); @@ -53,7 +55,7 @@ public class JaasSubjectHolder implements Serializable { this.username = username; } - public String getUsername() { + public @Nullable String getUsername() { return this.username; } @@ -65,7 +67,7 @@ public class JaasSubjectHolder implements Serializable { this.savedTokens.put(targetService, outToken); } - public byte[] getToken(String principalName) { + public byte @Nullable [] getToken(String principalName) { return this.savedTokens.get(principalName); } diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java index 35b907286e..9280a0a312 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosAuthenticationProvider.java @@ -16,6 +16,8 @@ package org.springframework.security.kerberos.authentication; +import org.jspecify.annotations.Nullable; + import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -32,17 +34,31 @@ import org.springframework.security.core.userdetails.UserDetailsService; */ public class KerberosAuthenticationProvider implements AuthenticationProvider { - private KerberosClient kerberosClient; + private @Nullable KerberosClient kerberosClient; - private UserDetailsService userDetailsService; + private @Nullable UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; - JaasSubjectHolder subjectHolder = this.kerberosClient.login(auth.getName(), auth.getCredentials().toString()); - UserDetails userDetails = this.userDetailsService.loadUserByUsername(subjectHolder.getUsername()); + if (this.kerberosClient == null) { + throw new IllegalStateException("kerberosClient must be set"); + } + if (this.userDetailsService == null) { + throw new IllegalStateException("userDetailsService must be set"); + } + Object credentials = auth.getCredentials(); + if (credentials == null) { + throw new IllegalArgumentException("credentials cannot be null"); + } + JaasSubjectHolder subjectHolder = this.kerberosClient.login(auth.getName(), credentials.toString()); + String username = subjectHolder.getUsername(); + if (username == null) { + throw new IllegalStateException("username cannot be null"); + } + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); KerberosUsernamePasswordAuthenticationToken output = new KerberosUsernamePasswordAuthenticationToken( - userDetails, auth.getCredentials(), userDetails.getAuthorities(), subjectHolder); + userDetails, credentials, userDetails.getAuthorities(), subjectHolder); output.setDetails(authentication.getDetails()); return output; diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java index 8f3ba89d47..b121a7f422 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosMultiTier.java @@ -26,6 +26,7 @@ import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; import org.ietf.jgss.Oid; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; @@ -59,9 +60,9 @@ public final class KerberosMultiTier { final JaasSubjectHolder jaasSubjectHolder = kerberosAuthentication.getJaasSubjectHolder(); Subject subject = jaasSubjectHolder.getJaasSubject(); - Subject.doAs(subject, new PrivilegedAction() { + Subject.doAs(subject, new PrivilegedAction<@Nullable Object>() { @Override - public Object run() { + public @Nullable Object run() { runAuthentication(jaasSubjectHolder, username, lifetimeInSeconds, targetService); return null; @@ -71,7 +72,7 @@ public final class KerberosMultiTier { return authentication; } - public static byte[] getTokenForService(Authentication authentication, String principalName) { + public static byte @Nullable [] getTokenForService(Authentication authentication, String principalName) { KerberosAuthentication kerberosAuthentication = (KerberosAuthentication) authentication; final JaasSubjectHolder jaasSubjectHolder = kerberosAuthentication.getJaasSubjectHolder(); diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceAuthenticationProvider.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceAuthenticationProvider.java index 5bdb2b702a..3b03fd8c91 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceAuthenticationProvider.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.kerberos.authentication; 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.security.authentication.AccountStatusUserDetailsChecker; @@ -55,9 +56,9 @@ public class KerberosServiceAuthenticationProvider implements AuthenticationProv private static final Log LOG = LogFactory.getLog(KerberosServiceAuthenticationProvider.class); - private KerberosTicketValidator ticketValidator; + private @Nullable KerberosTicketValidator ticketValidator; - private UserDetailsService userDetailsService; + private @Nullable UserDetailsService userDetailsService; private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); @@ -66,6 +67,12 @@ public class KerberosServiceAuthenticationProvider implements AuthenticationProv KerberosServiceRequestToken auth = (KerberosServiceRequestToken) authentication; byte[] token = auth.getToken(); LOG.debug("Try to validate Kerberos Token"); + if (this.ticketValidator == null) { + throw new IllegalStateException("ticketValidator must be set"); + } + if (this.userDetailsService == null) { + throw new IllegalStateException("userDetailsService must be set"); + } KerberosTicketValidation ticketValidation = this.ticketValidator.validateTicket(token); LOG.debug("Successfully validated " + ticketValidation.username()); UserDetails userDetails = this.userDetailsService.loadUserByUsername(ticketValidation.username()); diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java index b890e23736..027408fd4f 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosServiceRequestToken.java @@ -26,6 +26,7 @@ import javax.security.auth.Subject; import org.ietf.jgss.GSSContext; import org.ietf.jgss.MessageProp; +import org.jspecify.annotations.Nullable; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; @@ -56,11 +57,11 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp private final byte[] token; - private final Object principal; + private final @Nullable Object principal; - private final transient KerberosTicketValidation ticketValidation; + private final transient @Nullable KerberosTicketValidation ticketValidation; - private JaasSubjectHolder jaasSubjectHolder; + private @Nullable JaasSubjectHolder jaasSubjectHolder; /** * Creates an authenticated token, normally used as an output of an authentication @@ -127,12 +128,12 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp } @Override - public Object getCredentials() { + public @Nullable Object getCredentials() { return null; } @Override - public Object getPrincipal() { + public @Nullable Object getPrincipal() { return this.principal; } @@ -148,7 +149,7 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp * Gets the ticket validation * @return the ticket validation (which will be null if the token is unauthenticated) */ - public KerberosTicketValidation getTicketValidation() { + public @Nullable KerberosTicketValidation getTicketValidation() { return this.ticketValidation; } @@ -168,6 +169,9 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp if (!hasResponseToken()) { throw new IllegalStateException("Unauthenticated or no response token"); } + if (this.ticketValidation == null) { + throw new IllegalStateException("Ticket validation is not available"); + } return Base64.getEncoder().encodeToString(this.ticketValidation.responseToken()); } @@ -180,9 +184,16 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp * @throws PrivilegedActionException if jaas throws and error */ public byte[] decrypt(final byte[] data, final int offset, final int length) throws PrivilegedActionException { - return Subject.doAs(getTicketValidation().subject(), new PrivilegedExceptionAction() { + KerberosTicketValidation validation = getTicketValidation(); + if (validation == null) { + throw new IllegalStateException("Cannot decrypt without ticket validation"); + } + return Subject.doAs(validation.subject(), new PrivilegedExceptionAction() { public byte[] run() throws Exception { - final GSSContext context = getTicketValidation().getGssContext(); + final GSSContext context = validation.getGssContext(); + if (context == null) { + throw new IllegalStateException("GSSContext is not available"); + } return context.unwrap(data, offset, length, new MessageProp(true)); } }); @@ -207,9 +218,16 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp * @throws PrivilegedActionException if jaas throws and error */ public byte[] encrypt(final byte[] data, final int offset, final int length) throws PrivilegedActionException { - return Subject.doAs(getTicketValidation().subject(), new PrivilegedExceptionAction() { + KerberosTicketValidation validation = getTicketValidation(); + if (validation == null) { + throw new IllegalStateException("Cannot encrypt without ticket validation"); + } + return Subject.doAs(validation.subject(), new PrivilegedExceptionAction() { public byte[] run() throws Exception { - final GSSContext context = getTicketValidation().getGssContext(); + final GSSContext context = validation.getGssContext(); + if (context == null) { + throw new IllegalStateException("GSSContext is not available"); + } return context.wrap(data, offset, length, new MessageProp(true)); } }); @@ -227,6 +245,9 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken imp @Override public JaasSubjectHolder getJaasSubjectHolder() { + if (this.jaasSubjectHolder == null) { + throw new IllegalStateException("JaasSubjectHolder is not available for unauthenticated token"); + } return this.jaasSubjectHolder; } diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java index 63b650e65a..483e40ca3b 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/KerberosTicketValidation.java @@ -23,6 +23,7 @@ import javax.security.auth.kerberos.KerberosPrincipal; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; +import org.jspecify.annotations.Nullable; /** * Result of ticket validation @@ -35,17 +36,17 @@ public final class KerberosTicketValidation { private final byte[] responseToken; - private final GSSContext gssContext; + private final @Nullable GSSContext gssContext; - private final GSSCredential delegationCredential; + private final @Nullable GSSCredential delegationCredential; public KerberosTicketValidation(String username, String servicePrincipal, byte[] responseToken, - GSSContext gssContext) { + @Nullable GSSContext gssContext) { this(username, servicePrincipal, responseToken, gssContext, null); } public KerberosTicketValidation(String username, String servicePrincipal, byte[] responseToken, - GSSContext gssContext, GSSCredential delegationCredential) { + @Nullable GSSContext gssContext, @Nullable GSSCredential delegationCredential) { final HashSet princs = new HashSet(); princs.add(new KerberosPrincipal(servicePrincipal)); @@ -56,12 +57,13 @@ public final class KerberosTicketValidation { this.delegationCredential = delegationCredential; } - public KerberosTicketValidation(String username, Subject subject, byte[] responseToken, GSSContext gssContext) { + public KerberosTicketValidation(String username, Subject subject, byte[] responseToken, + @Nullable GSSContext gssContext) { this(username, subject, responseToken, gssContext, null); } - public KerberosTicketValidation(String username, Subject subject, byte[] responseToken, GSSContext gssContext, - GSSCredential delegationCredential) { + public KerberosTicketValidation(String username, Subject subject, byte[] responseToken, + @Nullable GSSContext gssContext, @Nullable GSSCredential delegationCredential) { this.username = username; this.subject = subject; this.responseToken = responseToken; @@ -77,7 +79,7 @@ public final class KerberosTicketValidation { return this.responseToken; } - public GSSContext getGssContext() { + public @Nullable GSSContext getGssContext() { return this.gssContext; } @@ -85,7 +87,7 @@ public final class KerberosTicketValidation { return this.subject; } - public GSSCredential getDelegationCredential() { + public @Nullable GSSCredential getDelegationCredential() { return this.delegationCredential; } diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/package-info.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/package-info.java new file mode 100644 index 0000000000..b20404dbaa --- /dev/null +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/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.authentication; + +import org.jspecify.annotations.NullMarked; diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/GlobalSunJaasKerberosConfig.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/GlobalSunJaasKerberosConfig.java index 4a3f0543c2..17f5153359 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/GlobalSunJaasKerberosConfig.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/GlobalSunJaasKerberosConfig.java @@ -16,6 +16,8 @@ package org.springframework.security.kerberos.authentication.sun; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -30,7 +32,7 @@ public class GlobalSunJaasKerberosConfig implements BeanPostProcessor, Initializ private boolean debug = false; - private String krbConfLocation; + private @Nullable String krbConfLocation; @Override public void afterPropertiesSet() throws Exception { diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java index d5e6916398..cc686d2db1 100644 --- a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java @@ -36,6 +36,7 @@ import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSManager; import org.ietf.jgss.GSSName; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.ClassPathResource; @@ -58,13 +59,13 @@ import org.springframework.util.Assert; */ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, InitializingBean { - private String servicePrincipal; + private @Nullable String servicePrincipal; - private String realmName; + private @Nullable String realmName; - private Resource keyTabLocation; + private @Nullable Resource keyTabLocation; - private Subject serviceSubject; + private @Nullable Subject serviceSubject; private boolean holdOnToGSSContext; @@ -79,6 +80,9 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, @Override public KerberosTicketValidation validateTicket(byte[] token) { try { + if (this.serviceSubject == null) { + throw new IllegalStateException("serviceSubject must be initialized"); + } if (!this.multiTier) { return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token)); } @@ -89,7 +93,7 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, return Subject.doAs(subjectHolder.getJaasSubject(), new KerberosMultitierValidateAction(token)); } - catch (PrivilegedActionException ex) { + catch (IllegalStateException | PrivilegedActionException ex) { throw new BadCredentialsException("Kerberos validation not successful", ex); } } @@ -98,6 +102,9 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, public void afterPropertiesSet() throws Exception { Assert.notNull(this.servicePrincipal, "servicePrincipal must be specified"); Assert.notNull(this.keyTabLocation, "keyTab must be specified"); + if (this.servicePrincipal == null || this.keyTabLocation == null) { + throw new IllegalStateException("servicePrincipal and keyTabLocation must be set"); + } if (this.keyTabLocation instanceof ClassPathResource) { this.LOG.warn( "Your keytab is in the classpath. This file needs special protection and shouldn't be in the classpath. JAAS may also not be able to load this file from classpath."); @@ -263,8 +270,15 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, if (!SunJaasKerberosTicketValidator.this.holdOnToGSSContext) { context.dispose(); } - return new KerberosTicketValidation(gssName.toString(), - SunJaasKerberosTicketValidator.this.servicePrincipal, responseToken, context, delegationCredential); + if (gssName == null) { + throw new BadCredentialsException("GSSContext name of the context initiator is null"); + } + String servicePrincipal = SunJaasKerberosTicketValidator.this.servicePrincipal; + if (servicePrincipal == null) { + throw new IllegalStateException("servicePrincipal must be set"); + } + return new KerberosTicketValidation(gssName.toString(), servicePrincipal, responseToken, context, + delegationCredential); } } @@ -280,7 +294,7 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, private String servicePrincipalName; - private String realmName; + private @Nullable String realmName; private boolean multiTier; @@ -288,8 +302,8 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, private boolean refreshKrb5Config; - private LoginConfig(String keyTabLocation, String servicePrincipalName, String realmName, boolean multiTier, - boolean debug, boolean refreshKrb5Config) { + private LoginConfig(String keyTabLocation, String servicePrincipalName, @Nullable String realmName, + boolean multiTier, boolean debug, boolean refreshKrb5Config) { this.keyTabLocation = keyTabLocation; this.servicePrincipalName = servicePrincipalName; this.realmName = realmName; diff --git a/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/package-info.java b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/package-info.java new file mode 100644 index 0000000000..0046fb7019 --- /dev/null +++ b/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/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.authentication.sun; + +import org.jspecify.annotations.NullMarked;