首页 > 解决方案 > Spring Security UserNotFoundException 在带有 JSP 视图模板的 Spring Boot 中不显示错误消息

问题描述

我正在使用Spring Boot和 JSP 创建一个 Web 应用程序。我已经实现了 Spring Security 并且我正在覆盖该loadUserByUsername()方法,当找不到用户时,我会抛出UsernameNotFoundException("User not found")一条消息。现在我想在我的登录页面中显示一条正确的消息,我该如何实现呢?

MyUserDetailsS​​ervice.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);
        }
        ...
    }

登录控制器.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;              
        }

登录.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");
        }
        ...
    }

JSP 中显示的错误消息是“无效的用户名和密码”。那么如果抛出错误消息,如何显示“找不到用户” UserNotFoundException

提前致谢!

标签: javaspringspring-bootjspspring-security

解决方案


默认情况下,AbstractUserDetailsAuthenticationProvider如果未找到用户名或密码不正确,则会引发 BadCredentialsException。将此属性设置为 false 将导致UsernameNotFoundExceptions前者被抛出。

请注意,这被认为不如抛出BadCredentialsException两个异常安全。为此,您必须实例化您的身份验证提供程序,如下所示:

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

警告:这样做不是很好的安全做法。

请参阅此处setHideUserNotFoundExceptionsspring security bad credentials 区分无效的用户名或密码


推荐阅读