javaspringspring-bootspring-oauth2spring-authorization-server

Spring Authorization Server threw exception with message: DelegatingAuthenticationConverter


I am confused at what is causing this error, I first started noticing this when I migrated from version 1.2 to 1.4 of the spring oauth2 authorization server. Below you will find relevant files to help debug the issue.

Error output when trying to run ./gradlw bootRun. It fails to build

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authorizationServerSecurityFilterChain' defined in class path resource [com/something/auth_service/configuration/OAuth2SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'authorizationServerSecurityFilterChain' threw exception with message: org/springframework/security/web/authentication/DelegatingAuthenticationConverter   
    

When I tried using the spring-boot-starter-oauth2-authorization-server it used an older dependency spring-security-oauth2-authorization-server:1.2.1 instead of 1.4.1. Not sure why that was happening but just decided to explicitly specify the dependencies.

Build.Gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'application'

}

group = 'com.something'
version = '0.0.1-SNAPSHOT'

application {
    mainClass = 'com.something.auth_service.AuthApplication'
}

java {
    sourceCompatibility = '17'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    all {
        exclude group: 'commons-logging', module: 'commons-logging'
    }
}

repositories {
    mavenCentral()
}


bootRun {
    args = ["--spring.profiles.active=dev"]
}

bootTestRun {
    args = ["--spring.profiles.active=dev"]
}

dependencies {
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.0'

    implementation 'software.amazon.awssdk:ses:2.28.20'
    implementation 'software.amazon.awssdk:sdk-core:2.28.20'
    implementation 'software.amazon.awssdk:auth:2.28.20'
    implementation 'software.amazon.awssdk:s3:2.28.20'
    implementation 'software.amazon.awssdk:regions:2.28.20'
    implementation 'software.amazon.awssdk:utils:2.28.20'
    implementation 'software.amazon.awssdk:services:2.28.20'
    implementation 'software.amazon.awssdk:aws-core:2.28.20'
    implementation 'jakarta.mail:jakarta.mail-api:2.1.3'
    implementation 'software.amazon.awssdk:sesv2:2.28.20'
    implementation 'software.amazon.awssdk:auth:2.28.20'

    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    implementation 'org.springframework.boot:spring-boot-starter-security:3.4.1'
    implementation 'org.springframework.boot:spring-boot-starter-web:3.4.1'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'redis.clients:jedis:5.1.2'

    implementation 'com.sun.mail:javax.mail:1.6.0'
    implementation 'javax.mail:javax.mail-api:1.6.2'
    implementation 'org.hibernate.validator:hibernate-validator'
    implementation 'io.jsonwebtoken:jjwt-api:0.12.3'

    implementation 'org.springframework.security:spring-security-oauth2-authorization-server:1.4.1'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
    implementation 'javax.servlet:servlet-api:2.5'
    implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    
}

tasks.named('test') {
    useJUnitPlatform()
}


This is my Spring Authorization Server configuration, which includes login settings. I successfully got the login routes working and tested the client registration. However, everything started breaking after upgrading to the latest version of Spring Authorization Server.

OAuth2 Security Config


package com.something.auth_service.configuration;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import java.security.interfaces.RSAPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.KeyPairGenerator;

import com.something.auth_service.configuration.user.CustomUserDetailsService;
import com.something.auth_service.configuration.user.UsernamePasswordAuthenticationProvider;
import com.something.auth_service.repository.UserRepository;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import com.nimbusds.jose.proc.SecurityContext;

import jakarta.servlet.http.HttpServletResponse;

import java.security.KeyPair;

@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class OAuth2SecurityConfig {

    @Bean
    CustomUserDetailsService userDetailsService(UserRepository userRepository) {
        return new CustomUserDetailsService(userRepository);
    }

    @Bean
    public PasswordEncoder customPasswordEncoder() {
        String idForEncode = "bcrypt";
        Map<String,PasswordEncoder> encoders = new HashMap<>();
        encoders.put(idForEncode, new BCryptPasswordEncoder());
        return new DelegatingPasswordEncoder("bcrypt", encoders);
    }

    @Bean
    public AuthenticationManager authenticationManager(
        CustomUserDetailsService userDetailsService, PasswordEncoder sharedPasswordEncoder) {
        UsernamePasswordAuthenticationProvider authenticationProvider = new UsernamePasswordAuthenticationProvider(userDetailsService, sharedPasswordEncoder);
        return new ProviderManager(authenticationProvider);
    }
    
    @Bean
    SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,RegisteredClientRepository registeredClientRepository,
            AuthorizationServerSettings authorizationServerSettings) throws Exception {
        OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
        OAuth2AuthorizationServerConfigurer.authorizationServer();

        // Failed below here.
        http
        .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
        .with(authorizationServerConfigurer, (authorizationServer) ->
            authorizationServer
                .oidc((oidc) ->
                    oidc.clientRegistrationEndpoint((clientRegistrationEndpoint) -> oidc.clientRegistrationEndpoint(Customizer.withDefaults())  
                    )
                )
        )
        .cors(cors -> cors.disable())
        .csrf(csrf -> csrf.disable())
        
        .authorizeHttpRequests((authorize) ->
            authorize
            .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .requestMatchers("/create-account").permitAll()
            .requestMatchers("/error").permitAll()
            .requestMatchers("/oauth2/authorize").permitAll()
            .requestMatchers("/login").permitAll()
                .anyRequest().authenticated()
        )
        .formLogin(formLogin -> {
                    formLogin.loginPage("/login");
                    formLogin.usernameParameter("emailOrUsername"); 
                    formLogin.passwordParameter("password");
                    formLogin.successHandler((request, response, authentication) -> {
                        System.out.println("Login Succeeded.");
                        response.setStatus(HttpServletResponse.SC_OK);
                        response.getWriter().write("Login successful!");
                        response.getWriter().flush();
                    });
                    
                })
                .logout(logout -> {
                    logout.logoutUrl("http://localhost:8082/logout");
                    logout.logoutSuccessUrl("http://localhost:8082/login");
                    logout.invalidateHttpSession(true);
                    logout.clearAuthentication(true);
                    logout.deleteCookies("JSESSIONID");
                    logout.logoutSuccessHandler((request, response, authentication) -> {
                        System.out.println("Logout Succeeded.");
                        response.setStatus(HttpServletResponse.SC_OK);
                        response.getWriter().write("Logout successful for "+request.getSession().getId());
                        response.getWriter().flush();
                    });
                })
        .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt(Customizer.withDefaults()))
        .exceptionHandling((exceptions) -> {        
            exceptions.accessDeniedHandler((request, response, accessDeniedException) -> {
                request.getSession().invalidate();
                System.out.println("Login Failed, Access Denied.");
                response.setStatus(HttpServletResponse.SC_OK); 
                response.getWriter().write("Login Failed, Access Denied.");
                response.getWriter().flush();
            });
            exceptions.defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint("http://localhost:8082"),
                new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                );
            }
            );
        
        return http.build();
    }

    
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    @Bean
    JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

    
}


Solution

  • It seems like you're mixing Spring Boot 3.2.0 and 3.4.1. After bumping Spring Boot plugin to 3.4.1, you may let deps follow versions in plugin by removing :3.4.1.

    You might consider using org.springframework.boot:spring-boot-starter-oauth2-authorization-server instead of spring-security-oauth2-authorization-server.