I have this error and I don't know what it could be. I'm following a tutorial to do the User authentication, But when I try to do the authentication it throws a "Bad credentials" but the credentials are right. I'm using MongoDB.
From the tutorial I'm following with these steps it should work (other users confirmed that they did) but the only thing I did differently from the tutorial is in the "ClientEntity" class that already existed and I just implemented what I needed.
User return:
ClientEntity(id=63166ddbe3ea6c4fffd70818, clientName=Test, clientCpf=000.000.000-00, [email protected], clientPassword=2b598e4c0e79baf9dc9211ad303e7626, clientIsBlocked=false, clientBirthDate=1989-05-20, creditCards=[CreditCardEntity()], clientCategory=[ACAO, COMEDIA])
My request to signin, I'm logging in by email and password:
AccountCredentials: AccountCredentials([email protected], password=2b598e4c0e79baf9dc9211ad303e7626)
I know the problem is in that class in "authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(email, password));" but I don't know how to solve it, because the credentials are correct
My AuthService class:
@Service
public class AuthService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private ClientRepository repository;
public ResponseEntity signin(AccountCredentials data) {
try {
var email = data.getEmail();
var password = data.getPassword();
log.info("METODO SIGNIN, AccountCredentials: " data);
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(email, password));
log.info("FOI AUTENTICADO!");
var user = repository.findByClientEmail(email);
log.info("O valor de User é: " user);
var tokenResponse = new Token();
if (user != null) {
tokenResponse = tokenProvider.createAccessToken(email, user.getRoles());
} else {
throw new UsernameNotFoundException("Email " email " not found!");
}
return ResponseEntity.ok(tokenResponse);
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println(e.getCause());
System.out.println(e.getLocalizedMessage());
throw new BadCredentialsException("Invalid email/password supplied!");
}
}
public ResponseEntity refreshToken(String email, String refreshToken) {
var user = repository.findByClientEmail(email);
var tokenResponse = new Token();
if (user != null) {
tokenResponse = tokenProvider.refreshToken(refreshToken);
} else {
throw new UsernameNotFoundException("Email " email " not found!");
}
return ResponseEntity.ok(tokenResponse);
}
}
My ClientEntity:
@Data
@Document(collection = "Client")
public class ClientEntity implements UserDetails, Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id;
private String clientName;
private String clientCpf;
private String clientEmail;
private String clientPassword;
private boolean clientIsBlocked = false;
private LocalDate clientBirthDate;
private List<CreditCardEntity> creditCards;
private List<ClientCategoryEnum> clientCategory;
private List<Permission> permissions;
public List<String> getRoles() {
List<String> roles = new ArrayList<>();
for (Permission permission : permissions) {
roles.add("USER");
roles.add(permission.getDescription());
}
return roles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.permissions;
}
@Override
public String getPassword() {
return this.clientPassword;
}
@Override
public String getUsername() {
return this.clientEmail;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
My Class Permission:
@Data
@Document(collection = "Roles")
public class Permission implements GrantedAuthority, Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
private String description;
@Override
public String getAuthority() {
return this.description;
}
}
My SecurityConfig:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenProvider tokenProvider;
/*
@Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
*/
@Bean
public PasswordEncoder passwordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("pbkdf2", encoders);
passwordEncoder.setDefaultPasswordEncoderForMatches(new Pbkdf2PasswordEncoder());
return passwordEncoder;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(
"/auth/signin",
"/auth/refresh"
).permitAll()
.antMatchers("/api/movie_search/**").authenticated()
.and()
.cors()
.and()
.apply(new JwtConfigurer(tokenProvider));
}
}
My CLass TokenProvider:
@Service
public class JwtTokenProvider{
@Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";
@Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h
@Autowired
private UserDetailsService userDetailsService;
Algorithm algorithm = null;
@PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
algorithm = Algorithm.HMAC256(secretKey.getBytes());
}
public Token createAccessToken(String email, List<String> roles) {
Date now = new Date();
Date validity = new Date(now.getTime() validityInMilliseconds);
var accessToken = getAccessToken(email, roles, now, validity);
var refreshToken = getRefreshToken(email, roles, now);
return new Token(email, true, now, validity, accessToken, refreshToken);
}
public Token refreshToken(String refreshToken) {
if (refreshToken.contains("Bearer ")) refreshToken =
refreshToken.substring("Bearer ".length());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(refreshToken);
String email = decodedJWT.getSubject();
List<String> roles = decodedJWT.getClaim("roles").asList(String.class);
return createAccessToken(email, roles);
}
private String getAccessToken(String email, List<String> roles, Date now, Date validity) {
String issuerUrl = ServletUriComponentsBuilder
.fromCurrentContextPath().build().toUriString();
return JWT.create()
.withClaim("roles", roles)
.withIssuedAt(now)
.withExpiresAt(validity)
.withSubject(email)
.withIssuer(issuerUrl)
.sign(algorithm)
.strip();
}
private String getRefreshToken(String email, List<String> roles, Date now) {
Date validityRefreshToken = new Date(now.getTime() (validityInMilliseconds * 3));
return JWT.create()
.withClaim("roles", roles)
.withIssuedAt(now)
.withExpiresAt(validityRefreshToken)
.withSubject(email)
.sign(algorithm)
.strip();
}
public Authentication getAuthentication(String token) {
DecodedJWT decodedJWT = decodedToken(token);
UserDetails userDetails = this.userDetailsService
.loadUserByUsername(decodedJWT.getSubject());
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
private DecodedJWT decodedToken(String token) {
Algorithm alg = Algorithm.HMAC256(secretKey.getBytes());
JWTVerifier verifier = JWT.require(alg).build();
DecodedJWT decodedJWT = verifier.verify(token);
return decodedJWT;
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring("Bearer ".length());
}
return null;
}
public boolean validateToken(String token) {
DecodedJWT decodedJWT = decodedToken(token);
try {
if (decodedJWT.getExpiresAt().before(new Date())) {
return false;
}
return true;
} catch (Exception e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token!");
}
}
}
My ClientService:
@Service
public class ClientService implements UserDetailsService {
private final ClientRepository clientRepository;
private final ModelMapper modelMapper;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
var user = clientRepository.findByClientEmail(email);
if (user != null) {
return user;
} else {
throw new UsernameNotFoundException("Email " email " not found!");
}
}
}
In debug I came across this: UsernamePasswordAuthenticationToken Credentials=[PROTECTED], Authenticated=false, Details=null, Granted Authorities=[]]
CodePudding user response:
Short answer: Passwords stored in your DB must be encrypted by PasswordEncoder
.
Long answer:
You created a bean of type PasswordEncoder
, so it is used by AuthenticationProvider
when you call .authenticate()
method of AuthenticationManager
class.
As stated in java docs, DelegatingPasswordEncoder
expects your passwords to be stored in DB in format like this:
{encoderId}encryptedPassword
You use pbkdf2
password encoder as a default encoder, so passwords in DB should look like this:
{pbkdf2}encryptedPassword
By the look of the returned ClientEntity
, you haven't used the PasswordEncoder
when you saved this entity to a DB, so the raw password is stored instead of an encrypted one.
The authentication flow should look like this:
- your app registers a user, stores his password in DB in encrypted form using
PasswordEncoder
bean; - user passes his username (email or whatever is neccessary) and raw password to your app through login form of API endpoint;
AuthenticationProvider
retrieves encrypted password from the DB and compares it with the raw password the user has provided