tomcat & spring-ecurity异常处理流程

tomcat正常请求过程

首先回顾下一个http请求在tomcat中的处理过程:

流程图
在这里插入图片描述

框架准备

为了分析异常处理的过程, 这里自定义一个filter,并主动抛出异常;
自定义Filter

public class HttpMethodAllowedFilter extends OncePerRequestFilter {
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		if(1 < 2) {
			throw new ServiceException("抛出异常");
		}
		filterChain.doFilter(request, response);
	}
}

配置Filter

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
	//略....
	httpSecurity.addFilterBefore(new HttpMethodAllowedFilter(), ChannelProcessingFilter.class);
}

tomcat异常处理

异常处理流程图
首先将异常处理的流程画出来,然后逐步分析代码的执行过程。
**tomcat请求过程**
接下来分析下错误流程处理代码:

StandardEngineValve

//org.apache.catalina.core.StandardEngineValve
public final void invoke(Request request, Response response)
    throws IOException, ServletException {
    Host host = request.getHost();

    //check host,略......

    // ErrorReportValve -> StandardHostValve
    host.getPipeline().getFirst().invoke(request, response);

}

StandardHostValve
异常主要是在StandardHostValve中处理的:

//org.apache.catalina.core.StandardHostValve
 public final void invoke(Request request, Response response)
    throws IOException, ServletException {
	
	//使用此Context来处理请求
    Context context = request.getContext();
    
    
    // NonLoginAuthenticator -> StandardContextValve
    context.getPipeline().getFirst().invoke(request, response);
       
    //获取request中ERROR_EXCEPTION属性,来判断是否在请求过程中存在异常.
    Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

    //略....

    //异常处理。
    throwable(request, response, t);
}

 protected void throwable(Request request, Response response,
                             Throwable throwable) {
    Context context = request.getContext();
    if (context == null) {
        return;
    }

    Throwable realError = throwable;

    //针对ServletException,ClientAbortException 的特殊处理....(略)
  
  	//寻找errorPage
    ErrorPage errorPage = findErrorPage(context, throwable);
    if (errorPage != null) {
        //如果配置了errorPage,则做相应的处理(略...)
    } else {
        // 未找到请求处理期间抛出的异常对应的error-page
        // 查看是否存在500错误页面,如果存在指定为响应返回;
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        response.setError();

        status(request, response);
    }
}

private void status(Request request, Response response) {
    int statusCode = response.getStatus();

    Context context = request.getContext();
    if (!response.isError()) {
        return;
    }

    //寻找有没有配置errorPage
    ErrorPage errorPage = context.findErrorPage(statusCode);

    //如果没有,则设置默认的page: "/error"
    if (errorPage == null) {
        // errorPage = "/error"
        errorPage = context.findErrorPage(0);
    }

    if (errorPage != null && response.isErrorReportRequired()) {
        response.setAppCommitted(false);
        request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
                          Integer.valueOf(statusCode));

        String message = response.getMessage();
        if (message == null) {
            message = "";
        }
        request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
                errorPage.getLocation());
        //设置dispatchType=="ERROR",匹配filter时只匹配“ERROR”类型的Filter
        request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
                DispatcherType.ERROR);

        if (custom(request, response, errorPage)) {
            response.setErrorReported();
           response.finishResponse();
        }
    }
}

private boolean custom(Request request, Response response,
                             ErrorPage errorPage) {
    ServletContext servletContext =
        request.getContext().getServletContext();
    RequestDispatcher rd =
        servletContext.getRequestDispatcher(errorPage.getLocation());

    if (response.isCommitted()) {
        rd.include(request.getRequest(), response.getResponse());
    } else {
        response.resetBuffer(true);
        response.setContentLength(-1);

        //org.apache.catalina.core.ApplicationDispatcher,执行跳转命令
        //执行跳转“/error”的请求;
        rd.forward(request.getRequest(), response.getResponse());

        response.setSuspended(false);
    }
    return true;
}

ApplicationDispatcher
到出现异常时,会调用ApplicationDispatcher.forward(“error”),方法执行调用过程为:forward -> doForward -> processRequest ->invoke

  private void invoke(ServletRequest request, ServletResponse response,
            State state) throws IOException, ServletException {

    	//略....

        // 获取filterChian,当出现错误时即forword("/error")时,
        // 获取的filter的dispatchType为“ERROR”
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        try {
           filterChain.doFilter(request, response);
        } //catch 其他异常(略.....)
         catch (RuntimeException e) {
            runtimeException = e;
        }

        // 抛出捕获的异常;  与后续的StandardWrapperValve(吃掉异常)不同
        if (ioException != null)
            throw ioException;
        if (servletException != null)
            throw servletException;
        if (runtimeException != null)
            throw runtimeException;
    }

当抛出异常forward("/error")ApplicationFilterChain内容如下:
在这里插入图片描述

StandardContextValve

public final void invoke(Request request, Response response)
    throws IOException, ServletException {

    // 禁止任何请求直接访问: WEB-INF or META-INF 下的资源;
    MessageBytes requestPathMB = request.getRequestPathMB();
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/META-INF"))
            || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
            || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Select the  to be used for this Request
    Wrapper wrapper = request.getWrapper();
    if (wrapper == null || wrapper.isUnavailable()) {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

  	//略.....
    wrapper.getPipeline().getFirst().invoke(request, response);
}

StandardWrapperValve

//org.apache.catalina.core.StandardWrapperValve
public final void invoke(Request request, Response response)
throws IOException, ServletException {

	//1.创建filterChain
	ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);


    //2. filterChain类型为: ApplicationFilterChain
    try{
   	 	filterChain.doFilter(request.getRequest(), response.getResponse());
                            (request.getRequest(), response.getResponse());
      // catch{} catch各种异常(略...)
      } catch (Throwable e) {
        ExceptionUtils.handleThrowable(e);
        //log异常(略....)
        
        throwable = e;
         // 注意!!! 这里不再向上抛出异常,
         // 而是将异常信息,写到request的attribute中;
        exception(request, response, e);
    }

    //释放filterchain
    if (filterChain != null) {
        filterChain.release();
    }

    //释放servlet
  	if (servlet != null) {
        wrapper.deallocate(servlet);
    }
}

ApplicationFilterFactory & ApplicationFilterChain

//org.apache.catalina.core.ApplicationFilterFactory
//1.
	public static ApplicationFilterChain createFilterChain(ServletRequest request,
        Wrapper wrapper, Servlet servlet) {


	ApplicationFilterChain filterChain =  new ApplicationFilterChain();
	//设置servlet
	filterChain.setServlet(servlet);
    filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

    // Acquire the filter mappings for this Context
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    //1.1  for-each filterMaps,匹配req.getRequestPath()的 filter
    for (int i = 0; i < filterMaps.length; i++) {
         if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
             continue;
         }
         if (!matchFiltersURL(filterMaps[i], requestPath))
             continue;
         ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
             context.findFilterConfig(filterMaps[i].getFilterName());
         if (filterConfig == null) {
             // FIXME - log configuration problem
             continue;
         }
         filterChain.addFilter(filterConfig);
     }
    //1.2  for-each filterMaps,匹配 servletName
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
             continue;
         }
         if (!matchFiltersServlet(filterMaps[i], servletName))
             continue;
         ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
             context.findFilterConfig(filterMaps[i].getFilterName());
         if (filterConfig == null) {
             // FIXME - log configuration problem
             continue;
         }
         filterChain.addFilter(filterConfig);
     }

}

//org.apache.catalina.core.ApplicationFilterChain
//2.
public final class ApplicationFilterChain implements FilterChain {

	private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

	//filterChain正在执行的filter的位置
	private int pos = 0;

	//filterchain中filter的个数
	private int n = 0;


    private Servlet servlet = null;


	public void doFilter(ServletRequest request, ServletResponse response)
	    throws IOException, ServletException {
	 	internalDoFilter(request,response);
	}

	private void internalDoFilter(ServletRequest request,
	                                  ServletResponse response)
	        throws IOException, ServletException {

        // 如果hasNext,则执行if{},调用filter.doFilter
        // 执行到 n-1 ,当pos = n时,return; 继续往下执行
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
             try {
                Filter filter = filterConfig.getFilter();

                filter.doFilter(request, response, this);
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // 当filterChain中 filter全部执行完毕后,执行servlet.service();
        servlet.service(request, response);
    }
}

此时正常请求时ApplicationFilterChain内容如下:
在这里插入图片描述

为什么处理异常时前后创建的ApplicationFilterChain,内容不一致
DispatcherType
每一个request都有DispatcherType类型,它是枚举类型

public enum DispatcherType {
    FORWARD,
    INCLUDE,
    REQUEST,
    ASYNC,
    ERROR
}

而每个Filter也有与之对应的类型:

    public class FilterMap extends XmlEncodingBase implements Serializable 
	    public static final int ERROR = 1;
	    public static final int FORWARD = 2;
	    public static final int INCLUDE = 4;
	    public static final int REQUEST = 8;
	    public static final int ASYNC = 16;
	}

在对servlet请求匹配关联Filter时,不仅需要匹配:URL,servletName更需要匹配DispatcherType.

FilterChainProxy

接上文中的FilterChainProxy的组成如下图:
在这里插入图片描述,我们都知道,SpringSecurity的异常都是交由ExceptionTranslationFilter来处理的:
ExceptionTranslationFilter

public class ExceptionTranslationFilter extends GenericFilterBean {
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
			//nextFilter为 FilterSecurityInterceptor
			//FilterSecurityInterceptor来判断是否通过认证和是否拥有权限;
			//若未通过则抛出对应的AuthenticationException(401),AccessDeniedException(403)
			chain.doFilter(request, response);
		}catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			//略 if()else{}判断....
			handleSpringSecurityException(request, response, chain, ase);
		}
	}


	private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
		if (exception instanceof AuthenticationException) {
			//调用authenticationEntryPoint.commence(request, response, reason);
			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				//调用authenticationEntryPoint.commence(request, response, reason);
				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				//调用accessDeniedHandler.handle()
				accessDeniedHandler.handle(request, response,
						(AccessDeniedException) exception);
			}
		}
	}

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值