Search code examples
javaspring-bootspring-securityspring-security-oauth2spring-oauth2

Spring boot Oauth2 grant_type password always return invalid_grant Bad Credentials


I am trying to create an API that is in charge of securing the rest of my APIs. This api has the functionality of generating the token for the users of the whole set. Users must authenticate by clientId and secrt and with their username and password. To test it I am using a postman request like this:

  curl --location --request POST 'http://localhost:5101/oauth/token' \
--header 'Authorization: Basic cHJ1ZWJhOnBydWViYQ==' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=usuario' \
--data-urlencode 'password=pass' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all'

The authorization header is composed of the clientId and its secret key.

I always get invalid_grant error when grant_type is password

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

When I use the grant_type client_credentials it works fine, it returns the client token.

This is my Main class

@EnableAuthorizationServer
@SpringBootApplication
public class Auth implements CommandLineRunner {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public static void main(String[] args) {
        SpringApplication.run(Auth.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        // TODO Auto-generated method stub
        String password = "pass";
        for (int i = 0; i < 4; i++) {
            String pasBcript = passwordEncoder.encode(password);
            System.out.println(pasBcript);
        }
    }
}

I have used run method to obtain passwords for my database users.

SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserService userService;
    
    // @Autowired private AuthenticationEventPublisher eventPublisher;
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        //.and().authenticationEventPublisher(eventPublisher);
    }
    
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

AuthorizationServerConfig

@RefreshScope
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
    
    @Autowired
    private Environment env;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()") //Endpoint para generar el token public, POST /oauth/token
        .checkTokenAccess("isAuthenticated()"); // validar el toekn
    }

    // Doble autenticacion cliente (aplicacion) y usuarios
    // hay que envioar dos crecenciales las de la aplicacion y las del usuario
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()      
        .withClient("prueba")
        .secret(passwordEncoder.encode("prueba"))
        .scopes("all") // Permiso de la app prueba
        .authorizedGrantTypes("password", "refresh_token", "client_credentials") // Tipo de autenticación, como vamos a obtener el token
        .accessTokenValiditySeconds(3600)
        .refreshTokenValiditySeconds(3600);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
        
        endpoints.authenticationManager(authenticationManager)
        .tokenStore(tokenStore()) // Componente que se encarga de guardar el token
        .accessTokenConverter(accessTokenConverter())
        .tokenEnhancer(tokenEnhancerChain);
    }

    @Bean
    public JwtTokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        // Codigo secreto para firmar
        tokenConverter.setSigningKey(Base64.getEncoder().encodeToString(env.getProperty("config.security.oauth.jwt.key").getBytes()));
        return tokenConverter;
    }
    
}

UserService

@Service
public class UserService implements IUserService, UserDetailsService {

    Logger logger = LoggerFactory.getLogger(UserService.class);
    

    @Autowired
    private IUserRepository userRepository;
    @Override
    public AuthUser loadUserByUsername(String username) {
        return userRepository.findByUsername(username)
                    .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }

}

POM.XML

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.11</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>net.example.auth</groupId>
    <artifactId>auth-service</artifactId>
    <version>3.0.0</version>
    <packaging>jar</packaging>
    <name>Auth</name>
    <description></description>

    <properties>
        <java.version>11</java.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>-->

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.8.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.1.1.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

I'm sure the credentials are fine, both for the client and for the user. I have checked it several times. I already ran out of ideas, any help please??


Solution

  • The problem was in userRepository, I was looking for users by username, and in my database the username is the login field. I have changed username to login in the repository and now it works correctly.