Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security: Custom UserDetailsService not being called (using Auth0 authentication)

I'm new to the Spring framework, so I apologize in advance for any gaping holes in my understanding.

I'm using Auth0 to secure my API, which works perfectly. My setup & config is the same as the suggested setup in the Auth0 documentation:

// SecurityConfig.java
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // auth0 config vars here

    @Override
    protected void configure(HttpSecurity http) {
        JwtWebSecurityConfigurer
                .forRS256(apiAudience, issuer)
                .configure(http)
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/api/public").permitAll()
                .antMatchers(HttpMethod.GET, "/api/private").authenticated();
    }
}

With this setup, the spring security principal is being set to the userId (sub) from the jwt token: auth0|5b2b.... However, instead of just the userId, I want it set to the matching user (from my database). My question is how to do that.

What I've tried

I've tried implementing a custom database-backed UserDetailsService that I copied from this tutorial. However, it's not getting called regardless of how I try to add it to my conf. I've tried adding it several different ways with no effect:

// SecurityConfig.java (changes only)

    // My custom userDetailsService, overriding the loadUserByUsername
    // method from Spring Framework's UserDetailsService.
    @Autowired
    private MyUserDetailsService userDetailsService;

    protected void configure(HttpSecurity http) {
        http.userDetailsService(userDetailsService);  // Option 1
        http.authenticationProvider(authenticationProvider());  // Option 2
        JwtWebSecurityConfigurer
                [...]  // The rest unchanged from above
    }

    @Override  // Option 3 & 4: Override the following method
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider());  // Option 3
        auth.userDetailsService(userDetailsService);  // Option 4
    }

    @Bean  // Needed for Options 2 or 4
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        return authProvider;
    }

Unfortunately none of the similar "userDetails not being called" questions have helped me due to me needing to combine it with Auth0 authentication.

I'm not positive that I'm on the right path with this. It seems strange to me that I can't find any documentation from Auth0 on this extremely common use case, so maybe I'm missing something obvious.

PS: Not sure if relevant, but the following is always logged during init.

Jun 27, 2018 11:25:22 AM com.test.UserRepository initDao
INFO: No authentication manager set. Reauthentication of users when changing passwords will not be performed.

EDIT 1:

Based on Ashish451's answer, I tried copying his CustomUserDetailsService, and added the following to my SecurityConfig:

@Autowired
private CustomUserDetailsService userService;

@Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

@Autowired
public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
    auth.userDetailsService( userService );
}

Unfortunately with those changes, CustomUserDetailsService is still not being called.

EDIT 2:

Output when adding the logging method suggested by @Norberto Ritzmann:

Jul 04, 2018 3:49:22 PM com.test.repositories.UserRepositoryImpl initDao
INFO: No authentication manager set. Reauthentication of users when changing passwords will not be performed.
Jul 04, 2018 3:49:22 PM com.test.runner.JettyRunner testUserDetailsImpl
INFO: UserDetailsService implementation: com.test.services.CustomUserDetailsService
like image 591
Joakim Avatar asked Jun 27 '18 12:06

Joakim


People also ask

What is userdetailsservice in Spring Boot?

UserDetailsService is used by DaoAuthenticationProvider for retrieving a username, password, and other attributes for authenticating with a username and password. Spring Security provides in-memory and JDBC implementations of UserDetailsService. You can define custom authentication by exposing a custom UserDetailsService as a bean.

How do I authenticate a spring security object?

The Authentication Provider Spring Security provides a variety of options for performing authentication. These follow a simple contract – an Authentication request is processed by an AuthenticationProvider and a fully authenticated object with full credentials is returned.

Is userdetailsservice an alternative to authenticationprovider?

UserDetailsService is not an alternative to AuthenticationProvider but it is used for a different purpose i.e. to load user details. Typically, an AuthenticationProvider implementation can use UserDetailsService instance to retrieve user details during its authentication process.

What is the use of websecurityconfigurer adapter in spring?

We are extending WebSecurityConfigurerAdapter class, which provides a convenient base class for creating a WebSecurityConfigurer instance. We are injecting custom UserDetailsService in the DaoAuthenticationProvider. Spring security use this provider to load the customer information. 7. Application Testing


Video Answer


1 Answers

You could extend JwtAuthenticationProvider with overridden authenticate method which will put your user into Authentication object:

  • Using springboot 2.1.7.RELEASE
  • Auth0 deps: com.auth0:auth0:1.14.2, com.auth0:auth0-spring-security-api:1.2.5, com.auth0:jwks-rsa:0.8.3

Note: some errors in the following code snippets might exist as I've transformed kotlin code by hand into java

Configure SecurityConfig as usual but pass modified authentication provider:

@Autowired UserService userService;

...

@Override
protected void configure(HttpSecurity http) {

    // same thing used in usual method `JwtWebSecurityConfigurer.forRS256(String audience, String issuer)`
    JwkProvider jwkProvider = JwkProviderBuilder(issuer).build()

    // provider deduced from existing default one
    Auth0UserAuthenticationProvider authenticationProvider = new Auth0UserAuthenticationProvider(userService, jwkProvider, issuer, audience)

    JwtWebSecurityConfigurer
           .forRS256(apiAudience, issuer, authenticationProvider)
           .configure(http)
           .authorizeRequests()
           .antMatchers(HttpMethod.GET, "/api/public").permitAll()
           .antMatchers(HttpMethod.GET, "/api/private").authenticated();
}

Extend default JwtAuthenticationProvider which is usually used in method JwtWebSecurityConfigurer.forRS256(String audience, String issuer)

public class Auth0UserAuthenticationProvider extends JwtAuthenticationProvider {
    private final UserService userService;
    public (UserService userService, JwkProvider jwkProvider, String issuer, String audience) {
        super(jwkProvider, issuer, audience);
        this.userService = userService;
    }

    /**
     * Intercept Authentication object before it is set in context
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Authentication jwtAuth = super.authenticate(authentication);
        // Use your service and get user details here
        User user = userService.getDetailsOrWhatever(jwtAuth.getPrincipal().toString());
        // TODO implement following class which merges Auth0 provided details with your user
        return new MyAuthentication(jwtAuth, user);
    }
}

Implement your own MyAuthentication.class which will override getDetails() and return the actual user instead of decoded token given by Auth0 library.

Afterwards user will be available in

SecurityContextHolder.getContext().getAuthentication().getDetails();
like image 99
Ivar Avatar answered Oct 02 '22 15:10

Ivar