项目亮点之拦截器

#项目中的拦截器:

在springboot中使用拦截器,要implementsHandlerInterceptor接口,实现preHandlepostHandleafterCompletion方法。

preHandle:该方法是进行处理器拦截用的,顾名思义,该方法将在Controller处理之前进行调用,SpringMVC中的Interceptor拦截器是链式的,可以同时存在多个Interceptor,然后SpringMVC会根据声明的前后顺序一个接一个的执行,而且所有的Interceptor中的preHandle方法都会在Controller方法调用之前调用。SpringMVC的这种Interceptor链式结构也是可以进行中断的,这种中断方式是令preHandle的返回值为false,当preHandle的返回值为false的时候整个请求就结束了。

postHandle:这个方法只会在当前这个Interceptor的preHandle方法返回值为true的时候才会执行。postHandle是进行处理器拦截用的,它的执行时间是在处理器进行处理之后,也就是在Controller的方法调用之后执行,但是它会在DispatcherServlet进行视图的渲染之前执行,也就是说在这个方法中你可以对ModelAndView进行操作。这个方法的链式结构跟正常访问的方向是相反的,也就是说先声明的Interceptor拦截器该方法反而会后调用,这跟Struts2里面的拦截器的执行过程有点像,只是Struts2里面的intercept方法中要手动的调用ActionInvocation的invoke方法,Struts2中调用ActionInvocation的invoke方法就是调用下一个Interceptor * 或者是调用action,然后要在Interceptor之前调用的内容都写在调用invoke之前,要在Interceptor之后调用的内容都写在调用invoke方法之后。

afterCompletion:该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。该方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图后, 这个方法的主要作用是用于清理资源的,当然这个方法也只能在当前这个Interceptor的preHandle方法的返回值为true时才会执行。

注册拦截器

springboot中光在Interceptor拦截器类上加@Component注解,只能说明拦截器在IOC容器里供用户使用,但是用户无法直接使用,所以必须注册才能生效,也就是下面重写的addInterceptors()方法。

@Component
//加入拦截器
public class ToutiaoWebConfig extends WebMvcConfigurerAdapter {
    @Autowired
    private PassportInterceptor passportInterceptor;
    @Autowired
    private LoginRequiredInterceptor loginRequiredInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * 错误写法,要体现spring的IOC
         */
        //registry.addInterceptor(new PassportInterceptor());
        /**
         * 正确写法,通过自动配置
         */
        registry.addInterceptor(passportInterceptor);
        registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting*");
    }
}

使用拦截器完成的功能

##页面访问(思想路线)

1.客户端:发送带token的HTTP请求

2.服务端:

  • 根据token获取用户id
  • 根据用户id获取用户的具体信息
  • 检查该用户是否有相关页面访问权限
  • 渲染页面/跳转页面。有,可以继续访问;没有跳转到主页

具体功能:

一、可以知道是哪个用户要登录

在进入controller前,调用preHandle方法处理,它可以检查客户端提交的cookie中是否有服务器之前下发的ticket,如果有证明这个请求是已经登陆的用户了。把登陆的用户放到线程本地变量(ThreadLocal)。在此时进入controller,可以拿到具体的用户HostHolder类,进而可以根据登陆的用户进行个性化渲染,比如关注用户的动态,个人收藏等。

二、可以进行权限管理

比如,在浏览某些页面时,要保证用户登陆,或者用户是某个等级的。可以先在preHandler中判断,这个可以在设置一个特定范围的拦截器,如下的拦截器LoginRequiredInterceptor,在访问setting页面时才会进入到该拦截器,如果验证没有HostHolder,说明用户没登陆,就跳转到主页或者给出登陆页面。拦截器也先后执行顺序。

三、可以自动登陆

我们平时在浏览网页的时候会碰到这样的情况,昨天登陆了某个网站,关机,第二天再登陆,自动跳转到我的用户了。这里是服务器会把浏览器的sessionId和服务器的数据库关联,在提交请求的时候服务器会设置拦截器去找sessionId,如果这个sessionId和我服务器上的ticket关联了起来,并且设置的过期时间没有过期,那在登陆主页前我就可以知道是某个用户,在controller层中可以进行渲染。可以做到自动登陆的功能。

###自动登录(登录验证)

想要达到的目的就是将访问网站的用户所在的线程和ThreadLocal进行绑定,解决了线程安全的问题。

com.nowcoder.interceptor.PassportInterceptor:

@Component
public class PassportInterceptor implements HandlerInterceptor {
    @Autowired
    private LoginTicketDAO loginTicketDAO;
    @Autowired
    private UserDAO userDAO;
    @Autowired
    private HostHolder hostHolder;
  //在preHandle中我们实现了自动登录,检查cookie中的ticket,通过找到的ticket在数据库中查找对应的LoginTicket对象,如果LoginTicket对象有效,然后通过LoginTicket对象中的userid字段,找到user对象
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

        String ticket=null;
        if (httpServletRequest.getCookies()!=null){
          //在cook中找ticket
            for (Cookie cookie:httpServletRequest.getCookies()){
               if (cookie.getName().equals("ticket")){
                   ticket=cookie.getValue();
                   break;
               }
            }
        }
        //cookie可以伪造,所以光查看cookie不够
        //如果客户端有ticket存在,查看与服务器后端的数据库中的值是否相等
        if (ticket!=null){
            LoginTicket loginTicket=loginTicketDAO.selectByTicket(ticket);
            //ticket不相等、超过期限或者状态无效
            if (loginTicket==null||loginTicket.getExpired().before(new Date())||loginTicket.getStatus()!=0)
            {
                return  true;
            }
            //认证过后,将用户信息保存在hostHolder中,方便后面的流程使用
            User user=userDAO.selectById(loginTicket.getUserId());
            hostHolder.setUser(user);
        }
        return true;
    }
         //在controller结束的时候,通常会把结果返回给view视图,在拦截器中postHandle方法做
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        if (modelAndView != null && hostHolder.getUser() != null) {
            modelAndView.addObject("user", hostHolder.getUser());
        }
    }

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

com.nowcoder.model.HostHolder:

/**
 * ThreadLocal的用法
 *
 * 使用ThreadLocal把里面的值共享给所有的类
 */
@Component
public class HostHolder {
    private static ThreadLocal<User> users = new ThreadLocal<User>();

    public User getUser() {
        return users.get();
    }

    public void setUser(User user) {
        users.set(user);
    }

    public void clear() {
        users.remove();;
    }
}

###权限管理

拦截位置放在登陆验证PassportInterceptor的后面,通过登陆验证看HostHolder类中有没有线程本地变量user类,如果有,能确定是谁登陆的。如果没有,那没有权限访问/setting*相关的页面。通过PassportInterceptor的PreHandler()方法后,进入LoginRequiredInterceptor的PreHandler方法,如果没有拿到user类,返回false。不能进入controller层,重定向到首页,并且约定pop=1,弹出登陆框。

新增一个拦截器LoginRequiredInterceptor:

@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
      //歧视就是当前用户没有登录,直接重定向
        if (hostHolder.getUser()==null){
            httpServletResponse.sendRedirect("/?pop=1");////自己定义的,传到前端,假如pop=1,让登录框弹出
            return false;
        }
        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 {

    }
}

当用户证明没有登录,网址会重定向到:http://localhost:8080/?pop=1,重定向到这个url会发生什么呢?往下看

templates/home.html中:

#if ($pop)
<script>
window.loginpop = $!{pop};
</script>
<script type="text/javascript" src="/scripts/main/site/home.js"></script>
#end

将变量pop的值付给了window.loginpop,此时的window.loginpop=1

然后static/scripts/main/site/home.js中:

    function fInitialize() {
        if (window.loginpop > 0) {
            fClickLogin();
        }
    }

    function fClickLogin() {
        var that = this;
        PopupLogin.show({
            listeners: {
                login: function () {
                    //alert('login');
                    window.location.reload();
                },
                register: function () {
                    //alert('reg');
                    window.location.reload();
                }
            }
        });
    }

上面大致的意思就是:当window.loginpop > 0时,执行function fClickLogin(),在项目中就是跳出登录框,如图:

屏幕快照 2019-03-29 下午9.24.17


   转载规则


《项目亮点之拦截器》 xuxinghua 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录
I I