美文网首页服务Framwork
SpringBoot集成SpringSecurity - 异常处

SpringBoot集成SpringSecurity - 异常处

作者: 匆匆岁月 | 来源:发表于2019-07-24 10:29 被阅读5次

    源码地址:https://github.com/springsecuritydemo/microservice-auth-center03

    当我们登录失败的时候,SpringSecurity 帮我们跳转到了 /login?error URL,奇怪的是不管是控制台还是网页上都没有打印错误信息。

    这是因为首先 /login?error 是SpringSecurity 默认的失败 URL,其次如果你不自己处理这个异常,这个异常时不会被处理的。

    一、常见异常

    我们先来列举下一些 SpringSecurity 中常见的异常:

    • UsernameNotFoundException (用户不存在)
    • DisableException(用户已被禁用)
    • BadCredentialsException(坏的凭据)
    • LockedException(账号锁定)
    • CerdentialsExpiredException(证书过期)
    • ...
      以上列出的这些异常都是 AuthenticationException 的子类,然后我们看 SpringSecurity 是如何处理 AuthenticationException 异常的。

    二、源码分析

    SpringSecurity的异常处理是在过滤器中进行的,我们在 AbastrctAuthenticationProcessingFilter 中找到了对 Authentication 的处理:

    • 在 doFilter() 中,捕获 AuthenticationException 异常,并交给 unsuccessfulAuthentication() 处理。


    • unsuccessfulAuthentication() 中,转交给了 SimpleUrlAuthenticationFailureHandler 类的 onAuthencicationFailure() 处理。

    • 在 onAuthenticationFailure() 中,首先判断有没有设置 defaultFailureUrl

      a. 如果没有设置,直接返回 401 错误,即 HttpStatus.UNAUTHORIZED 的值。
      b. 如果设置了,首先执行 saveException() 方法。然后判断 forwardToDestination 是否为服务器调整,默认使用重定向即客户端跳转。

    • 在 saveException() 方法中,首先判断 forwardToDestination,如果使用服务器跳转则写入Request,客户端跳转则写入 Session。写入名为 WebAttributes.AUTHENTICATION_EXCEPTION 常量对应值SPRING_SECURITY_LAST_EXCEPTION,值为 AuthenticationException 对象。

    至此 SpringSecurity 完成了异常处理,总结下流程:

    –> AbstractAuthenticationProcessingFilter.doFilter()
    –> AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication()
    –> SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure()
    –> SimpleUrlAuthenticationFailureHandler.saveException()

    三、处理异常

    上面通过源码看着挺复杂,但真正处理起来SpringSecurity为我们提供了方便的方式,我们只需要指定错误的url,然后在该方法中对异常进行处理即可。

    • 指定错误url ,在WebSecurityConfig 中添加 .failureUrl("/login/error")
     @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    // 如果有允许匿名的url,填在下面
    //                .antMatchers().permitAll()
                    .anyRequest().authenticated()
                    .and()
                    // 设置登陆页
                    .formLogin().loginPage("/login")
                    // 设置登陆成功url
                    .defaultSuccessUrl("/").permitAll()
                    // 设置登录失败url
                    .failureUrl("/login/error")
                    // 自定义登陆用户名和密码参数,默认为username和password
    //                .usernameParameter("username")
    //                .passwordParameter("password")
                    .and()
                    .logout().permitAll()
                    // 自动登录
                    .and().rememberMe()
                    .tokenRepository(persistentTokenRepository())
                    // 有效时间,单位:s
                    .tokenValiditySeconds(60)
                    .userDetailsService(userDetailsService);
    
            // 关闭CSRF跨域
            http.csrf().disable();
        }
    
    • 在 Controller 中编写 loginError方法完成异常处理操作:
     @GetMapping("/login/error")
        @ResponseBody
        public Result loginError(HttpServletRequest request) {
            AuthenticationException authenticationException = (AuthenticationException) request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
            log.info("authenticationException={}", authenticationException);
            Result result = new Result();
            result.setCode(201);
    
            if (authenticationException instanceof UsernameNotFoundException || authenticationException instanceof BadCredentialsException) {
                result.setMsg("用户名或密码错误");
            } else if (authenticationException instanceof DisabledException) {
                result.setMsg("用户已被禁用");
            } else if (authenticationException instanceof LockedException) {
                result.setMsg("账户被锁定");
            } else if (authenticationException instanceof AccountExpiredException) {
                result.setMsg("账户过期");
            } else if (authenticationException instanceof CredentialsExpiredException) {
                result.setMsg("证书过期");
            } else {
                result.setMsg("登录失败");
            }
            return result;
        }
    

    四、运行项目

    首先我们修改 CustomUserDetailsService loadUserByUsername() 方法的返回值:

    1. 输入错误的用户名或密码:


    2. 修改返回值:enable 为 false



    3. 修改返回值:accountNonExpired 为 false



    4. 修改返回值:credentialsNonExpired 为 false



    5. 修改返回值:accountNonLocked 为 false



    五、存在问题

    细心的同学再完成上面功能是会发现,当我们输入的用户名不存在时,不会抛出UserNameNotFoundException,而是抛出 BadCredentialsException这个异常,如果有需要区分 用户名不存在和密码错误的,可参考https://blog.csdn.net/wzl19870309/article/details/70314085

    相关文章

      网友评论

        本文标题:SpringBoot集成SpringSecurity - 异常处

        本文链接:https://www.haomeiwen.com/subject/pzktrctx.html