Spring security UserNotFoundException not show error message in Spring Boot with JSP view template

Viewed 1891

I'm creating a web application with Spring Boot and JSP. I have implemented the Spring Security and I am overriding the loadUserByUsername() method, when the user is not found, I'm throwing UsernameNotFoundException("User not found") with a message. Now I want to show a proper message in my login page, how can I achieve that?

MyUserDetailsService.java

    package in.bushansirgur.security.service;

    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;

    import javax.transaction.Transactional;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.SpringSecurityMessageSource;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;

    import in.bushansirgur.security.entity.Role;
    import in.bushansirgur.security.entity.User;

    @Service
    public class MyUserDetailsService implements UserDetailsService {

        @Autowired
        private UserService userService;

        @Override
        @Transactional
        public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException{
            User user = userService.findUserByEmail(email);
            if(user == null || user.getActive() == null){
                throw new UsernameNotFoundException("User not found!");
            }

            List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
            return buildUserForAuthentication(user, authorities);
        }
        ...
    }

LoginController.java

    @RequestMapping(value = {"/login", "/"})
        public ModelAndView login(@RequestParam(name="error", required = false)String error, 
                @RequestParam(name="logout", required = false)String logout, HttpServletRequest request
                ) throws UnknownHostException {     
            // Port
            String portNumber = environment.getProperty("server.port");
            // Local address
            String hostAddress = InetAddress.getLocalHost().getHostAddress();
            String hostName = InetAddress.getLocalHost().getHostName();
            System.out.println("Host address:"+hostAddress+" Host name:"+hostName+" Port number:"+portNumber);
            // Remote address
            InetAddress.getLoopbackAddress().getHostAddress();
            InetAddress.getLoopbackAddress().getHostName();
            ModelAndView mv = new ModelAndView("login");

            if(error!=null) {
                mv.addObject("message", "Invalid Username and Password!");
            }

            if(logout!=null) {
                mv.addObject("logout", "User has successfully logged out!");
            }
            System.out.println("context path:"+request.getContextPath());
            return mv;              
        }

login.jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <c:set var="contextRoot" value="${pageContext.request.contextPath}" />
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <style>
    .help-block {
        color: #ff0000;
    }
    </style>
    <body>

        <h1>Login</h1>

        <c:if test="${not empty message}">
                        <div class="row">

                            <div class="col-md-offset-3 col-md-6">

                                <div class="alert alert-danger">${message}</div>

                            </div>
                        </div>

                    </c:if>
                    <c:if test="${not empty logout}">
                        <div class="row">

                            <div class="col-md-offset-3 col-md-6">

                                <div class="alert alert-success">${logout}</div>

                            </div>
                        </div>

                    </c:if>
        <form action="login" method="post" id="loginform">
            <input type="text" name="email" placeholder="Enter email" /><br/>
            <input type="password" name="password" placeholder="Enter password" /><br/>
            <button type="submit">Login</button>
            <%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> --%>
        </form>

        <a href="${contextRoot}/registration">Register</a>
        <script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.17.0/dist/jquery.validate.js"></script>
        <script>
            $(function(){
                const $loginForm = $("#loginform");

                if($loginForm.length){

                    $loginForm.validate({

                        rules: {

                            email: {
                                required: true
                            },

                            password: {
                                required: true
                            }
                        },

                        messages: {

                            email: {
                                required: "Please enter email"
                            },

                            password: {
                                required: "Please enter password"
                            }
                        },
                        errorElement: 'em',
                        errorPlacement: function(error, element) {
                            error.addClass('help-block');
                            error.insertAfter(element);
                        }

                    })

                }
            })
        </script>
    </body>
    </html>

WebSecurityConfiguration.java

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;

        @Autowired
        private MyUserDetailsService userDetailsService;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.
            authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/login").permitAll()
            .antMatchers("/registration").permitAll()
            .antMatchers("/confirm-account").permitAll()
            .antMatchers("/admin/**").hasAuthority("ADMIN")
            .antMatchers("/user/**").hasAnyAuthority("ADMIN", "USER")
            .anyRequest().authenticated().and().csrf().disable().formLogin()
            .loginPage("/login")//.failureUrl("/login?error=true")
            .defaultSuccessUrl("/dashboard")
            .usernameParameter("email")
            .passwordParameter("password")
            .and().exceptionHandling().accessDeniedPage("/403");
        }
        ...
    }

Error message displaying in JSP is "Invalid username and password". So how to display error message like "User not found" if UserNotFoundException is thrown!

Thanks in advance!

1 Answers

By default the AbstractUserDetailsAuthenticationProvider throws a BadCredentialsException if a username is not found or the password is incorrect. Setting this property to false will cause UsernameNotFoundExceptions to be thrown instead for the former.

Note this is considered less secure than throwing BadCredentialsException for both exceptions. To do this you have to instantiate your authentication provider, just like below:

  @Bean
public AuthenticationProvider daoAuthenticationProvider() {
    DaoAuthenticationProvider impl = new DaoAuthenticationProvider();
    impl.setUserDetailsService(yourUserDetailsService());
    impl.setHideUserNotFoundExceptions(false) ;
    return impl ;
}

Warning: it's not good security practice to do so.

See here, setHideUserNotFoundExceptions and spring security bad credentials distinguish between invalid username or password

Related