后端登录拦截/过滤

记录项目学习过程中的一个知识点——后端登录拦截。
并思考用一种方式——Servlet Filter实现该功能。

注:拦截器@Override的prehandle逻辑,在后期学习过程修改为了shiro验证,本文代码不再修改,详情见Vue + Spring Boot 项目实战(十四):用户认证方案与完善的访问拦截

项目还是那个项目:Vue + Spring Boot 项目实战(一):项目简介
还是第6节Vue + Spring Boot 项目实战(六):前端路由与登录拦截器

文中有讲到一个需求——后端登录拦截,意思就是没有登录的用户,必须要求其登录之后才可以访localhost:8843/blog/index页面。
文中实现的方式使用拦截器,拦截器是什么?拦截器是SpringMVC的一种机制,类似于Servlet的过滤器Filter,主要实现对某个处理方法进行拦截,执行某些操作。有个链接可参考: Spring MVC拦截器(Interceptor )详解

  1. 如何使用拦截器实现登录拦截? (原文:Vue + Spring Boot 项目实战(六):前端路由与登录拦截器的实现方式)
  • 首先自定义一个类实现HandlerInterceptor,比如public class LoginInterceptor implements HandlerInterceptor,然后重写回调函数preHandle。这里的逻辑就是:判断request.getServletPath()是否是否是"/index",如果是,就判断是否登录(session.getAttribute("user") 是否为空),没有登录就重定向到"/login"response.sendRedirect(request.getContextPath() + "/login");
    request_xxPath.jpg
  • 然后自定义类实现WebMvcConfigurer ,重写addInterceptors,把上面的LoginInterceptor注册到拦截器中。
    贴一个我自己写的代码,可以对比着原文代码看看(注意不要忘了注解):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    HttpSession session = request.getSession();
    String servletPath = request.getServletPath();
    if (servletPath.startsWith("/index")) {
    if (session.getAttribute("user") == null) {
    System.out.println("Not Login");
    response.sendRedirect(request.getContextPath() + "/login");
    return false;
    }
    }
    return true;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @SpringBootConfiguration
    public class MyWebConfigurer implements WebMvcConfigurer {

    @Bean
    public LoginInterceptor getLoginInterceptor() {
    return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/index.html");
    }
    }
    下面是原博客中的LoginInterceptor的实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class LoginInterceptor  implements HandlerInterceptor{

    @Override
    public boolean preHandle (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
    HttpSession session = httpServletRequest.getSession();
    String contextPath=session.getServletContext().getContextPath();
    String[] requireAuthPages = new String[]{
    "index",
    };

    String uri = httpServletRequest.getRequestURI();

    uri = StringUtils.remove(uri, contextPath+"/");
    String page = uri;

    if(begingWith(page, requireAuthPages)){
    User user = (User) session.getAttribute("user");
    if(user==null) {
    httpServletResponse.sendRedirect("login");
    return false;
    }
    }
    return true;
    }

    private boolean begingWith(String page, String[] requiredAuthPages) {
    boolean result = false;
    for (String requiredAuthPage : requiredAuthPages) {
    if(StringUtils.startsWith(page, requiredAuthPage)) {
    result = true;
    break;
    }
    }
    return result;
    }
    }
  1. 既然一些博客在介绍SpringMVC的拦截器时说类似Servlet的Filter,那能否用Filter实现呢?
    我做了做实验,应该是可以的。但因为博主还是小白,因此如果写的不对,还请读者指教。
    逻辑和拦截器的代码差不多,就是对所有url先拦截,在判断ServletPath的前缀是不是"/index",是的话就判断用户是否登录,没有登录就重定向到"/login",已经登录就放行。
    直接上代码吧,同样注意不要忘了注解。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @Configuration
    @WebFilter(filterName = "LoginFilter", urlPatterns = {"/*"})
    @Order(value = 1)
    public class LoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    HttpSession session = request.getSession();
    System.out.println("Servlet Path=" + request.getServletPath());
    if (request.getServletPath().startsWith("/index")) {
    User user = (User) session.getAttribute("user");
    if (user == null) {
    System.out.println("Not Login");
    response.sendRedirect(request.getContextPath() + "/login");
    } else {
    //放行
    filterChain.doFilter(request, response);
    }
    } else {
    filterChain.doFilter(request, response);
    }
    }

    @Override
    public void destroy() {

    }
    }
  2. 请仔细理解这两种方式的代码。如果您感觉我写的不对,请批评指正。我能写出来也是“麦芒掉进针眼里——凑巧了”。
    如果您也在学习Vue + Spring Boot 项目实战(六):前端路由与登录拦截器,并且您实践了我说的这两种方式(前提我写对了,嘿嘿),请您继续往下看。
    我说一个小细节吧。使用过滤器Filter可以对"/index""/index.html"都进行过滤,要求登录以后才能访问,但是使用拦截器的方式并不能对"/index.html"进行拦截,即不要求登录就能访问(注:这个index.htmlVue.js单页面开发,直接访问是空白页面,由于不懂前端,我并不知道是否存在危害,或许可以直接访问并不存在危害,只是我感觉不太好)。
    因为MyWebConfigurer·中的·addInterceptor()方法有一句excludePathPatterns("/index.html")。可是为什么要加这一句呢?原博客也有说明原因,是因为未登录状态下,"/index"会重定向到"/login"界面,然后又因为"/login"使用了ErrorConfig(这个请看原博客,属于项目内容),将HTTP 404重定向到了"/index.html""/index.html"的前缀同样是"/index",未登录情况下再次重定向到"/login",然后这就造成了死循环,即重定向过多导致页面访问失败了。
    那为什么Filter不会重定向过多呢?我认为和拦截器、过滤器的机制不同有关,拦截器是属于SpringMVC规范,但过滤器是属于Servlet规范。博客(蛮不错的)《spring boot 过滤器、拦截器的区别与使用》给了几个图,并指出“过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。”
    Interceptor_Filter1.png
    Interceptor_Filter2.png
    Interceptor_Filter3.png
    这说明,过滤器Filter是在进入容器之后就起作用了,但是拦截器Interceptor是在进入了LoginController之前才被调用,并且这个过程应该是在ErrorConfig的后面(我感觉ErrorConfig的重定向应该是属于dispatcher)。这还有个利用System.out.println()输出Spring组件加载顺序的图,
    Error_Interceptor.png
    因此,如果MyWebConfigurer中的addIntercepto不写excludePathPatterns("/index.html"),就会造成这样的重定向死循环顺序:"/index"(未登录) --> "/login" --> "/index.html" --> "/login" --> "/index.html" --> ...,要把"/index.html"排除在外。
    截了个图可以看一下:
    wrongRedirection.png
    当然,加了excludePathPatterns("/index.html"),也就如前面所说,意味着"/index.html"不需要登录了,可以直接访问。

好了,说完了。欢迎批评指正。