spring-security

Why is AuthenticationProvider for UsernamePasswordAuthenticationToken not found?


I have a security setup with a custom provider and filter for parsing and authenticating by jwt token. When I hit the /api/auth/login endpoint, I get the following:

DEBUG org.springframework.web.servlet.DispatcherServlet : POST "/api/auth/login", parameters={}
DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping : Mapped to ro.seedC.gestiunebackend.security.AuthController#authenticateUser(LoginRequest)
DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [LoginRequest(username=Admin, password=test)]
INFO  ro.seedC.gestiunebackend.security.AuthController : authenticateUser Admin
DEBUG org.springframework.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
ERROR ro.seedC.gestiunebackend.security.jwt.JwtAuthenticationEntryPoint : Responding with unauthorized error. Message - No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken

My configuration:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    http.csrf(AbstractHttpConfigurer::disable);
    http.cors(AbstractHttpConfigurer::disable);
    http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    http.exceptionHandling(exception -> exception
            .authenticationEntryPoint(unauthorizedHandler));
    http.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    http.authorizeHttpRequests(authorize -> authorize
            .requestMatchers(HttpMethod.OPTIONS).permitAll()
            .requestMatchers("/api/auth/**").permitAll()
            .requestMatchers("/error").permitAll()
            .requestMatchers("/api/public/**").permitAll()
            .anyRequest().authenticated());

    return http.build();
} 

Here is my provider:

@RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider {

    private final JwtTokenProvider jwtTokenProvider;
    private final ByIdUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String jwt = ((JwtAuthenticationToken) authentication).getPrincipal();
        if (!StringUtils.hasText(jwt) || !jwtTokenProvider.validateToken(jwt)) {
            throw new AuthenticationCredentialsNotFoundException("invalid jwt token");
        }

        Long userId = jwtTokenProvider.getUserIdFromJWT(jwt);
        CustomUserDetails userDetails = userDetailsService.loadUserById(userId);

        return new CustomUserDetailsAuthentication(userDetails);
    }

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

Here is my filter:

public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
            "POST");

    public JwtAuthenticationFilter() {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }

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

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        return this.getAuthenticationManager().authenticate(
                new JwtAuthenticationToken(getJwtFromRequest(request))
        );
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

I also have a custom UserDetailService declared as a Service and I create an AuthenticationManager bean like this:

@Bean
public AuthenticationManager authenticationManagerBean() {
    return new ProviderManager(new JwtAuthenticationProvider(jwtTokenProvider, byIdUserDetailsService));
}

Solution

  • Try registering your authentication provider in your http security filter chain configuration. In your logs, the last 2 lines indicate that Spring security can't find the authentication provider for your configuration as indicated here

    ERROR ro.seedC.gestiunebackend.security.jwt.JwtAuthenticationEntryPoint : Responding with unauthorized error. Message - No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken

    So, you can start with registering an instance of your authentication provider like this:

    http.authenticationProvider(new JwtAuthenticationProvider())
                .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)...
    

    It is important to know that Spring Security always uses the AuthenticationProvider as the standard way of handling authentication, regardless of whether you are using built-in authentication methods or custom ones. So if you fail to register one, Spring Security uses the default AuthenticationProvider which is the DaoAuthenticationProvider which expects the username/password-based authentication.

    I hope that clarifies things for you.