美文网首页
拦截器的使用,源码分析

拦截器的使用,源码分析

作者: 猴猴猪027 | 来源:发表于2017-07-05 11:12 被阅读0次

问题

拦截器的使用场景是一个很常见的问题,下面是一些常见企业应用。

场景

拦截器配置

根据拦截器的name来看作用是:

  1. 拦截未登陆用户
  2. 账号安全相关
  3. 权限相关
  4. 防止CSRF攻击
  5. 风控相关

使用

实现HandlerInterceptor接口

HandlerInterceptor接口定义如下:

public interface HandlerInterceptor {

    /**
     * Intercept the execution of a handler. Called after HandlerMapping determined
     * an appropriate handler object, but before HandlerAdapter invokes the handler.
     * <p>DispatcherServlet processes a handler in an execution chain, consisting
     * of any number of interceptors, with the handler itself at the end.
     * With this method, each interceptor can decide to abort the execution chain,
     * typically sending a HTTP error or writing a custom response.
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler chosen handler to execute, for type and/or instance evaluation
     * @return {@code true} if the execution chain should proceed with the
     * next interceptor or the handler itself. Else, DispatcherServlet assumes
     * that this interceptor has already dealt with the response itself.
     * @throws Exception in case of errors
     */
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception;

    /**
     * Intercept the execution of a handler. Called after HandlerAdapter actually
     * invoked the handler, but before the DispatcherServlet renders the view.
     * Can expose additional model objects to the view via the given ModelAndView.
     * <p>DispatcherServlet processes a handler in an execution chain, consisting
     * of any number of interceptors, with the handler itself at the end.
     * With this method, each interceptor can post-process an execution,
     * getting applied in inverse order of the execution chain.
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler handler (or {@link HandlerMethod}) that started async
     * execution, for type and/or instance examination
     * @param modelAndView the {@code ModelAndView} that the handler returned
     * (can also be {@code null})
     * @throws Exception in case of errors
     */
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;

    /**
     * Callback after completion of request processing, that is, after rendering
     * the view. Will be called on any outcome of handler execution, thus allows
     * for proper resource cleanup.
     * <p>Note: Will only be called if this interceptor's {@code preHandle}
     * method has successfully completed and returned {@code true}!
     * <p>As with the {@code postHandle} method, the method will be invoked on each
     * interceptor in the chain in reverse order, so the first interceptor will be
     * the last to be invoked.
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler handler (or {@link HandlerMethod}) that started async
     * execution, for type and/or instance examination
     * @param ex exception thrown on handler execution, if any
     * @throws Exception in case of errors
     */
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}

在调用下一个拦截器之前会先执行preHandle方法,从下一个拦截器返回之后会执行postHandle方法,最后会执行afterCompletion方法。我么只需要重写preHandler和postHandler方法。

xml文件配置

如上上图所示,将自己定义的实现了HandlerInterceptor方法的拦截器配置到"Interceptors"标签中即可。

拦截器场景分析

登陆拦截
public class LoginInterceptor implements HandlerInterceptor {

    private static final Logger logger= LoggerFactory.getLogger(LoginInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        User user = ContextUtil.getCurrentUser();
        if (user == null) {
            redirectLogin(request, response);
            return false;
        }
        if(user.getUserType() == UserTypeEnum.SHOP.getCode()){
            SSOHelper.logoutV2(request, response);
            redirectLogin(request, response);
            return false;
        }
        logger.info("[LoginInterceptor - preHandle ] requestUrl:{} currentUser: {}  ",request.getRequestURI(),hipacUser
                .getUserNickName());
        LoginUtil.setCurrentUser(user);
        return true;
    }

    private void redirectLogin(HttpServletRequest request, HttpServletResponse response) throws IOException,
                                                                                       ServletException {
        if (ContextUtil.isAjax()) {
            // ajax请求
            PrintWriter out = response.getWriter();
            response.setStatus(403);
            out.print("_NO_LOGIN_SESSION");
            out.flush();
            return;
        } else {
            try {
                StringBuilder sb=new StringBuilder(request.getRequestURI());
                sb.append("?");
                Enumeration<String> names = request.getParameterNames();
                while (names.hasMoreElements()){
                    String key = names.nextElement();
                    sb.append(key).append("=").append(request.getParameter(key)).append("&");
                }
                response.sendRedirect(request.getContextPath() + "/admin/toIndex.do");
            }catch (Exception e){
                logger.error("[LoginInterceptor - redirectLogin ] fail ",e);
                response.sendRedirect(request.getContextPath() + "/admin/toIndex.do");
            }
            return;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                                                                                                                       throws Exception {
        LoginUtil.removeCurrentUser();
    }
}

可以看到以上拦截器的作用的检查用户是否存在,如果不存在会重定向到登陆页面。
其中获取用户信息的方法如下:

    /**
     * pc端查询登录用户信息
     *
     * @return
     */
    public static User getCurrentUser() {
        String userId = getCurrentUserId();
        if (StringUtils.isBlank(userId)) {
            return null;
        }
        UserService userService = ApplicationContextUtil.getBean(UserService.class);
        return userService.getUserById(userId);
    }
账号安全拦截器

如果用户被打标成登陆密码过于简单,会引导用户去修改密码。
实现如下:

public class AccountSecurityInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(AccountSecurityInterceptor.class);

    @Autowired
    private TagQueryApi         tagQueryApi;

    @Autowired
    private UserQueryApi        userQueryApi;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        String userId = ContextUtil.getCurrentUser().getId();
        ResultData<UserTO> userTOResultData = userQueryApi.getUserInfoById(userId);
        if(null == userTOResultData || null == userTOResultData.getData()){
            logger.error("[LoginInterceptor - redirectSecurity]用户信息获取失败.");
            return true;
        }
        ResultData<TTagValueTO> resultDate = tagQueryApi.queryTag(userId, WebConstants.USER_IS_UPDATE_PASS);
        if (null == resultDate || !resultDate.isSuccess()) {
            logger.error("[LoginInterceptor - redirectSecurity]获取用户打标接口失败.");
            return true;
        }
        //if (null != resultDate && null != resultDate.getData()) {
        if (null == resultDate.getData()) {
            if (ContextUtil.isAjax()) {
                // ajax请求
                PrintWriter out = response.getWriter();
                response.setStatus(403);
                out.print("LOW_ACCOUNT_SECURITY");
                out.flush();
            } else {
                response.sendRedirect(request.getContextPath() + "/admin/toAccountSecurity.do");
            }
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {
    }
CSRF防御拦截器

对list之外的请求进行过滤,防止CSRF攻击。

public class CSRFInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(CSRFInterceptor.class);

    public static final List<String> list = Lists.newArrayList(
                                                "http://","http://");
    // 顶级域名
    public static final String DOMAIN = "";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 验证 Referer来源
        String referer = request.getHeader("Referer");
        String domain="http://";
        String httpsDomain = "https://";
        if (StringUtils.isNotBlank(domain) && StringUtils.isNotBlank(referer)  && !(referer.startsWith(domain) || referer.startsWith(httpsDomain))){
            String host = getHost(referer);
            if(StringUtils.isNotBlank(host) && host.endsWith(DOMAIN)){
                return true;
            }
            for (String s : list) {
                if (referer.startsWith(s)){
                    return true;
                }
            }
            throw new Exception(302,"不可信的网站来源!referer="+referer+",handler="+handler);
        }
        return true;
    }
    private String getHost(String url){
        try {
            URL u = new URL(url);
            String host = u.getHost();
            host.replaceAll("/","");
            return u.getHost();
        } catch (MalformedURLException e) {
            return null;
        }
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

源码分析

springMVC中的核心就是DispatchServlet类,该类的核心又是doDispatch方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
//这里先调用preHandle方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(request, mv);
//这里调用posthandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
//触发执行afterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

总结

正如拦截器的名字,应用到的主要功能是对不合法,或者没有权限,或者不安全的账号进行过滤或者引导。下面会单独写一篇文章介绍接口权限的实现。

谢谢阅读!禁止转载。

相关文章

网友评论

      本文标题:拦截器的使用,源码分析

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