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.
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);
}