Home > Software design >  Infinite Loop with authenticate spring
Infinite Loop with authenticate spring

Time:11-11

I'm at a loss. I upgraded an application from Spring Boot 2.1 to 2.6 and from Wicket 8.0 to 9.6. I had two issues with circular-references that I fixed but now I get an infinite loop if I want to start the application with H2 database because of authenticate. And I'm not sure what is happening there.

So this is the part of the StackTrace that keeps repeating. IntelliJ cuts off the beginning, not sure what to do about that:


   at com.xyz.ufa.app.TestUFSession.<init>(TestUFSession.java:15)
   at jdk.internal.reflect.GeneratedConstructorAccessor102.newInstance(Unknown Source)
   at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
   at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
   at org.apache.wicket.authroles.authentication.AuthenticatedWebApplication.newSession(AuthenticatedWebApplication.java:108)
   at org.apache.wicket.Application.fetchCreateAndSetSession(Application.java:1527)
   at org.apache.wicket.Session.get(Session.java:194)
   at org.apache.wicket.protocol.http.WebSession.get(WebSession.java:41)
   at com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.security.SecureWebSession.authenticate(SecureWebSession.java:48)
   at org.apache.wicket.authroles.authentication.AuthenticatedWebSession.signIn(AuthenticatedWebSession.java:66)
   at com.xyz.ufa.app.TestUFSession.<init>(TestUFSession.java:15)

Here is the TestUFSession.class

import com.xyz.ufa.frontend.config.UFSession;
import org.apache.wicket.request.Request;


/**
* Helper session to login autmatically.
*
*/
public class TestUFSession extends UFSession {

   public TestUFSession(Request request) {
       super(request);
       signIn("admin", "admin"); // this calls authenticate
   }
       
}

And here the UFSession class


import com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.security.SecureWebSession;
import java.util.Locale;
import lombok.Getter;
import org.apache.wicket.request.Request;
@Getter
public class UFSession extends SecureWebSession {

   private String username;
   private Locale locale;

   public UFSession(Request request) {
       super(request);
       locale = request.getLocale();

   }


   @Override
   public void signOut() {
       username = null;
       super.signOut();

}
} 

And here the WebSecurityConfiguration class

import com.xyz.uf.common.ApplicationProfile;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsService;

@Slf4j
@Configuration
@Import(LdapProperties.class)
@ComponentScan(basePackages = {"com.xyz.ufa.security.userinfo"})
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

   private final String AUTHENTICATION_PROVIDER_BEAN_NAME = "authenticationProvider";

   @Autowired
   private LdapProperties ldapProperties;

   @Bean(name = "authenticationManager")
   @Override
   public AuthenticationManager authenticationManagerBean() throws AuthenticationServiceException {
       try {
           return super.authenticationManagerBean();
       } catch (Exception e) {
           log.error("Error in authenticationManagerBean", e);
           throw new AuthenticationServiceException(e.getMessage(), e);
       }
   }

   @Override
   protected void configure(HttpSecurity httpSecurity) throws AuthenticationServiceException {
       try {
           httpSecurity
               .csrf().disable()
               .authorizeRequests()
               .antMatchers("/*").permitAll()
               .antMatchers("/restservice/**").hasAuthority(UFARole.TECHNICAL_ADMIN)
               .and().httpBasic()
               .and().logout().permitAll();
           httpSecurity.headers().frameOptions().disable();
       } catch (Exception e) {
           throw new AuthenticationServiceException(String.format("Could not configure %s with csrf disabled and matching Pattern /*.", httpSecurity), e);
       }
   }

   @Override
   public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
       try {
           AbstractLdapAuthenticationProvider authentProvider = (AbstractLdapAuthenticationProvider) getApplicationContext().getBean(AUTHENTICATION_PROVIDER_BEAN_NAME);
           authentProvider.setAuthoritiesMapper(authoritiesMapper());
           DaoAuthenticationConfigurer<AuthenticationManagerBuilder, LdapUserDetailsService> configurer = authenticationManagerBuilder
               .authenticationProvider(authentProvider)
               .userDetailsService(ldapUserDetailsService());
           passwordEncoder.ifPresent(configurer::passwordEncoder);
       } catch (Exception e) {
           throw new AuthenticationServiceException("Could not configure authentication manager ", e);
       }
   }

   @Bean
   public LdapUserDetailsService ldapUserDetailsService() {
       LdapUserDetailsService userDetailsService = new LdapUserDetailsService(userSearch(), ldapAuthoritiesPopulator());
       return userDetailsService;
   }

   @Bean
   public LdapUserSearch userSearch() {
       return new FilterBasedLdapUserSearch(ldapProperties.getUserSearchBase(), ldapProperties.getUserSearchFilter(), contextSource());
   }

   @Bean
   public GrantedAuthoritiesMapper authoritiesMapper() {
       return new GrantAuthoritiesMapperWithEnvTag(ldapProperties.getEnv());
   }

   @Bean
   public LdapContextSource contextSource() {
       LdapContextSource ldapContextSource = new LdapContextSource();
       ldapContextSource.setUrl(ldapProperties.getServerUrl());
       ldapContextSource.setAnonymousReadOnly(true);
       return ldapContextSource;
   }

   @Bean
   public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
       DefaultLdapAuthoritiesPopulator ldapAuthoritiesPopulator = new DefaultLdapAuthoritiesPopulator(contextSource(), ldapProperties.getGroupSearchBase());
       ldapAuthoritiesPopulator.setGroupSearchFilter(ldapProperties.getGroupSearchFilter());
       ldapAuthoritiesPopulator.setGroupRoleAttribute(ldapProperties.getGroupRoleAttribute());
       ldapAuthoritiesPopulator.setRolePrefix("");
       ldapAuthoritiesPopulator.setConvertToUpperCase(false);
       return ldapAuthoritiesPopulator;
   }

   @Bean(name = AUTHENTICATION_PROVIDER_BEAN_NAME)
   @Profile(value = { ApplicationProfile.Values.TEST, ApplicationProfile.Values.PROD })
   public AuthenticationProvider activeDirectory() {
       ActiveDirectoryLdapAuthenticationProvider authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider("HRE.LOC", ldapProperties.getServerUrl());
       authenticationProvider.setSearchFilter(ldapProperties.getUserSearchFilter());
       return authenticationProvider;
   }

   @Bean(name = AUTHENTICATION_PROVIDER_BEAN_NAME)
   @Profile(value = { ApplicationProfile.Values.DEFAULT, ApplicationProfile.Values.DEV })
   public AuthenticationProvider defaultAuthenticationProvider() {
       PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(contextSource());
       authenticator.setPasswordAttributeName("userPassword");
       passwordEncoder.ifPresent(authenticator::setPasswordEncoder);
       authenticator.setUserSearch(userSearch());
       LdapAuthenticationProvider authenticationProvider = new LdapAuthenticationProvider(authenticator, ldapAuthoritiesPopulator());
       return authenticationProvider;
   }

   /**
    * This bean is optional and not available for some profiles. Password encoder is only required for embedded LDAP, for productive Active Directory it is not used
    */
   @Bean("passwordEncoder")
   @Profile(value = { ApplicationProfile.Values.DEFAULT, ApplicationProfile.Values.DEV })
   public static PasswordEncoder passwordEncoder() {
       return NoOpPasswordEncoder.getInstance();
   }

   @Autowired(required = false)
   @Qualifier("passwordEncoder")
   private Optional<PasswordEncoder> passwordEncoder;

} 

Profile used for test is default.

Anyone any ideas?

Edit: So Martin helped me to understand the problem, but I'm too dumb to fix it. TestUFApplication is instantiated here via ReflectionTestUtils

´´´´
@SpringBootApplication
public class TestApplicationWithH2Database {

    @Autowired
    private UFyWicketWebApplication webApplication;

    @Value("${test.autologin.active}")
    private boolean testAutologinActive;

    public static void main(String[] args) {
        new SpringApplicationBuilder()
            .sources(Application.class)
            .run(args);
    }

    @PostConstruct
    public void PostConstruct() {
        if (testAutologinActive) {

            ReflectionTestUtils.setField(webApplication, "sessionClass", TestUFSession.class);
        
        }
    }

}

I tried to make signIn() a method in TestUFSession and call it like that

TestUFSession testUFSession = (TestUFSession) ReflectionTestUtils.getField(webApplication, "sessionClass");
            testUFSession.signIn();

but got a ClassCastException

CodePudding user response:

You will need to call signIn("admin", "admin"); after instantiating the TestUFSession.

From the stacktrace we can see that com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.security.SecureWebSession.authenticate(SecureWebSession.java:48) tries to lookup the WebSession via its static #get() method and that leads to the infinite loop.

  • Related