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:
- How do I get Spring to use my
PrincipalExtractor
? - Should I use
PrincipalExtractor
? Maybe there is another way to do this? - Having problems with my
application.yml
?
Something I’ve tried :
- Add @EnableAuthorizationServer ( Why is my spring @bean never instantiated? )
Nothing has changed.
- 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.
- Many different solutions. None of them work.
Solution
When you define ssoFilter
, add the following:
tokenServices.setPrincipalExtractor(myCustomPrincipalExtractor());
Benefits: The same goes for AuthorityExtractor
.