文档章节

Spring boot 前后台分离项目 怎么处理spring security 抛出的异常

liululee
 liululee
发布于 2018/05/07 10:33
字数 912
阅读 3811
收藏 21

最近在开发一个项目 前后台分离的 使用 spring boot + spring security + jwt 实现用户登录权限控制等操作。但是 在用户登录的时候,怎么处理spring  security  抛出的异常呢?使用了@RestControllerAdvice 和@ExceptionHandler 不能处理Spring Security抛出的异常,如 UsernameNotFoundException等,我想要友好的给前端返回提示信息  如,用户名不存在之类的。 贴上我的代码:

JWT 验证类 : 重写了spring security UsernamaPasswordAuthenticationFilter

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    private RedisServiceImpl redisService;

    private AppConfig appConfig;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, RedisServiceImpl redisService, AppConfig appConfig) {
        this.authenticationManager = authenticationManager;
        this.redisService = redisService;
        this.appConfig = appConfig;
    }

    /**
     * @param req
     * @param res
     * @return
     * @throws AuthenticationException
     * @// TODO: 2018/4/12 接受并解析用户凭证
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
        try {
            AuthEntity creds = new ObjectMapper()
                    .readValue(req.getInputStream(), AuthEntity.class);

            //验证码校验
            if (appConfig.getCaptchaEnabled()) { //如果开启了验证码登录校验功能
                if (StringUtils.isBlank(creds.getCaptcha())) {
                    logger.error("验证码为空");
                    throw new WelendException(StatusCode.CAPTCHA_EMPTY);
                }
                if (!redisService.exists(appConfig.getCaptchaKey())) {
                    logger.error("验证码已失效");
                    throw new WelendException(StatusCode.CAPTCHA_OVERDUE);
                }
                String captcha = (String) redisService.get(appConfig.getCaptchaKey());
                if (!creds.getCaptcha().equals(captcha)) {
                    logger.error("验证码不正确");
                    throw new WelendException(StatusCode.CAPTCHA_ERROR);
                }
            }
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            logger.error("Client's variables can't be parsed by com.fasterxml.jackson.core.JsonParse");
            throw new WelendException(StatusCode.SERVER_ERROR);
        }

    }
}

验证用户名 密码:

public class CustomAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsServiceImpl userDetailsService;

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public CustomAuthenticationProvider(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 获取认证的用户名 & 密码
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        // 认证逻辑
        JWTUserDetails userDetails = userDetailsService.loadUserByUsername(name);
        if (null != userDetails) {
            Boolean verifyPwd = bCryptPasswordEncoder.matches(password,userDetails.getLoginPwd());
            if (verifyPwd) {
                // 生成令牌 这里令牌里面存入了:userDetails,password,authorities(权限列表)
                Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
                return auth;
            } else {
                throw new BadCredentialsException("username or password wrong!");
            }
        } else {
            throw new UsernameNotFoundException("can not find this account");
        }
    }

    /**
     * 是否可以提供输入类型的认证服务
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * @param request
     * @param exception
     * @return
     * @throws Exception
     * @// TODO: 2018/4/25 参数未通过验证异常
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Object MethodArgumentNotValidHandler(HttpServletRequest request, MethodArgumentNotValidException exception) throws Exception {
        //按需重新封装需要返回的错误信息
        //List<StatusCode> invalidArguments = new ArrayList<>();
        //解析原错误信息,封装后返回,此处返回非法的字段名称,原始值,错误信息
        ResultObject resultMsg = ResultObject.dataMsg(exception.getBindingResult().getFieldError().getDefaultMessage(), StatusCode.VARIABLE_ERROR);
        return resultMsg;
    }

    /**
     * @param request
     * @param exception
     * @return
     * @throws Exception
     * @// TODO: 2018/4/25 无法解析参数异常
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public Object HttpMessageNotReadableHandler(HttpServletRequest request, HttpMessageNotReadableException exception) throws Exception {
        logger.info(exception.getMessage());
        ResultObject resultMsg = ResultObject.dataMsg("参数无法正常解析", StatusCode.VARIABLE_ERROR);
        return resultMsg;
    }

    /**
     * @param exception
     * @return
     * @throws Exception
     * @// TODO: 2018/4/25 处理token 过期异常
     */
    @ExceptionHandler(value = ExpiredJwtException.class)
    public Object ExpiredJwtExceptionHandler(ExpiredJwtException exception) throws Exception {
        logger.info(exception.getMessage());
        ResultObject resultMsg = ResultObject.dataMsg("登录已过期!", StatusCode.FORBIDDEN);
        return resultMsg;
    }

    /**
     * @param request
     * @param exception
     * @return
     * @throws Exception
     * @// TODO: 2018/4/25 方法访问权限不足异常
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public Object AccessDeniedExceptionHandler(AccessDeniedException exception) throws Exception {
        logger.info(exception.getMessage());
        ResultObject resultMsg = ResultObject.dataMsg("权限不足!", StatusCode.FORBIDDEN);
        return resultMsg;
    }

    @ExceptionHandler(value = NoHandlerFoundException.class)
    public Object NoHandlerFoundExceptionHandler(NoHandlerFoundException exception) throws Exception {
        logger.info(exception.getMessage());
        return ResultObject.dataMsg("链接不存在", StatusCode.NOT_FOUND);
    }
    /**
     * 处理自定义异常
     */
    @ExceptionHandler(value = WelendException.class)
    public Object WelendExceptionHandler(WelendException e) {
        ResultObject r = new ResultObject();
        r.setStatus(String.valueOf(e.getCode()));
        r.setMessage(e.getMessage());
        return r;
    }

    @ExceptionHandler(value = AuthenticationException.class)
    public Object AuthenticationExceptionHandler(AuthenticationException e) {
        return ResultObject.dataMsg(e.getLocalizedMessage(),StatusCode.FORBIDDEN);
    }

    @ExceptionHandler(value = DuplicateKeyException.class)
    public Object DuplicateKeyExceptionHandler(DuplicateKeyException e) throws Exception {
        logger.error(e.getMessage(), e);
        return ResultObject.codeMsg(StatusCode.EXISTED);
    }

    @ExceptionHandler(value = BadCredentialsException.class)
    public Object BadCredentialsExceptionHandler(BadCredentialsException e) throws Exception {
        logger.error(e.getMessage(), e);
        return ResultObject.codeMsg(StatusCode.AUTH_ERROR);
    }

    @ExceptionHandler(value = Exception.class)
    public Object ExceptionHandler(Exception e) throws Exception {
        logger.error(e.getMessage(), e);
        return ResultObject.codeMsg(StatusCode.FAILED);
    }
}

登录时输入错误的用户名

控制台直接打印信息了, 并没有经过ExceptionHandler 处理。

如上所示,我想在全局异常类中 处理spring security抛出异常, 以便返回友好的提示信息。有什么解决办法么?

 

© 著作权归作者所有

liululee
粉丝 125
博文 50
码字总数 52592
作品 0
杭州
程序员
私信 提问
加载中

评论(10)

idoz
idoz

引用来自“放手的风筝”的评论

你的异常抛出是在filter里面,filter和controller是两个概念
先走filter-->走完后才走controller就是说RestControllerAdvice根本就执行不到
处理方式模拟
public class TestFilter implements Filter {

  public void destroy() {
    // TODO Auto-generated method stub
    
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    if(1==1) {
      //方式1 直接print到前端,考虑的问题避免后续chain.doFilter()执行
      response.getWriter().write("filter print ok");
      
    }
  }

  public void init(FilterConfig arg0) throws ServletException {
    // TODO Auto-generated method stub
    
  }

}


public class TestFilter implements Filter {

  public void destroy() {
    // TODO Auto-generated method stub
    
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    if(1==1) {
      //方式2 使用filter拦截异常
      throw new RuntimeException("filter error");
      
    }
  }

  public void init(FilterConfig arg0) throws ServletException {
    // TODO A
正解
liululee
liululee 博主
OK, 首先谢谢大家的回答,给了我很多灵感。我的问题可以总结为:在spring security 进行用户验证的时候,想要抛出自定义的异常,并且该异常能通过ExceptionHandler捕获并处理。今天我在JWTAuthenticationFilter 这个方法中,注入了一个exceptionHandlerResovler 。
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

private AuthenticationManager authenticationManager;

private RedisServiceImpl redisService;

private AppConfig appConfig;

private ExceptionHandlerResovler resolver;

public JWTAuthenticationFilter(AuthenticationManager authenticationManager, RedisServiceImpl redisService, AppConfig appConfig, ExceptionHanderResolver resolver) {
this.authenticationManager = authenticationManager;
this.redisService = redisService;
this.appConfig = appConfig;
this.resolver = resolver;
}

然后在下面的验证方法中, 就可以处理自己的异常,resolver.handlerException(request, response, null, new WelendException(StatusCode.CAPTCHA_ERROR));
通过这个调用,就可以用@ExceptionHandler(WelendException.class) 处理异常, 并返回json格式的数据给前端。
william-wang
william-wang
前后端分离的应该把后端的业务错误直接返给前端
竹隐江南
竹隐江南
TOKEN的过滤器,我没有用。用的OncePerRequestFilter ,在里面进行的验证,然后抛出对应的401权限异常。
wad12302
wad12302
还有一个ExceptionHandler 这个说明已经进入到controller层,权限认证哪个是filter层次,还没到controller,所以捕获不到
wad12302
wad12302
@ControllerAdvice
public class GlobalExceptionHandler


  @ExceptionHandler(value = BusException.class)
  public Object busExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) {
    Boolean isAjax = isAjax(request);
    BusException bus = (BusException) e;
    String errorCode = bus.getErrorCode();
    String errorCodeMsg = bus.getErrorMessage();
    if (isAjax) {
      ResultObj<Object> retObj = new ResultObj<Object>();
      retObj.setCode(errorCode);
      retObj.setCodeMsg(errorCodeMsg);
      String jsonStr = JSON.toJSONString(retObj);
      responseJson(response, jsonStr);
      return null;
    } else {
      ModelAndView modelView = new ModelAndView();
      String viewName = "error-all";
      modelView.addObject("errorCode", errorCode);
      modelView.addObject("errorCodeMsg", errorCodeMsg);
      modelView.setViewName(viewName);
      return modelView;
    }
  }
放手的风筝
放手的风筝
@Bean
public FilterRegistrationBean<ExceptionHandlerFilter> filterDemo3Registration() {
FilterRegistrationBean<ExceptionHandlerFilter> registration = new FilterRegistrationBean<ExceptionHandlerFilter>();
registration.setFilter(new ExceptionHandlerFilter());
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}
o
o开源中国
兄弟,你这种处理方法可以吗?有没有源码参考下?
放手的风筝
放手的风筝
}

}



public class ExceptionHandlerFilter implements Filter {

  public void destroy() {
    // TODO Auto-generated method stub
    
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    try {
      chain.doFilter(request, response);
    } catch(Exception e) {
//处理对应异常
      if(e instanceof RuntimeException) {
        response.getWriter().print("ExceptionHandlerFilter print error");
      } else {
        try {
          throw e;
        } catch (Exception e1) {
          e1.printStackTrace();
        }
      }
    }
  }

  public void init(FilterConfig arg0) throws ServletException {
    
  }

}


//配置 ExceptionHandlerFilter的order要排在TestFilter前 TestFilter对应的就是JWTAuthenticationFilter
@Bean
public FilterRegistrationBean<TestFilter> testFilter() {
FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<TestFilter>();
registration.setFilter(new TestFilter());
registration.addUrlPatter
放手的风筝
放手的风筝
你的异常抛出是在filter里面,filter和controller是两个概念
先走filter-->走完后才走controller就是说RestControllerAdvice根本就执行不到
处理方式模拟
public class TestFilter implements Filter {

  public void destroy() {
    // TODO Auto-generated method stub
    
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    if(1==1) {
      //方式1 直接print到前端,考虑的问题避免后续chain.doFilter()执行
      response.getWriter().write("filter print ok");
      
    }
  }

  public void init(FilterConfig arg0) throws ServletException {
    // TODO Auto-generated method stub
    
  }

}


public class TestFilter implements Filter {

  public void destroy() {
    // TODO Auto-generated method stub
    
  }

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    if(1==1) {
      //方式2 使用filter拦截异常
      throw new RuntimeException("filter error");
      
    }
  }

  public void init(FilterConfig arg0) throws ServletException {
    // TODO A
Spring boot 前后台分离项目 怎么处理spring security 抛出的异常

最近在开发一个项目 前后台分离的 使用 spring boot + spring security + jwt 实现用户登录权限控制等操作。但是 在用户登录的时候,怎么处理spring security 抛出的异常呢?使用了@RestCont...

developlee的潇洒人生
2018/05/07
2.1K
3
从源码看Spring Security之采坑笔记(Spring Boot篇)

一:唠嗑 鼓捣了两天的Spring Security,踩了不少坑。如果你在学Spring Security,恰好又是使用的Spring Boot,那么给我点个赞吧!这篇博客将会让你了解Spring Security的各种坑! 阅读前说一...

巅峰小学生
2018/06/23
0
0
springboot + shiro 权限注解、请求乱码解决、统一异常处理

springboot + shiro 权限注解、请求乱码解决、统一异常处理 前篇 后台权限管理系统 相关: spring boot + mybatis + layui + shiro后台权限管理系统 springboot + shiro之登录人数限制、登录...

wyait
2018/06/06
0
0
spring session整合

花了大半天时间,解决了springMVC项目增加spring-session共享session报了异常 前情 项目做了前后端分离,springMVC项目部署在三台tomcat上,前端部署在另三台tomcat上,然后HA做了分发处理,...

jia程序员
2018/05/23
0
0
使用vue集成spring security进行安全登陆

在前后端分离的状态下,传统的spring security认证模式也需要做一点改造,以适应ajax的前端访问模式 现在前后端分离的开发模式已经成为主流,好处不多说了,说说碰到的问题和坑。首先要解决的...

暴走的初号机
05/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

64.监控平台介绍 安装zabbix 忘记admin密码

19.1 Linux监控平台介绍 19.2 zabbix监控介绍 19.3/19.4/19.6 安装zabbix 19.5 忘记Admin密码如何做 19.1 Linux监控平台介绍: 常见开源监控软件 ~1.cacti、nagios、zabbix、smokeping、ope...

oschina130111
今天
13
0
当餐饮遇上大数据,嗯真香!

之前去开了一场会,主题是「餐饮领袖新零售峰会」。认真听完了餐饮前辈和新秀们的分享,觉得获益匪浅,把脑子里的核心纪要整理了一下,今天和大家做一个简单的分享,欢迎感兴趣的小伙伴一起交...

数澜科技
今天
7
0
DNS-over-HTTPS 的下一代是 DNS ON BLOCKCHAIN

本文作者:PETER LAI ,是 Diode 的区块链工程师。在进入软件开发领域之前,他主要是在做工商管理相关工作。Peter Lai 也是一位活跃的开源贡献者。目前,他正在与 Diode 团队一起开发基于区块...

红薯
今天
13
0
CC攻击带来的危害我们该如何防御?

随着网络的发展带给我们很多的便利,但是同时也带给我们一些网站安全问题,网络攻击就是常见的网站安全问题。其中作为站长最常见的就是CC攻击,CC攻击是网络攻击方式的一种,是一种比较常见的...

云漫网络Ruan
今天
12
0
实验分析性专业硕士提纲撰写要点

为什么您需要研究论文的提纲? 首先当您进行研究时,您需要聚集许多信息和想法,研究论文提纲可以较好地组织你的想法, 了解您研究资料的流畅度和程度。确保你写作时不会错过任何重要资料以此...

论文辅导员
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部