2

I have somewhat special case in my spring boot RESTfull project, not the standard customization of error message on auth exception. I need a different message depending if the username or password is wrong, or if username doesn't exists, or if the user was deactivated in the database. Currently I can only get message "Bad credentials" and I haven't found any solutions how to customize message depending on some user properties or special cases.

I currently have custom authentication provider like this:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication)
            throws org.springframework.security.core.AuthenticationException {


        String name = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails userDetails = userDetailsService.loadUserByUsername(name);


        if(passwordEncoder.matches(password, userDetails.getPassword())) {
            return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(),
                    userDetails.getAuthorities());
        }

        return null;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }


}

And I have custom user details service like this:

@Service
public class CustomUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService{

    @Autowired
    UserService userService; //my custom user service

    @Override
    public UserDetails loadUserByUsername(String username) {
        try {
            User user = userService.getUserByUsername(username);

            if(user == null) {
                throw new UsernameNotFoundException("Username doesn't exist");
            } else if(user.isDeactivated()) {
                throw new UsernameNotFoundException("User deactivated");
            }

            List<Authority> listOfAuthorities = userService.getAllAuthoritiesFromUser(user.getUserId());
            List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

            for(Authority authority : listOfAuthorities) {
                grantedAuthorities.add(new SimpleGrantedAuthority(authority.getName()));
            }

            org.springframework.security.core.userdetails.User userNew =
            new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
            return userNew;

        }
        catch(Exception ex) {
            throw new UsernameNotFoundException("Username or password not correct");
        }

    }
}

Where can I process the message from throw new UsernameNotFoundException and return it as "error_description"?

EDIT Here is also my SecurityConfig and ResourceServerConfig:

@Configuration
@EnableWebSecurity
@Order(Ordered.LOWEST_PRECEDENCE)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    CustomUserDetailsService userDetailsService;

    @Autowired
    private CustomAuthenticationProvider authProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider)  
        .userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.addFilterBefore(new AuthenticationTokenFilter(authenticationManager()), BasicAuthenticationFilter.class);
    }

    // must be overriden and exposed as @Bean, otherwise boot's AuthenticationManagerConfiguration will take precedence
    @Bean @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{

    @Autowired
    private AuthExceptionEntryPoint myEntryPoint;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.anonymous().and().authorizeRequests().antMatchers("/**")
                .authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(myEntryPoint).accessDeniedHandler(new OAuth2AccessDeniedHandler());
    }

}
Mario Rudman
  • 1,899
  • 2
  • 14
  • 18

1 Answers1

3

This generic message on Spring Security has a purpose, and it's to obfuscate what's the actual reason for the failure in the login.

Once you give specific messages as you want, e.g. Username doesn't exist, User deactivated, Password incorrect, and so on, you're starting to give too much information for a malicious user.

Update

If you still want to go that way, you can implement your own AuthenticationFailureHandler, something like this should work:

public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, 
                                            AuthenticationException exception) throws IOException, ServletException {
            super.onAuthenticationFailure(request, response, exception);
            if (exception.getClass().isAssignableFrom(UsernameNotFoundException.class)) {
                response.sendRedirect("User not found")
            } else if (exception.getClass().isAssignableFrom(LockedException.class)) {
                response.sendRedirect("User Locked")
            }
        }
    }
Marcos Barbero
  • 1,101
  • 8
  • 14
  • Thanks for the answer. I understand why the actual reason is not given by default but I have specific request from the client. Is there no workaround for specific cases when there is a need to have more specific message? – Mario Rudman Feb 10 '20 at 20:23
  • Thanks again. As my project is RESTful web service I'm note sure where is the right place to add custom failure handle. I've updated my question with code with `ResourceServerConfig` and `SecurityConfig`. – Mario Rudman Feb 10 '20 at 21:48