Java – Why doesn’t Spring use my PrincipalExtractor bean?

Why doesn’t Spring use my PrincipalExtractor bean?… here is a solution to the problem.

Why doesn’t Spring use my PrincipalExtractor bean?

Spring doesn’t want to use my PrincipalExtractor bean. Instead, it uses the default FixedPrincipalExtractor.

I’m trying to follow Spring’s OAuth2 tutorial:
https://spring.io/guides/tutorials/spring-boot-oauth2/

Everything went smoothly until I decided to save the authenticated user to my database. The tutorial simply says: “It’s too easy, so we won’t show how to do it”. Of course, that was the moment when I was stuck for days.

There is a WebSecurityConfig class. It’s a mess, but it’s used for educational purposes.

@Configuration
@EnableWebSecurity
@EnableOAuth2Client
@RestController
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
    OAuth2ClientContext oauth2ClientContext;

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/", "/login**", "/js/**", "/error**", "/webjars/**").permitAll()
                .anyRequest().authenticated()
                .and().logout().logoutSuccessUrl("/").permitAll()
                .and()
                .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
                .addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);

}

private Filter ssoFilter() {
        CompositeFilter filter = new CompositeFilter();
        List<Filter> filters = new ArrayList<>();
        filters.add(ssoFilter(google(), "/login/google"));
        filter.setFilters(filters);

return filter;
    }

private Filter ssoFilter(ClientResources client, String path) {
        OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationFilter = new OAuth2ClientAuthenticationProcessingFilter(path);
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
        oAuth2ClientAuthenticationFilter.setRestTemplate(oAuth2RestTemplate);
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(client.getResource().getUserInfoUri(),
                client.getClient().getClientId());
        tokenServices.setRestTemplate(oAuth2RestTemplate);
        oAuth2ClientAuthenticationFilter.setTokenServices(tokenServices);

return oAuth2ClientAuthenticationFilter;
    }

@Bean
    @ConfigurationProperties("google")
    public ClientResources google() {
        return new ClientResources();
    }

@Bean
    public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
        FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
        registration.setFilter(filter);
        registration.setOrder(-100);

return registration;
    }

@Bean
    public PrincipalExtractor principalExtractor(UserDetailsRepo userDetailsRepo) {
        return map -> {
            String id = (String) map.get("sub");
            User user = userDetailsRepo.findById(id).orElseGet(() -> {
                User newUser = new User();

newUser.setId(id);
                newUser.setEmail((String) map.get("email"));
                 and so on...

return newUser;
            });

return userDetailsRepo.save(user);
        };
    }
}

class ClientResources {

@NestedConfigurationProperty
    private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();

@NestedConfigurationProperty
    private ResourceServerProperties resource = new ResourceServerProperties();

public AuthorizationCodeResourceDetails getClient() {
        return client;
    }

public ResourceServerProperties getResource() {
        return resource;
    }
}

and application.yml:

spring:
  datasource:
    url: jdbc:postgresql://localhost/my_db
    username: postgres
    password: password
  jpa:
    generate-ddl: true
    properties:
      hibernate:
        jdbc:
          lob:
            non_contextual_creation: true

google:
  client:
    clientId: 437986124027-7072jmbsba04d11fft0h9megkqcpem2t.apps.googleusercontent.com
    clientSecret: ${clientSecret}
    accessTokenUri: https://www.googleapis.com/oauth2/v4/token
    userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
    clientAuthenticationScheme: form
    scope: openid,email,profile
  resource:
    userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
    preferTokenInfo: true

As I wrote above, Spring doesn’t really want to use my PrincipalExtractor bean, but the default FixedPrincipalExtractor. I spent a lot of time trying to fix this but nothing helped. Except change application.yml:: like this

security:
  oauth2:
    client:
      clientId: 620652621050-v6a9uqrjq0ejspm5oqbek48sl6od55gt.apps.googleusercontent.com
      clientSecret: ${clientSecret}
  [...]
    resource:
      userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
      preferTokenInfo: true

As you can see, google.client.clientId becomes security.oauth2.client.clientId.

If you remove all the filter methods and all the related stuff, then it works, yes. It does use my PrincipleExtractor. But how do I add more authentication providers (Facebook, GitHub, etc.) and local authentication now?

Finally, I have a few questions:

  1. How do I get Spring to use my PrincipalExtractor?
  2. Should I use PrincipalExtractor? Maybe there is another way to do this?
  3. Having problems with my application.yml?

Something I’ve tried :

  1. Add @EnableAuthorizationServer ( Why is my spring @bean never instantiated? )

Nothing has changed.

  1. Add ResourceServerTokenServices ( PrincipalExtractor and AuthoritiesExtractor doesn’t hit )

Spring could not find the UserInfoRestTemplateFactory. I’m guessing adding beans manually is not right, it simply won’t work.

  1. Many different solutions. None of them work.

Solution

When you define ssoFilter, add the following:

tokenServices.setPrincipalExtractor(myCustomPrincipalExtractor());

Benefits: The same goes for AuthorityExtractor.

Related Problems and Solutions