0

I am newbie in Spring Boot.

I'd like to know how can I validate the Credentials DTO before the Authentication in Spring Boot?

I have this controller:

@PostMapping
public ResponseEntity<TokenDTO> generateTokenJwt(@Valid @RequestBody CredentialsDTO credentials, BindingResult result)
        throws AuthenticationException {

    TokenDTO response = new TokenDTO();

    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
            credentials.username, credentials.password);

    Authentication authentication = authenticationManager.authenticate(authenticationToken);

    SecurityContextHolder.getContext().setAuthentication(authentication);

    UserDetails userDetails = userDetailsService.loadUserByUsername(credentials.username);

    String token = jwtTokenUtils.getToken(userDetails);

    response.token = token;

    return ResponseEntity.ok(response);
}

The Credentials DTO is:

public class CredentialsDTO {

    @NotEmpty
    public String username;

    @NotEmpty
    public String password;

}

So, when I execute the POST like this:

curl -i -X POST \
   -H "Accept:application/json" \
   -H "Content-Type:application/json" \
   -d \
'{
    "username": "",
    "password": "123456"
}' \
 'http://localhost:8080/api/login'

I'd like to show a 422 error telling that the username propertie should not be empty, but, what is happening is that authentication is being done first and the error returned is 401.

Luciano Borges
  • 817
  • 3
  • 12
  • 31
  • I am a bit confused about the purpose of your post method. It looks to me that you are doing a lot of manual authentication. You should never perform manual authentication on your mapping. Authentication is a critical aspect and should be in dedicated classes. Spring already got a lot of built-in functionality which you can adapt and alter to fit your needs. You should look into GlobalMethodSecurityConfiguration and UserDetailsService. – kkflf May 04 '18 at 18:45

2 Answers2

1

You could add an implementation of a HandlerInterceptor.

Your implementation of the preHandle(...) method should handle the error scenario and then return false (to stop the request from being passed to other listeners and handlers).

You could extend HandlerInterceptorAdapter to make life easier. That would look something like this.

@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(isRequestToLoginUrl(request)) {
            return hasValidBody(request);
        }
        return true;
    }
}

Then you have to register that handler with Spring MVC, by adding this:

@Configuration
public static class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(loginInterceptor);
    }
}
Maarten
  • 196
  • 8
1

I assume the problem is that Spring handles authentication before your Post method is ever reached. Bean validation will therefore never be performed on CredentialsDTO

I would suggest you to create your own UserDetailsService. This will enable you do some validation on the authentication.

Example:

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserSecurityService implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    public User loadUserByUsername(String email) throws UsernameNotFoundException {

        if (email.isEmpty()) {
        //Throw whatever exception you see fitting
            throw new UsernameNotFoundException("security.userNotFound");
        }

        User user = userRepository.getUserByEmail(email);

        return user;
    }

}
kkflf
  • 2,435
  • 3
  • 26
  • 42