Spring Boot OAuth 2.0 UserDetails user not found

14,946

Solution 1

Yeah, I had the same issue... wanted to use JPA's UserDetailsService but the same problem - user couldn't be found... got resolved it eventually, thanks to Dave Syer's OAuth2 samples on GitHub.

The problem seem to be in authenticationManager instance autowired in @EnableAuthorizationServer AuthorizationServer class. AuthenticationManager is autowired there and seems to initialize with default DAOAuthenticationProvider, and for some reason it doesn't use custom JPA UserDetailsService we initialize authenticationManager with in WebSecurityConfiguration.

In Dave Syer samples authenticationManager is exposed as a bean in WebSecurityConfiguration:

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

then in AuthorizationServer we autowire authenticationManager as follows:

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

Once I did it I finally managed to get my user authenticated against my customer JPA user repository.

Solution 2

I faced the same issue and spent hours investigating the case. As a workaround, if you are using Spring Boot version 1.1.8.RELEASE, downgrade it to 1.0.2.RELEASE. Things went fine that way but I did not investigate yet the reasons of compatibility issue with Spring Boot version 1.1.8.RELEASE.

Solution 3

InitializeUserDetailsBeanManagerConfigurer has default order as

static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;

So it Initializee DaoAuthenticationProvider before custom one.

@Order(-5001)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { ... }
Share:
14,946
Steven Romero
Author by

Steven Romero

Updated on June 08, 2022

Comments

  • Steven Romero
    Steven Romero almost 2 years

    I am new to Spring Boot, and I am trying to configure OAuth 2.0. The problem I am having at this moment is that I keep getting the following message when I attempt to request for an access token:

    { "error": "invalid_grant", "error_description": "Bad credentials" }

    The error message in the Spring Boot console says that the user cannot be found.

    : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider : User 'stromero' not found : Returning cached instance of singleton bean 'authenticationAuditListener'

    I have implemented a custom user that has already been saved to a database using JPA, I am unable to figure why Spring Security cannot find this user, it may an issue with my logic or configuration. If someone with more experience can look at my code and perhaps guide me to the right direction, that would be greatly appreciated.

    This is the HTTP Request:

    POST /oauth/token HTTP/1.1 Host: localhost:8181 Authorization: Basic YnJvd3NlcjpzZWNyZXQ= Cache-Control: no-cache Content-Type: application/x-www-form-urlencoded username=stromero&password=password&client_id=browser&client_secret=secret&grant_type=password

    These are the classes that I used to implement my custom user and OAuth 2.0

    @Repository
    public interface UserRepository extends CrudRepository<CustomUser, String> {
    
    public CustomUser findByUsername(String name);
    }
    

    Below is the custom user I have created

    @Entity
    @Table (name = "custom_user")
     public class CustomUser {
    
    @Id
    @Column(name = "id", nullable = false, updatable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column(name = "username", unique=true, nullable = false)
    private String username;
    @Column(name = "password", nullable = false)
    private String password;
    @ElementCollection
    private List<String> roles = new ArrayList<>();
    
    public List<String> getRoles() {
        return roles;
    }
    
    public void setRoles(List<String> roles) {
        this.roles = roles;
    }
    
    public long getId() {
        return id;
    }
    
    public void setId(long id) {
        this.id = id;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    }
    

    Below is a customdetails service that reads the user information from the database and returns it as a UserDetails Object

    @Service
    @Transactional(readOnly = true)
    public class CustomUserDetailsService implements UserDetailsService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
        CustomUser customUser = userRepository.findByUsername(s);
    
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
    
        return new User(
                customUser .getUsername(),
                customUser .getPassword().toLowerCase(),
                enabled,
                accountNonExpired,
                credentialsNonExpired,
                accountNonLocked,
                getAuthorities(customUser.getRoles()));
    }
    public Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
        List<GrantedAuthority> authList = getGrantedAuthorities(roles);
        return authList;
    }
    
    
    public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
    }
    

    The class below is a data structure that holds both the UserDetailsService and ClientDetailsService

    public class ClientAndUserDetailsService implements UserDetailsService,
        ClientDetailsService {
    
        private final ClientDetailsService clients;
    
        private final UserDetailsService users;
    
        private final ClientDetailsUserDetailsService clientDetailsWrapper;
    
        public ClientAndUserDetailsService(ClientDetailsService clients,
                                           UserDetailsService users) {
            super();
            this.clients = clients;
            this.users = users;
            clientDetailsWrapper = new ClientDetailsUserDetailsService(this.clients);
        }
    
        @Override
        public ClientDetails loadClientByClientId(String clientId)
                throws ClientRegistrationException {
            return clients.loadClientByClientId(clientId);
        }
    
        @Override
        public UserDetails loadUserByUsername(String username)
                throws UsernameNotFoundException {
    
            UserDetails user = null;
            try{
                user = users.loadUserByUsername(username);
            }catch(UsernameNotFoundException e){
                user = clientDetailsWrapper.loadUserByUsername(username);
            }
            return user;
        }
        }
    

    The class below is my configuration for OAuth 2.0 using Spring Boot

     @Configuration
    public class OAuth2SecurityConfiguration {
    
    @Configuration
    @EnableWebSecurity
    protected static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        protected void registerAuthentication(
                final AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }
    }
    
    
    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends
            ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable();
    
            http.authorizeRequests().antMatchers("/oauth/token").anonymous();
    
            // Require all GET requests to have client "read" scope
            http.authorizeRequests().antMatchers(HttpMethod.GET, "/**")
                    .access("#oauth2.hasScope('read')");
    
            // Require all POST requests to have client "write" scope
            http.authorizeRequests().antMatchers(HttpMethod.POST,"/**")
                    .access("#oauth2.hasScope('write')");
        }
    
    }
    
    @Configuration
    @EnableAuthorizationServer
    @Order(Ordered.LOWEST_PRECEDENCE - 100)
    protected static class AuthorizationServer extends
            AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        private ClientAndUserDetailsService combinedService;
    
        public AuthorizationServer() throws Exception {
    
            ClientDetailsService clientDetailsService = new InMemoryClientDetailsServiceBuilder()
                    .withClient("browser")
                    .secret("secret")
                    .authorizedGrantTypes("password")
                    .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                    .scopes("read","write")
                    .resourceIds("message")
                    .accessTokenValiditySeconds(7200)
                    .and()
                    .build();
    
            // Create a series of hard-coded users.
            UserDetailsService userDetailsService = new CustomUserDetailsService();
            combinedService = new ClientAndUserDetailsService(clientDetailsService,   userDetailsService);
        }
    
        @Bean
        public ClientDetailsService clientDetailsService() throws Exception {
            return combinedService;
        }
        @Bean
        public UserDetailsService userDetailsService() {
            return combinedService;
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            endpoints.authenticationManager(authenticationManager);
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients)
                throws Exception {
            clients.withClientDetails(clientDetailsService());
        }
    
    }
    
    }
    

    Below is my pom.xml file

        <properties>
        <tomcat.version>8.0.8</tomcat.version>
    </properties>
    
    <dependencies>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    
        <!-- Postgres JDBC Driver -->
    
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.2-1002-jdbc4</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    
        <!-- Hibernate validator -->
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    
    
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>
    
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>17.0</version>
        </dependency>
    
    </dependencies>