Issue
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.
Answered By - María Cristina Fernández López
Answer Checked By - Senaida (JavaFixing Volunteer)