mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-02-08 22:44:35 +00:00
Null safety via JSpecify spring-security-kerberos-core
Closes gh-18549
This commit is contained in:
parent
b970746a03
commit
f942ead2eb
@ -1,4 +1,5 @@
|
||||
plugins {
|
||||
id 'security-nullability'
|
||||
id 'io.spring.convention.spring-module'
|
||||
id 'javadoc-warnings-error'
|
||||
}
|
||||
|
||||
@ -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<String, byte[]> savedTokens = new HashMap<String, byte[]>();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<Object>() {
|
||||
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();
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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<byte[]>() {
|
||||
KerberosTicketValidation validation = getTicketValidation();
|
||||
if (validation == null) {
|
||||
throw new IllegalStateException("Cannot decrypt without ticket validation");
|
||||
}
|
||||
return Subject.doAs(validation.subject(), new PrivilegedExceptionAction<byte[]>() {
|
||||
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<byte[]>() {
|
||||
KerberosTicketValidation validation = getTicketValidation();
|
||||
if (validation == null) {
|
||||
throw new IllegalStateException("Cannot encrypt without ticket validation");
|
||||
}
|
||||
return Subject.doAs(validation.subject(), new PrivilegedExceptionAction<byte[]>() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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<KerberosPrincipal> princs = new HashSet<KerberosPrincipal>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
Loading…
x
Reference in New Issue
Block a user