authenticationspring-security

problems with custom authentication provider in spring boot 3.3.0


I've been struggling with this for too many days and could really use some help. I am trying to use spring-boot 3.3.0 to create an unusual authentication provider. I need it to work like this:

public class RefererAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

  private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("**","GET");

  public RefererAuthenticationFilter(AuthenticationManager authenticationManager) {
    super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
    String url        = request.getHeader( "Referer" );
    String fetchDest  = request.getHeader( "Sec-Fetch-Dest" );
    String id         = request.getParameter( "id" );
    String name       = request.getParameter( "name" );
    String email      = request.getParameter( "email" );
    String ipAddress = request.getHeader("X-FORWARDED-FOR");
    if (ipAddress == null) {
      ipAddress = request.getRemoteAddr();
    }

    Referer referer = new Referer();
    referer.setUrl(url);
    referer.setFetchDest(fetchDest);
    referer.setId(id);
    referer.setName(name);
    referer.setEmail(email);
    referer.setIpAddress(ipAddress);

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( referer, null );
    return this.getAuthenticationManager().authenticate(authRequest);
  }
}
@Component
public class RefererAuthenticationProvider implements AuthenticationProvider {

  @Value("${referer}")
  private String validReferer;

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    UsernamePasswordAuthenticationToken challengeAuth = (UsernamePasswordAuthenticationToken) authentication;
    log.debug( "starting authentication ..." );
    Referer referer = (Referer) challengeAuth.getPrincipal();
    if ( referer.getUrl().startsWith(validReferer) &&
         referer.getFetchDest().equals("iframe") ) {
      SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
      List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
      grantedAuthorities.add(authority);
      return new UsernamePasswordAuthenticationToken( referer, null, grantedAuthorities);
    }
    throw new BadCredentialsException("referer did not match within the iframe");
  }

  @Override
  public boolean supports(Class<?> authentication) {
    return authentication.equals( UsernamePasswordAuthenticationToken.class );
  }

}
@Configuration
@AllArgsConstructor
@EnableWebSecurity(debug = true)
public class SecurityConfig {

  @Autowired
  RefererAuthenticationProvider refererAuthenticationProvider;

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
    authenticationManagerBuilder.authenticationProvider(refererAuthenticationProvider);
    AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
    RefererAuthenticationFilter refererAuthenticationFilter = new RefererAuthenticationFilter(authenticationManager);
    refererAuthenticationFilter.setAuthenticationManager(authenticationManager);

    return http
      .csrf(AbstractHttpConfigurer::disable)
      .addFilterAt(refererAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
      .authorizeHttpRequests( request -> { request
        .anyRequest().authenticated();
      })
      .headers( headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable) )
      .build();
  }

}

This ends up just telling me ...

java.lang.NullPointerException: Cannot invoke "org.springframework.security.authentication.AuthenticationManager.authenticate(org.springframework.security.core.Authentication)"
because the return value of "com.example.security.filter.RefererAuthenticationFilter.getAuthenticationManager()" is null

I thought I could use the default AuthenticationManager.

To make the situation even more challenging I want multiple AuthenticationProviders. I want this one to be tried first, followed by okta-spring-boot-starter 3.0.6. I'm not sure if these can co-exist in the same AuthenticationManager, who provides it, and where exactly I would try to lay down my RefererAuthenticationFilter in the filter chain with Okta, which looks like

org.springframework.security.web.session.DisableEncodeUrlFilter@4bf806ab, 
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@2e7b49c0, 
org.springframework.security.web.context.SecurityContextHolderFilter@1e26110d, 
org.springframework.security.web.header.HeaderWriterFilter@1e1b3778, 
org.springframework.web.filter.CorsFilter@5dd7342f, 
org.springframework.security.web.authentication.logout.LogoutFilter@1865d91, 
org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@5d116afe, 
org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@1780aa5d, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@264c6be6, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@3f60f8c4, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4650f833, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3acc81dd, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@34a66cdd, 
org.springframework.security.web.access.ExceptionTranslationFilter@206f53b0, 
org.springframework.security.web.access.intercept.AuthorizationFilter@fbb75c2

Solution

  • The NullPointerException you are encountering is due to the AuthenticationManager not being properly initialized in your RefererAuthenticationFilter. To resolve this issue, you need to explicitly set the AuthenticationManager in your custom filter. Additionally, to support multiple AuthenticationProviders, you can configure Spring Security to use a list of providers.

    Here’s a updated version of your configuration:

    public class RefererAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
        private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("**","GET");
    
        public RefererAuthenticationFilter(AuthenticationManager authenticationManager) {
            super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
            setAuthenticationManager(authenticationManager);
        }
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
            String url        = request.getHeader("Referer");
            String fetchDest  = request.getHeader("Sec-Fetch-Dest");
            String id         = request.getParameter("id");
            String name       = request.getParameter("name");
            String email      = request.getParameter("email");
            String ipAddress  = request.getHeader("X-FORWARDED-FOR");
            if (ipAddress == null) {
                ipAddress = request.getRemoteAddr();
            }
    
            Referer referer = new Referer();
            referer.setUrl(url);
            referer.setFetchDest(fetchDest);
            referer.setId(id);
            referer.setName(name);
            referer.setEmail(email);
            referer.setIpAddress(ipAddress);
    
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(referer, null);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
    
    

    Configure multiple AuthenticationProviders and integrate them with the AuthenticationManager in your SecurityConfig:

    @Configuration
    @EnableWebSecurity(debug = true)
    public class SecurityConfig {
    
        @Autowired
        private RefererAuthenticationProvider refererAuthenticationProvider;
    
        @Autowired
        private OktaOAuth2AuthenticationProvider oktaAuthenticationProvider; // Assuming you have a custom Okta provider
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
            authenticationManagerBuilder.authenticationProvider(refererAuthenticationProvider);
            authenticationManagerBuilder.authenticationProvider(oktaAuthenticationProvider);
            AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
    
            RefererAuthenticationFilter refererAuthenticationFilter = new RefererAuthenticationFilter(authenticationManager);
    
            return http
                    .csrf(AbstractHttpConfigurer::disable)
                    .addFilterAt(refererAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                    .authorizeHttpRequests(request -> request.anyRequest().authenticated())
                    .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                    .build();
        }
    
        @Bean
        public RefererAuthenticationProvider refererAuthenticationProvider() {
            return new RefererAuthenticationProvider();
        }
    
        @Bean
        public OktaOAuth2AuthenticationProvider oktaAuthenticationProvider() {
            return new OktaOAuth2AuthenticationProvider(); // Customize as needed
        }
    }
    
    

    making sureRefererAuthenticationProvider and Okta provider implementations are properly defined:

    @Component
    public class RefererAuthenticationProvider implements AuthenticationProvider {
    
        @Value("${referer}")
        private String validReferer;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            UsernamePasswordAuthenticationToken challengeAuth = (UsernamePasswordAuthenticationToken) authentication;
            log.debug("starting authentication ...");
            Referer referer = (Referer) challengeAuth.getPrincipal();
            if (referer.getUrl().startsWith(validReferer) && referer.getFetchDest().equals("iframe")) {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
                List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();
                grantedAuthorities.add(authority);
                return new UsernamePasswordAuthenticationToken(referer, null, grantedAuthorities);
            }
            throw new BadCredentialsException("referer did not match within the iframe");
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return authentication.equals(UsernamePasswordAuthenticationToken.class);
        }
    }