Search code examples
javaspring-bootspring-securityoauth-2.0

Spring Security - Multiple OAuth2 Identity providers (github and google) work with only one Bean with @Prirmary?


I've a Spring boot application version 2.7.10 which I wanted to be able to authenticate with two identity providers (github and google) via OAuth2.

Basically, my web application should allow user to select either github or google to authenticate, but I could not make it work for both authentication providers, only the one which has bean annotated @Primary in OAuth2ClientServiceConfig.java.

With that, I need two configurations for the two providers as below:

  • Oauth2ClientConfiguration.java


@Configuration
public class Oauth2ClientConfiguration {

    @Bean(name = "github")
    public ClientRegistrationRepository githubClientRegistrationRepository() {

        ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("github")
                .clientId("GITHUB_CLIENT_ID")
                .clientSecret("GITHUB_CLIENT_SECRET")
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
                .scope("user")
                .authorizationUri("https://github.com/login/oauth/authorize")
                .tokenUri("https://github.com/login/oauth/access_token")
                .userInfoUri("https://api.github.com/user")
                .userNameAttributeName("id")
                .clientName("gitHub")
                .build();

        return new InMemoryClientRegistrationRepository(clientRegistration);

    }
    
    
    @Bean(name = "google")
    public ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("google")
                .clientId("GOOGLE_CLIENT_ID")
                .clientSecret("GOOGLE_CLIENT_SECRET")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
                .scope("openid", "profile", "email", "address", "phone")
                .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
                .tokenUri("https://www.googleapis.com/oauth2/v4/token")
                .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
                .userNameAttributeName(IdTokenClaimNames.SUB)
                .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
                .clientName("Google")
                .build();

        return new InMemoryClientRegistrationRepository(clientRegistration);

    }
    
}

  • OAuth2ClientServiceConfig.java
@Configuration
public class OAuth2ClientServiceConfig {

    @Bean(name = "githubClientService")
    public OAuth2AuthorizedClientService githubAuthorizedClientService(
            @Qualifier("github") ClientRegistrationRepository githubClientRegistrationRepository) {
        return new InMemoryOAuth2AuthorizedClientService(githubClientRegistrationRepository);
    }

    @Bean(name = "googleClientService")
    @Primary
    public OAuth2AuthorizedClientService googleAuthorizedClientService(
            @Qualifier("google")  ClientRegistrationRepository googleClientRegistrationRepository) {
        return new InMemoryOAuth2AuthorizedClientService(googleClientRegistrationRepository);
    }


}
  • WebSecurityConfig.java (note the NOTE comment at the bottom of the class)

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    @Qualifier("githubClientService")
    private OAuth2AuthorizedClientService githubOAuth2AuthorizedClientService;

    @Autowired
    @Qualifier("googleClientService")
    private OAuth2AuthorizedClientService googleOAuth2AuthorizedClientService;

    @Autowired
    @Qualifier("github")
    private ClientRegistrationRepository githubClientRegistrationRepository;

    @Autowired
    @Qualifier("google")
    private ClientRegistrationRepository googleClientRegistrationRepository;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
            http
                .addFilterBefore(new CustomSecurityExceptionHandlingFilter(), UsernamePasswordAuthenticationFilter.class)
                .oauth2Login()
                .authorizationEndpoint()
                .authorizationRequestResolver(
                        new CustomAuthorizationRequestResolver(
                                googleClientRegistrationRepository)
                )
                .and()
                .loginPage("/login") // this one is important to not use Spring security formlogin
                .clientRegistrationRepository(googleClientRegistrationRepository)
                .successHandler(this::handleOAuth2Login)
                
    }
    
    private void handleOAuth2Login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
        String clientRegistrationId = "google";

        OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();


        // NOTE:
        // - authorizedClient here is NOT null if @Primary is SET to @Bean(name = "googleClientService") in OAuth2ClientServiceConfig.java
        // - authorizedClient here is null if @Primary is  SET to @Bean(name = "githubClientService") in OAuth2ClientServiceConfig.java

        OAuth2AuthorizedClient authorizedClient = googleOAuth2AuthorizedClientService.loadAuthorizedClient(
                clientRegistrationId, oauth2User.getName())
                
         String accessToken = authorizedClient.getAccessToken().getTokenValue();        
                

                
                
    }


}


I'm not sure I missed something or Spring security has some limitations to support authenticate via multiple identity providers via OAuth2.


Solution

  • InMemoryClientRegistrationRepository, like any repository, is designed to manage any number of registrations.

    @Bean
    ClientRegistrationRepository clientRegistrationRepository(
        @Value("${github-client-id}") String githubClientId,
        @Value("${github-client-secret}") String githubClientSecret,
        @Value("${google-client-id}") String googleClientId,
        @Value("${google-client-secret}") String googleClientSecret) {
      final var github = ClientRegistration.withRegistrationId("github")
          .clientId(githubClientId)
          .clientSecret(githubClientSecret)
          .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
          .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
          .scope("user")
          .authorizationUri("https://github.com/login/oauth/authorize")
          .tokenUri("https://github.com/login/oauth/access_token")
          .userInfoUri("https://api.github.com/user")
          .userNameAttributeName("id")
          .clientName("gitHub")
          .build();
    
      final var google = ClientRegistration.withRegistrationId("google")
          .clientId(googleClientId)
          .clientSecret(googleClientSecret)
          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
          .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
          .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
          .scope("openid", "profile", "email", "address", "phone")
          .issuerUri("https://accounts.google.com")
          .userNameAttributeName(IdTokenClaimNames.SUB)
          .clientName("Google")
          .build();
    
      return new InMemoryClientRegistrationRepository(github, google);
    }