spring error exception with oauth2 and securityconfig

84 views Asked by At

Good morning, I'm redoing a demo and I have a compilation error,I have 3 exceptions which all have a link : threw exception with message: authorizationUri cannot be empty.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig' defined in file [C:\Users\chres\Documents\jetBrainProjects\intellij-idea-projects\sdia2\target\classes\com\example\sdia2\secu\SecurityConfig.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'clientRegistrationRepository' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.class]: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method 'clientRegistrationRepository' threw exception with message: **authorizationUri cannot be empty**
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:236) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1350) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1187) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:558) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:518) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:949) ~[spring-context-6.0.16.jar:6.0.16]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:615) ~[spring-context-6.0.16.jar:6.0.16]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.8.jar:3.1.8]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738) ~[spring-boot-3.1.8.jar:3.1.8]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440) ~[spring-boot-3.1.8.jar:3.1.8]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:324) ~[spring-boot-3.1.8.jar:3.1.8]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317) ~[spring-boot-3.1.8.jar:3.1.8]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.8.jar:3.1.8]
    at com.example.sdia2.Sdia2Application.main(Sdia2Application.java:15) ~[classes/:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.8.jar:3.1.8]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'clientRegistrationRepository' defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.class]: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method 'clientRegistrationRepository' **threw exception with message: authorizationUri cannot be empty**
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:650) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1330) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:558) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:518) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:906) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:784) ~[spring-beans-6.0.16.jar:6.0.16]
    ... 22 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository]: Factory method **'clientRegistrationRepository' threw exception with message: authorizationUri cannot be empty**
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.0.16.jar:6.0.16]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:646) ~[spring-beans-6.0.16.jar:6.0.16]
    ... 36 common frames omitted
Caused by: java.lang.IllegalArgumentException: **authorizationUri cannot be empty**
    at org.springframework.util.Assert.hasText(Assert.java:294) ~[spring-core-6.0.16.jar:6.0.16]
    at org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.validateAuthorizationCodeGrantType(ClientRegistration.java:660) ~[spring-security-oauth2-client-6.1.6.jar:6.1.6]
    at org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.build(ClientRegistration.java:610) ~[spring-security-oauth2-client-6.1.6.jar:6.1.6]
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.getClientRegistration(OAuth2ClientPropertiesMapper.java:87) ~[spring-boot-autoconfigure-3.1.8.jar:3.1.8]
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.lambda$asClientRegistrations$0(OAuth2ClientPropertiesMapper.java:65) ~[spring-boot-autoconfigure-3.1.8.jar:3.1.8]
    at java.base/java.util.HashMap.forEach(HashMap.java:1429) ~[na:na]
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.asClientRegistrations(OAuth2ClientPropertiesMapper.java:64) ~[spring-boot-autoconfigure-3.1.8.jar:3.1.8]
    at org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration.clientRegistrationRepository(OAuth2ClientRegistrationRepositoryConfiguration.java:49) ~[spring-boot-autoconfigure-3.1.8.jar:3.1.8]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.16.jar:6.0.16]
    ... 37 common frames omitted

application.properties

spring.application.name=customer-app
server.port=8084
spring.security.oauth2.client.registration.google.client-id=290479700275-7t9jjpd5agua1u7n881fubvd2ceqg7aq.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret=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
spring.security.oauth2.client.provider.github.user-name-attribute=login

spring.security.oauth2.client.registration.github.client-id=Iv1.3369f41e3d67c0a3
spring.security.oauth2.client.registration.github.client-secret=f7cb85296f581390511d1bf4aa29bcc43af7b163
spring.security.oauth2.client.provider.google.user-name-attribute=email
spring.datasource.url=jdbc:h2:mem:customers-db
spring.h2.console.enabled=true

spring.security.oauth2.client.registration.keycloak.client-name=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=client-cree-customer
spring.security.oauth2.client.registration.keycloak.client-secret=C6JJFHCOkDSvaZwLAy673yHVnXkDu6k3
spring.security.oauth2.client.registration.keycloak.scope=openid, profile, email, offline_access
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.redirect-uri=http://localhost:8084/login/oauth2/code/sdia3
spring.security.oauth2.client.registration.keycloak.issuer-uri=http://localhost:8080/realms/sdia3/account
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
    @Controller   
    public class CustomerController {
        /**
         * acces a interface
         */
        private  CustomerRepository customerRepository;
        public CustomerController(CustomerRepository customerRepository) {
            this.customerRepository = customerRepository;
        }

        @GetMapping("/customers")
        @PreAuthorize("hasAuthority('ADMIN')")

        public String customers(Model model){  
            List<Customer> customersList = customerRepository.findAll();
            model.addAttribute("customers", customersList);
            return "customers";
        }

        @GetMapping("/products")
        public String products(Model model){
            return "products";
        }

        
        @GetMapping("/auth")
        @ResponseBody//vu que ce n'est pas un rest controller, il doit y avoir cette anotation pour retourner la reponse sous format json
        public Authentication auth(Authentication authentication){
            return authentication;
        }
        
        @GetMapping("/")
        public String index(){
            return "index";
        }
}

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>sdia2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sdia2</name>
    <description>sdia2</description>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.maven.shared/maven-dependency-tree -->
        <dependency>
            <groupId>org.apache.maven.shared</groupId>
            <artifactId>maven-dependency-tree</artifactId>
            <version>3.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-messaging -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-messaging</artifactId>
            <version>6.2.3</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.1.8</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.2.224</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>5.3.2</version>
        </dependency>
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>
        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-clean-plugin</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.11.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

SecurityConfig

@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)

public class SecurityConfig {
    //stock les diff providers conf dans l'appli pour la connection clients
    private final ClientRegistrationRepository clientRegistrationRepository;
    //inj de dependance via constructeur
    public SecurityConfig(ClientRegistrationRepository clientRegistrationRepository){
        this.clientRegistrationRepository=clientRegistrationRepository;
    }

    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(Customizer.withDefaults())//config par def.
                .authorizeHttpRequests(ar-> ar.anyRequest().authenticated())//toutes requetes necessite une authentification
                .authorizeHttpRequests(ar->ar.requestMatchers("/","/webjars/**","/h2-console/**").permitAll())//toute url sans besoin de s'authentifier:parefeu
                .oauth2Login(Customizer.withDefaults())
                .logout((logout)->logout
                        .logoutSuccessHandler(oidcLogoutSuccessHandler())
                        .logoutSuccessUrl("/").permitAll()
                        .clearAuthentication(true)
                        .deleteCookies("JSESSIONID"));
        return http.build();

    }

    private OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() {
        final OidcClientInitiatedLogoutSuccessHandler oidcClientInitiatedLogoutSuccessHandler=
                new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
        oidcClientInitiatedLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}?logoutsuccess=true");
        return oidcClientInitiatedLogoutSuccessHandler;
    }
}

Main

@SpringBootApplication
public class Sdia2Application {

    public static void main(String[] args) {
        SpringApplication.run(Sdia2Application.class, args);
    }


//s'execute au demarrage=onInit() avec en para l'int customerrepository pour utiliser ses methodes REST=inj dep
    @Bean
    CommandLineRunner commandLineRunner(CustomerRepository customerRepository) {
        return args -> {
            customerRepository.save(Customer.builder().name("casfr").email("[email protected]").build());
            customerRepository.save(Customer.builder().name("chrissdf").email("[email protected]").build());
            customerRepository.save(Customer.builder().name("lor").email("[email protected]").build());

        };
    }
}

CustomerRepository

public interface CustomerRepository  extends JpaRepository<Customer, Long> {

}

I clearly defined this in my property file:

spring.security.oauth2.client.registration.keycloak.redirect-uri=http://localhost:8084/login/oauth2/code/sdia3
spring.security.oauth2.client.registration.keycloak.issuer-uri=http://localhost:8080/realms/sdia3/account

is this not enough?

1

There are 1 answers

8
ch4mp On

You have not set the provider and registration properties correctly for Keycloak:

  • only user-name-attribute is present for provider conf, which is not enough
  • issuer-uri is a provider property, not a registration one (your IDE should lint that)
  • issuer-uri is probably http://localhost:8080/realms/sdia3 (and not http://localhost:8080/realms/sdia3/account)
  • when you're not using one of the "magic" ones (like googleis), you should explicitly reference the provider to use in the registration

As Keycloak is OIDC compliant, setting spring.security.oauth2.client.provider.keycloak.issuer-uri= with the exact value of the iss claim in one of your tokens should be enough (even trailing slash, if any, and case are important). Spring will pull the rest of the conf from the .well-known/openid-configuration.

Here is the configuration you're probably looking for (in the YAML format):

keycloak-issuer: http://localhost:8080/realms/sdia3
keycloak-authorization-code-secret: you-should-not-commit-this-in-git-or-post-it-on-stackoverflow

spring:
  security:
    oauth2:
      client:
        provider:
          keycloak-provider:
            issuer-uri: ${keycloak-issuer}
            user-name-attribute: preferred_username
        registration:
          keycloak-authorization-code:
            provider: keycloak-provider
            authorization-grant-type: authorization_code
            client-id: client-cree-customer
            client-secret: ${keycloak-authorization-code-secret}
            scope: openid, profile, email, offline_access
            client-name: keycloak

As you seem to be at the very beginning of your Spring with OAuth2 journey, you might find this tutorials I wrote useful.