SpringMVC(十三):HandlerInterceptor拦截器详解

star2017 1年前 ⋅ 410 阅读

  拦截器是 Web 项目很重要和常用的功能,如对用户权限验证,判断用户是否已登录等。

  SpringMVC 中的拦截器通过实现HanderInterceptor接口来完成,或继承抽象类HandlerInterceptorAdapter,重写里面的方法来完成。

HanderInterceptor

  HanderInterceptor 接口中定义了三个方法,SpringMVC 通过这三个方法来对用户请求进行拦截处理。

  1. boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

    本方法在请求处理之前执行,返回值是 Boolean 类型,返回值为 false 时,表示请求结束;返回 true 时就会继承调用下一个 interceptorpreHandle() 方法,如果是最后一个 Interceptor,就调用当前请求的 Controller 方法。SpringMVC 中的 Interceptor 是链式调用,即在一个应用中或一个请求中可以同时存在多个Interceptor, 调用的的顺序是根据声明的顺序依次执行;所以可以在该方法中对一些请求做一个预处理,或初始化操作。

    handler 参数是目标处理方法,是 HandlerMethod 类型,即处理请求的具体方法。可以通过该参数获取目标方法对象,再根据目标方法对象获取方法上的注解,再根据注解进行业务处理。

    HandlerMethod handler1 = (HandlerMethod) handler;
    Method method1 = handler1.getMethod();
    RequestMapping methodAnnotation = handler1.getMethodAnnotation(RequestMapping.class);
    if (null != methodAnnotation) {
        System.out.println("-----------------");
    }
    
  2. void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    preHandle()方法返回true;,请求被处理之后,也就是 Controller 方法被调用之后执行,但是在DispatcherServlet 进行视图返回渲染之前被调用。

    多个 Interceptor 时,该方法的调用顺序与 preHandle() 相反。

  3. void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
    preHandle() 方法返回true,整个请求结束之后,DispatcherServlet 进行视图返回渲染之后执行。该方法主要作用是进行资源清理。

  4. 接口源码

    public interface HandlerInterceptor {
    
        /**
         * 拦截处理执行器
         * 在 HandlerMapping 确定适当的处理程序对象之后,但在 HandlerAdapter 调用处理程序之前。
         */
        default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
    
            return true;
        }
    
        /**
         * 实际上在HandlerAdapter之后调用执行,但在 DispatcherServlet 呈现视图之前。
         * 可以通过给定的ModelAndView将其他模型对象公开给视图。
         */
        default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                @Nullable ModelAndView modelAndView) throws Exception {
        }
    
        /**
         * 完成请求处理后(即渲染视图之后)的回调。 
         * 将在处理程序执行的任何结果上被调用,从而允许适当的资源清理。
         * 该方法将在每个链中的拦截器的顺序相反,因此第一个拦截器将是最后被调用。
         */
        default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                @Nullable Exception ex) throws Exception {
        }
    }
    

拦截器链调用顺序

实现示例

登录认证拦截器

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.anqi.entity.User;

/**
 * 登录拦截器 登录login请求放行; 其它请求则判断是否已登录,未登录则跳转到登录界面
 * 
 * @author Rocky
 *
 */
public class LoginInterceptor implements HandlerInterceptor {

    // 以下URI放行不拦截
    private static final String[] IGNORE_URI = { "/loginForm", "/login", "/register" };

    /**
     * preHandle方法是进行拦截处理,在Controller处理之前调用 该方法返回true时才会继续往下执行,返回false则整个请求结束。
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) throws Exception {
        //默认用户没有登录
        boolean flag = false;        
        //获取ServletPath
        String servletPath = request.getServletPath();
        //判断是否有需要放行的请求
        for (String uri : IGNORE_URI) {
            if (servletPath.contains(uri)) {
                flag = true;
                break;
            }
        }

        if (!flag) {
            // 1. 获取session中的用户
            HttpSession session = request.getSession();
            User user = (User) session.getAttribute("user");
            // 2. 判断用户是否已登录
            if (user == null || "".equals(user.toString())) {
                request.setAttribute("msg", "请先登录");
                request.getRequestDispatcher("loginForm").forward(request, response);

                // response.sendRedirect("/admin/userLogin");
                // resp.sendRedirect(req.getSession().getServletContext().getContextPath()+LOGIN_URL);
                // req.getRequestDispatcher("/WEB-INF/jsp/admin-login.jsp").forward(req, resp);

                return false;
            } else {
                flag = true;
            }
        }
        return flag;
    }


    /**
     * 该方法将在Controller方法调用之后执行,方法中可对ModelView进行操作
     * 也只能在当前Interceptor的preHandle方法返回true时才会执行
     */
    @Override
    public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object obj, ModelAndView exception)
            throws Exception {
        // TODO Auto-generated method stub

    }

    /**
     * 该方法将在整个请求完成之后执行,主要作用是用于清理资源 也只能在当前Interceptor的preHandle方法返回true时才会执行
     */
    @Override
    public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object obj, Exception exception)
            throws Exception {
        // TODO Auto-generated method stub

    }


}

登录Token认证拦截器

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    UserService userService;
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
        // 如果不是映射到方法直接通过
        if(!(object instanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod=(HandlerMethod)object;
        Method method=handlerMethod.getMethod();
        //检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if (userLoginToken.required()) {
                // 执行认证
                if (token == null) {
                    throw new RuntimeException("无token,请重新登录");
                }
                // 获取 token 中的 user id
                String userId;
                try {
                    userId = JWT.decode(token).getAudience().get(0);
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("401");
                }
                User user = userService.findUserById(userId);
                if (user == null) {
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                // 验证 token
                JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try {
                    jwtVerifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("401");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, 
                                  HttpServletResponse httpServletResponse, 
                            Object o, ModelAndView modelAndView) throws Exception {

    }
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, 
                                          HttpServletResponse httpServletResponse, 
                                          Object o, Exception e) throws Exception {
    }

装配拦截器

拦截/admin/开头的所有请求。

<!-- 配置sprignmvc 拦截器 -->
<mvc:interceptors>
    <!-- 多个拦截器,顺序执行 -->
    <mvc:interceptor>
        <mvc:mapping path="/admin/**" />
        <bean class="com.interceptor.LoginInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>
更多内容请访问:IT源点

全部评论: 0

    我有话说: