Fix Shibboleth/Tomcat providing a principal with a blank name in the unauthenticated case

This commit is contained in:
Andreas Svanberg 2025-04-01 20:11:08 +02:00
parent 71862afb55
commit c421125eb4
Signed by: ansv7779
GPG Key ID: 729B051CFFD42F92
3 changed files with 31 additions and 24 deletions

@ -9,7 +9,6 @@ import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
@ -24,10 +23,8 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import se.su.dsv.oauth2.shibboleth.Entitlement;
import se.su.dsv.oauth2.shibboleth.ShibbolethAuthenticationDetailsSource;
import se.su.dsv.oauth2.shibboleth.ShibbolethConfigurer;
import se.su.dsv.oauth2.shibboleth.ShibbolethTokenPopulator;
import se.su.dsv.oauth2.staging.StagingSecurityConfigurer;
@ -140,30 +137,11 @@ public class AuthorizationServer extends SpringBootServletInitializer {
.accessDeniedPage("/forbidden")
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")));
http.jee(jee -> jee
.withObjectPostProcessor(makeShibbolethAware()));
http.with(new ShibbolethConfigurer(), Customizer.withDefaults());
return http.build();
}
private static ObjectPostProcessor<J2eePreAuthenticatedProcessingFilter> makeShibbolethAware()
{
return new ObjectPostProcessor<>() {
@Override
public <O extends J2eePreAuthenticatedProcessingFilter> O postProcess(final O object) {
// Using a custom authentication details source to extract the Shibboleth attributes
// and convert them to the relevant Spring Security objects.
object.setAuthenticationDetailsSource(new ShibbolethAuthenticationDetailsSource());
// Prevent session creation
// It can cause conflicts when running on the same host as an embedded docker container
// as it overwrites the session cookie (it does not factor in port)
object.setSecurityContextRepository(new RequestAttributeSecurityContextRepository());
return object;
}
};
}
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return new ShibbolethTokenPopulator();

@ -0,0 +1,29 @@
package se.su.dsv.oauth2.shibboleth;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.log.LogMessage;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
import java.security.Principal;
/// Can't use the built-in [J2eePreAuthenticatedProcessingFilter] because Tomcat
/// unconditionally provides a [HttpServletRequest#getUserPrincipal()] with a
/// blank [Principal#getName()] that causes the authentication process to fail.
class ShibbolethAuthenticatedProcessingFilter extends J2eePreAuthenticatedProcessingFilter {
@Override
protected Object getPreAuthenticatedPrincipal(final HttpServletRequest httpRequest) {
Principal userPrincipal = httpRequest.getUserPrincipal();
if (userPrincipal == null) {
return null;
}
// Tomcat provides a blank Principal name when the user is not authenticated.
String principalName = userPrincipal.getName();
if (principalName == null || principalName.isBlank()) {
return null;
}
this.logger.debug(LogMessage.format("PreAuthenticated J2EE principal: %s", principalName));
return principalName;
}
}

@ -20,7 +20,7 @@ public class ShibbolethConfigurer extends AbstractHttpConfigurer<ShibbolethConfi
@Override
public void configure(final HttpSecurity http) {
J2eePreAuthenticatedProcessingFilter filter = new J2eePreAuthenticatedProcessingFilter();
J2eePreAuthenticatedProcessingFilter filter = new ShibbolethAuthenticatedProcessingFilter();
filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
filter.setAuthenticationDetailsSource(new ShibbolethAuthenticationDetailsSource());
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());