I'm trying to build a route, processed in Spring, to generate a token if the user/password submited in the body are valid. Only in that scenario it responds with the token.
The problem is that I'm Posting the correct username and password, exactly as stored in DB but I keep getting a "bad credentials" error.
This is the controller:
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
try {
LOGGER.info("Received a request to generate a token for user: "+authenticationRequest.getUsername()+"/"+authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
System.out.println(userDetails);
// Logs: org.springframework.security.core.userdetails.User@7ee29d27: Username: thisWasTheGoodUserName; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Admin
// And also logs the hibernate query:
// select
// employee0_.id as id1_0_,
// employee0_.employee_name as employee_name2_0_,
// employee0_.pwd as pwd3_0_,
// employee0_.user_name as user_nam4_0_
// from
// employees employee0_
// where
// employee0_.user_name=?
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, authenticationRequest.getPassword(), userDetails.getAuthorities());
// This step gets executed
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
// And the same query to the DB is logged, one that I can run on dbeaver manually and get results from:
// select
// employee0_.id as id1_0_,
// employee0_.employee_name as employee_name2_0_,
// employee0_.pwd as pwd3_0_,
// employee0_.user_name as user_nam4_0_
// from
// employees employee0_
// where
// employee0_.user_name=?
// And throws exception in this authenticate
final String token = jwtTokenUtil.generateToken(userDetails);
TokenSucess tokenSuccessResponse = new TokenSucess(true, new JwtResponse(token));
LOGGER.info("Obtained token with success ");
return new ResponseEntity<TokenSucess>(tokenSuccessResponse, HttpStatus.OK);
} catch (Exception e) {
TokenError tokenErrorResp = new TokenError(false, "Error generating token.");
LOGGER.error("Error generating a token. Details: "+ e);
return new ResponseEntity<TokenError>(tokenErrorResp, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is the method I'm using in the service:
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Employee emp = employeeRepository.findByUserName(username);
if (emp == null ) {
throw new UsernameNotFoundException("Employee not found with username: " + username);
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("Admin"));
return new org.springframework.security.core.userdetails.User(emp.getUserName(), emp.getPassword(), authorities);
}
}
The security config:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsService jwtUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
try {
httpSecurity.csrf().disable()
// don't authenticate this particular request
.authorizeRequests().antMatchers("/authenticate").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
} catch (Exception e) {
LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
}
}
}
And this is the curl example:
curl --location --request POST 'http://127.0.0.1:<port>/authenticate'
--header 'Content-Type: application/json'
--data-raw '{
"username": "thisWasTheGoodUserName",
"password": "123454321"
}'
Already tried with diferent scenarios in DB:
1. thisWasTheGoodUserName/123454321
2. thisWasTheGoodUserName/$2y$12$Mj0PRHipe14Wgm5c/GOuO.RyhjhuwRwoQYUnK8LcgsvHzQ4weYHGm (bcrypted 123454321)
Using several "step into" I found that the problem is happening in the following function - /Users/<user>/.m2/repository/org/springframework/security/spring-security-core/5.0.3.RELEASE/spring-security-core-5.0.3.RELEASE.jar!/org/springframework/security/authentication/dao/DaoAuthenticationProvider.class
, even though the passwords are se same string:
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
// presentedPassword is exactly the same as the one in userDetails.getPassword() but the matcher returns False...
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
Any idea what I could be doing wrong and causing the following Exception?
ERROR c.r.t.s.c.JwtAuthenticationController - Error generating a token. Details: org.springframework.security.authentication.BadCredentialsException: Bad credentials
Note: found the problem causing all this: the stored password in the DB was encrypted using an online bcrypt generator... The value does not match the generated bcrypt encoded password even though they are the same string.