springboot shiro filter 异常解决

631 阅读4分钟

项目springboot + shiro 。

其中shiro 缓存采用redis处理。

有一个对外提供服务的接口,可以直接请求(不需要登录)。

问题: 如果redis故障,那么客户请求接口,收到了应用内部的redis异常。

描述: 1. 客户请求并没有到达请求接口。 在shiro filter 处理的过程中,因为使用 sessionId 请求 redis 直接抛出异常。

要求: 产品要求这种情况不能将应用内部的异常返回给客户,统一异常回复。

诸位大神,怎么解?

  1. 不清楚在哪里接收这些异常。
  2. 要区分开 对外接口的请求异常, 还有 正常的 WEB 页面请求异常。

解决结果:

shiro filter 处理中,需要操作session 的查询 更新等,使用redis 处理,直接抛异常,抛给了 tomcat 服务。tomcat 把异常信息写给了 response 。

\

另外:

请求进来 会按照 filter -> interceptor -> controllerAdvice -> aspect -> controller的顺序调用\

当controller返回异常 也会按照controller -> aspect -> controllerAdvice -> interceptor -> filter来依次抛出\

\

  • 这种Filter发生的404、405、500错误都会到Spring默认的异常处理。  

  • 如果你在配置文件配置了server.error.path的话,就会使用你配置的异常处理地址,

 * 如果没有就会使用你配置的error.path路径地址, 

 * 如果还是没有,默认使用/error来作为发生异常的处理地址。  

  • 如果想要替换默认的非Controller异常处理直接实现Spring提供的ErrorController接口就行了\

\

但是我收到 filter 异常的时候,无法获取真正的 url , 就无法针对性的给出响应结果。

\

后来:看到 shiro filter 中,主要处理流程逻辑在 AbstractShiroFilter  的 doFilterInternal 中。

随后尝试覆盖这个方法。发现行不通。 AbstractShiroFilter 是 指定在  ShiroFilterFactoryBean 中创建的,无法覆盖。 

\

于是 想到 重写 ShiroFilterFactoryBean 。 在重写的类里面,指定 覆盖 AbstractShiroFilter 的过滤器。然后 catch 异常。

\

如此 测试通过。

\

附代码:

\

ShiroConfig 中: 只是由原来指定的 ShiroFilterFactoryBean  ,换成自己的实现类。\

/** * Shiro过滤器配置 */ 

 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { AxShiroFilterFactoryBean shiroFilterFactoryBean = new AxShiroFilterFactoryBean();\

.......

}

\

AxShiroFilterFactoryBean:\

package com.ruoyi.framework.shiro.filter; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.mgt.FilterChainManager; import org.apache.shiro.web.filter.mgt.FilterChainResolver; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.mgt.WebSecurityManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanInitializationException; /** * 重写 ShiroFilterFactoryBean ,因为里面 指定使用了 AbstractShiroFilter 处理shiro 的核心请求流程 *

  * 同时 AbstractShiroFilter 的路程里面 不catch 异常,直接抛给 ApplicationFilterChain 中。这个类是 tomcat 服务的 * * @author zanqinglong * @version 1.0 * @since 1.0 * Created by zanqinglong on 2022/3/25. */ public class AxShiroFilterFactoryBean extends ShiroFilterFactoryBean { private static transient final Logger log = LoggerFactory.getLogger(AxShiroFilterFactoryBean.class); private AxAbstractShiroFilter instance; public Class getObjectType() { return AxSpringShiroFilter.class; } public Object getObject() throws Exception { if (instance == null) { instance = createInstance(); } return instance; } protected AxAbstractShiroFilter createInstance() throws Exception { log.debug("Creating Shiro Filter instance."); SecurityManager securityManager = getSecurityManager(); if (securityManager == null) { String msg = "SecurityManager property must be set."; throw new BeanInitializationException(msg); } if (!(securityManager instanceof WebSecurityManager)) { String msg = "The security manager does not implement the WebSecurityManager interface."; throw new BeanInitializationException(msg); } FilterChainManager manager = createFilterChainManager(); //Expose the constructed FilterChainManager by first wrapping it in a // FilterChainResolver implementation. The AbstractShiroFilter implementations // do not know about FilterChainManagers - only resolvers: PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver(); chainResolver.setFilterChainManager(manager); //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built //FilterChainResolver. It doesn't matter that the instance is an anonymous inner class //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts //injection of the SecurityManager and FilterChainResolver: return new AxSpringShiroFilter((WebSecurityManager) securityManager, chainResolver); } private static final class AxSpringShiroFilter extends AxAbstractShiroFilter { protected AxSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) { super(); if (webSecurityManager == null) { throw new IllegalArgumentException("WebSecurityManager property cannot be null."); } setSecurityManager(webSecurityManager); if (resolver != null) { setFilterChainResolver(resolver); } } } } \

\

AxAbstractShiroFilter :这里收集异常,根据url 抛出指定类型的异常。\

package com.ruoyi.framework.shiro.filter; import com.ruoyi.framework.web.exception.AxSmsSendApiShiroException; import lombok.SneakyThrows; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; /** * 覆盖原生的 AbstractShiroFilter 类 *

* 着重在 doFilterInternal catch 异常 * * @author zanqinglong * @version 1.0 * @since 1.0 * Created by zanqinglong on 2022/3/25. */ public class AxAbstractShiroFilter extends AbstractShiroFilter { private static transient final Logger log = LoggerFactory.getLogger(AxAbstractShiroFilter.class); public AxAbstractShiroFilter() { super(); } @SneakyThrows protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { try { super.doFilterInternal(servletRequest, servletResponse, chain); } catch (Exception e) { // /sms/api/send String url = WebUtils.getPathWithinApplication(WebUtils.toHttp(servletRequest)); log.error("Shiro filter 异常!", e); if (url.startsWith("/sms/api")) { throw new AxSmsSendApiShiroException(url); } throw e; } } } \

\

MyErrorController:这里抛出后,相当于走进了controller 容器了。就可以被 @RestControllerAdvice 的全局处理异常抓到了。\

package com.ruoyi.framework.web.exception; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; /** * 这里是接收 filter 异常的。 * 请求进来 会按照 filter -> interceptor -> controllerAdvice -> aspect -> controller的顺序调用 *

* 当controller返回异常 也会按照controller -> aspect -> controllerAdvice -> interceptor -> filter来依次抛出 *

* 这种Filter发生的404、405、500错误都会到Spring默认的异常处理。 * 如果你在配置文件配置了server.error.path的话,就会使用你配置的异常处理地址, * 如果没有就会使用你配置的error.path路径地址, * 如果还是没有,默认使用/error来作为发生异常的处理地址。 * 如果想要替换默认的非Controller异常处理直接实现Spring提供的ErrorController接口就行了 * * @author zanqinglong * @version 1.0 * @since 1.0 * Created by zanqinglong on 2022/3/24. */ @RestController public class MyErrorController implements ErrorController { @Override public String getErrorPath() { return "/error"; } @RequestMapping("/error") public void handleError(HttpServletRequest request) throws Throwable { if (request.getAttribute("javax.servlet.error.exception") != null) { throw (Throwable) request.getAttribute("javax.servlet.error.exception"); } } } \

\

GlobalExceptionHandler : 全局处理异常\

\

/** * 系统异常 */ @ExceptionHandler(Exception.class) public Object handleException(Exception e) { log.error(e.getMessage(), e); if (e.getCause() instanceof AxSmsSendApiShiroException) { return new AxSmsApiResultPo("-1000", "服务器错误,请联系管理员"); } else { if (CheckUtils.isNotEmpty(e.getMessage())) { return AjaxResult.error(e.getMessage()); } } return AjaxResult.error("服务器错误,请联系管理员"); }\

\

AxSmsSendApiShiroException: 指定的异常\

package com.ruoyi.framework.web.exception; /** * 自定义一个异常,专门面对 访问 开放api接口 的时候 * * @author zanqinglong * @version 1.0 * @since 1.0 * Created by zanqinglong on 2022/3/25. */ public class AxSmsSendApiShiroException extends Exception { private static final long serialVersionUID = -4537905131652108724L; public AxSmsSendApiShiroException() { super(); } public AxSmsSendApiShiroException(String msg) { super(msg); } } \

\

\