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

拦截器的使用,源码分析

作者: 猴猴猪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