Shiro 认证失败返回自定义 json 数据

编程 / 2023-11-03

问题描述

配置Realm之后,发现在Realm中抛出的异常被无法捕获,最后抛出AuthenticationException异常,返回的是默认异常json格式,无法自定义选择code

原因分析

AuthenticationException 异常时Shiro内部进行抛出的,全局异常捕获器在 Filter 之后执行,不能正常进行补捕获,只能在 Filter内部进行处理。

解决方案

项目内部集成shiro时,自定义Filter继承了BasicHttpAuthenticationFilter重写 onAccessDenied 方法。在onAccessDenied 方法中我们可以返回我们需要的 json 数据,也可以记录日志执行我们自己的方法。这里返回的数据是我自定义的Result 类,里面包含了错误码和错误信息。

认证失败是我们的业务逻辑的错误而不是网络请求的错误,所以我们把HTTP的状态码设置成了 200 ,通过我们自己定义的Result来返回实际的错误信息。

 @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)  {
        try {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            httpResponse.setStatus(200);
            httpResponse.setContentType("application/json;charset=utf-8");
            //解决跨域问题
            if ("OPTIONS".equals(httpRequest.getMethod())) {
                httpResponse.setStatus(HttpServletResponse.SC_NO_CONTENT);
                return true;
            }
            httpResponse.getWriter().flush();
            httpResponse.getWriter().close();
        }catch (IOException e) {
            e.printStackTrace();
        }
        return false;

配置到这没有结束,这里配置了后只是为了让在下面的配置生效。
重写登录认证的时候进行返回结果集的处理,通过ServletResponse 返回统一的结果集 。

 @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            executeLogin(request, response);
            return true;
        } catch (Exception e) {
        /*自定义返回结果集*/
             try {
                if(e.getCause() instanceof BuyPackageException){
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().print(JSON.toJSONString( Result.error(1006,e.getCause().getMessage())));
                }else {
                    response.setContentType("application/json;charset=UTF-8");
                    response.getWriter().print(JSON.toJSONString(Result.error(1005, e.getMessage())));
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return false;
        }
    }

重写了isAccessAllowed和onAccessDenied后返回的显示结果就会如下所示

{
  "code": 1005,
  "exception": "org.apache.shiro.authc.AuthenticationException",
  "message": "前端的租户信息与后台设置的主租户不一致,请重新登录",
  "success": false,
  "timestamp": 1698974976351
}

原理分析

我个人认为是onAccessDenied方法中将所有被拦截的500的Internal Server Error 转成了可通过的200后,isAccessAllowed方法中写入的返回才得以生效,具体的原理未知。

粤ICP备2022112743号 粤公网安备 44010502002407号