• day26-过滤器Filter


    Filter过滤器01

    1.Filter过滤器说明

    • 为什么需要过滤器?

      先来看一个例子:

      我们在登录网站页面时,需要先进行登录验证。

      用户访问的正常的流程应该是:

      1. 用户先通过登录页面进行验证,然后才可以访问各种页面。

      2. 为了防止用户绕过登录验证,我们需要在每个页面进行验证, 获取session,验证用户是否登录过。

        image-20221127211237397

        但是上述的方法又会产生下面的问题:

        1. 使用传统方法,每个页面都要进行登录验证
        2. 这将会造成代码的冗余,而且功能是重复的,比较麻烦,维护起来也不方便

        这时候就需要filter过滤器,它可以统一进行验证,比如权限,身份的验证,还可以进行日志记录,事务管理等...

    • 过滤器介绍

    1. Filter过滤器是JavaWeb的三大组件之一(Servlet程序,Listener监听器,Filter过滤器)
    2. Filter过滤器是JavaEE规范,是接口
    3. Filter过滤器的作用是:拦截请求,过滤响应
    4. 应用场景:
      • 权限检查
      • 日志操作
      • 事务管理

    2.过滤器的基本原理

    image-20221127214754209

    上述整个过程如下:

    1. 浏览器请求某个资源,Tomcat接收请求后,先判断是否需要使用过滤器。
    2. 在web.xml文件中配置过滤器,并指定过滤器的url-pattern(规则),tomcat会根据你指定的规则来决定是否需要使用过滤器。
    3. 如果需要,就使用过滤器,过滤器会根据你写的业务需要来进行验证。如果验证成功,就可以继续访问;如果验证失败,就返回业务指定的地方。
    4. 如果不需要过滤器,就直接访问/返回资源。

    3.过滤器快速入门

    需求:在web工程下,有后台管理目录manage,要求该目录下所有资源(html,图片,jsp,servlet)等资源需要用户登录后才能访问。

    3.1思路分析

    image-20221128164826911

    上述流程如下:

    1. 用户访问login.jsp页面填写数据,login.jsp将数据发送到loginCheckServlet验证。
    2. 如果验证成功,servlet创建用户浏览器关联的session并保存。
    3. servlet转发到过滤器,验证还是不是真的登录过(有无session)
      • 如果验证成功,就可以访问资源
      • 如果验证失败,就直接返回登录页面
    4. 如果用户直接访问资源,会先通过过滤器进行验证。
      • 如果没有登陆过,就打回登录页面,不允许未经登录就直接访问资源
      • 如果登录过,可以直接访问资源

    3.2-1代码实现

    1. 首先创建项目,添加web支持,添加需要的jar包

      image-20221128165853747
    2. 完成模块的整体流程

      先完成一个正确完整的流程,再加入其它的功能,完善功能。

      总地来说就是先把框架搭建起来,再完善细节。

      2.1 先完整一个正确的流程-看到一个效果->后面代码就可以验证

      2.2 加入其它功能

      2.3 完善功能

    3. 配置Tomcat


    login.jsp:

    <%--
    Created by IntelliJ IDEA.
    User: li
    Date: 2022/11/28
    Time: 17:03
    Version: 1.0
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    管理后台页面登录

    管理后台页面登录

    "<%=request.getContextPath()%>/loginCheckServlet" method="post">
    u: "text" name="username"/>

    p: "password" name="password"/>

    "submit" value="用户登录"/>

    LoginCheckServlet:

    package com.servlet;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    public class LoginCheckServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    doPost(request, response);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取到用户名和密码(这里应到数据库获取)
    // 这里假设密码是123456就可以通过
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    if ("123456".equals(password)) {
    //合法,将用户名加入session
    request.getSession().setAttribute("username", username);
    //请求转发到admin.jsp
    //根据过滤器原理:请求转发的链接路径是不会被过滤器拦截的,而重定向会被过滤器拦截
    request.getRequestDispatcher("/manage/admin.jsp").forward(request, response);
    } else {
    //非法
    //返回登录页面
    request.getRequestDispatcher("/login.jsp").forward(request, response);
    }
    }
    }

    admin.jsp:

    <%--
    Created by IntelliJ IDEA.
    User: li
    Date: 2022/11/28
    Time: 17:05
    Version: 1.0
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    admin
    <%--指定浏览器解析的目录--%>
    "<%=request.getContextPath()%>/manage/">

    后台管理

    "#">用户列表||"#">添加用户||"#">删除用户

    <%--该图片放在manage目录下--%>
    "myphoto.png" height="300"/>

    ManageFilter:

    package com.filter;
    import javax.servlet.*;
    import java.io.IOException;
    public class ManageFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    //当Tomcat创建filter后,会调用该方法,进行初始化
    System.out.println("ManageFilter init方法被调用...");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    //当每次调用该filter时,doFilter方法就会被调用
    System.out.println("ManageFilter doFilter被调用...");
    }
    @Override
    public void destroy() {
    //当filter对像被销毁时,就会调用该方法
    System.out.println("ManageFilter destroy被调用...");
    }
    }

    在web.xml配置Filter和Servlet:

    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">
    <filter>
    <filter-name>ManageFilterfilter-name>
    <filter-class>com.filter.ManageFilterfilter-class>
    filter>
    <filter-mapping>
    <filter-name>ManageFilterfilter-name>
    <url-pattern>/manage/*url-pattern>
    filter-mapping>
    <servlet>
    <servlet-name>LoginCheckServletservlet-name>
    <servlet-class>com.servlet.LoginCheckServletservlet-class>
    servlet>
    <servlet-mapping>
    <servlet-name>LoginCheckServletservlet-name>
    <url-pattern>/loginCheckServleturl-pattern>
    servlet-mapping>
    web-app>

    redeployTomcat,可以看到后台输出:

    image-20221128175158091

    这说明filter对象是由Tomcat去创建的。我们先来看一下filter底层实现。

    3.3filter底层原理分析

    filter的xml配置和servlet非常相似,filter也是被Tomcat管理和维护的。我们之前在学习servlet的时候知道了Tomcat是如何管理维护servlet的:

    image-20221128180134049

    详见:手动实现Tomcat底层机制+自己设计servlet->day03-实现02->3.4容器设计

    同理,我们也可以这样理解:Tomcat还维护了两个关于filter的容器。一个容器filterURLMapping存放url-pattern和filter-name。另一个容器filterMapping存放filter-name和filter实例。

    image-20221128180817251
    • 一旦Tomcat启动以后:

      1. 把程序员配置的url和filter-name,放到另一容器filterURLMapping中。
      2. 会将filter实例化,把filter-name和filter实例,放到filterMapping容器里面。
    • Tomcat接收到请求时:获取请求中的url,和filterURLMapping容器中的url进行匹配。

      1. 如果匹配上了,在容器filterURLMapping中根据配置的url找到对应的filter-name
      2. 再到另一容器filterMapping中根据filter-name,找到对应filter实例,并调用实例,完成这个filter的doFilter方法。
    • 并且,有了filter机制后,在调用servlet前会先匹配filter

      • 根据request对象封装的uri,先到filterUrlMapping中匹配
      • 如果匹配上了,就走上诉流程,最后调用相应filter对象的doFilter方法
      • 如果没有匹配上,就直接走我们后面的servlet/jsp/html等。(至于是servlet,又会走它那一套机制)

    3.2-2代码实现

    回到之前的程序,我们在浏览器中直接访问manage目录下的资源admin.jsp,可以看到后台输出如下:

    image-20221128185001864 image-20221128185012495

    说明过滤器已经起作用了。

    如果没有显示doFilter被调用,可能是因为浏览器缓存机制,建议在调试台点击禁用缓存。

    现在来完善ManageFilter过滤器里面的业务代码:

    既然请求manage目录下的资源首先要经过过滤器,那么我们可以在过滤器中获取session,如果发现session中已经保存了用户信息(即登录过),就放行;如果没有session信息,就返回登录页面。

    package com.filter;
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    public class ManageFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    //当Tomcat创建filter后,会调用该方法,进行初始化
    System.out.println("ManageFilter init方法被调用...");
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
    FilterChain filterChain) throws IOException, ServletException {
    //当每次调用该filter时,doFilter方法就会被调用
    System.out.println("ManageFilter doFilter被调用...");
    //如果这里没有调用继续请求的方法,就会停止在这
    // 如果继续访问目标资源-->等价于放行
    // 在调用过滤器前,request对象已经被创建并封装起来了
    // 所以我们这里就可以通过servletRequest获取很多信息,比如访问url,session,比如访问的参数...
    // 同时可以做事务管理,数据获取,日志管理等等
    //获取session
    //用户浏览器向服务器发送带有jsessionid的cookie,服务器根据该sid找到对应session
    //这样写后面还可以接续使用httpServletRequest的相关方法
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    HttpSession session = httpServletRequest.getSession();
    //获取username session对象,后面还可以继续使用
    Object username = session.getAttribute("username");
    //如果在对应的session中,没有找到用户的用户名,就说明没有登录过,否则就是登录过
    if (username != null) {
    //用户登录成功过,直接放行
    /**
    * filterChain.doFilter(servletRequest, servletResponse);
    * 1.doFilter代表继续访问目标资源
    * 2.ServletRequest和 ServletResponse对象会传递给目标资源/文件
    * 3.一定要理解filter传递的两个对象,在一次http请求中,
    * 和后面的servlet/jsp中的request,response是同一个对象
    */
    filterChain.doFilter(servletRequest, servletResponse);
    } else {//说明没有登录过,令其返回登录页面
    servletRequest.getRequestDispatcher("/login.jsp")
    .forward(servletRequest, servletResponse);
    }
    /**
    * 关于chain.doFilter()方法:
    * 在doFilter()方法中,在chain.doFilter()之前的代码,一般是对request执行的过滤操作;
    * 在chain.doFilter()后面的代码,一般是对response执行的操作;
    * chain.doFiter()执行下一个过滤器或者业务处理器。
    * 如果在doFilter()方法中,不写chain.doFilter(),业务无法继续往下处理。
    */
    }
    @Override
    public void destroy() {
    //当filter对像被销毁时,就会调用该方法
    System.out.println("ManageFilter destroy被调用...");
    }
    }

    3.4测试

    1. redeployTomcat,在浏览器访问login.jsp,因为login.jsp不在过滤器配置的url下,因此请求不会被拦截。

      image-20221128202613481
    2. 在login.jsp输入正确的用户数据,点击登录,可以看到后台成功转发到了manage目录下的admin.jsp。

      整个过程为:

      1. 用户在login.jsp页面输入数据,login.jsp将表单数据提交到loginCheckServlet中检查

      2. servlet发现数据合法,于是给浏览器返回一个jsessionid,并在服务器内存中创建和此jsessionid关联的session,然后在此session中放入用户数据,最后请求转发到/manage/admin.jsp。

      3. 根据filter原理,请求转发不会经过过滤器。但是在返回admin.jsp后,浏览器需要向服务器请求图片资源,并且,我们的图片资源刚好匹配在filter配置的url。因此关于图片的请求,将会经过过滤器。

        一次请求只能返回一个资源,想要获取图片资源,因此浏览器发出了第二次请求,去获取图片资源。

        image-20221128202907169 image-20221128204108144

      综上所述,在这次过程中,只经过了一次过滤器,后台输出如下:

      image-20221128204843651
    3. 这时,打开浏览器新页面,访问http://localhost:8080/filter/manage/admin.jsp,可以看到访问成功,因为之前已经登录过了。

      image-20221128210847816

    3.5补充说明

    3.5.1关于filterChain.doFilter()

    https://www.jb51.net/article/229734.htm

    filterChain.doFilter(servletRequest, servletResponse);

    chain.doFilter将请求转发给过滤器链下一个filter , 如果没有filter那就是你请求的资源

    在doFilter()方法中,在chain.doFilter()之前的代码,一般是对request执行的过滤操作;在chain.doFilter()后面的代码,一般是对response执行的操作;chain.doFiter()执行下一个过滤器或者业务处理器。如果在doFilter()方法中,不写chain.doFilter(),业务无法继续往下处理;

    1. doFilter代表继续访问目标资源,如servlet,jsp,或是下一个filter

    2. ServletRequest和 ServletResponse对象会传递给目标资源/文件

    3. 在一次http请求中,filter传递的两个对象,和后面的servlet/jsp中的requestresponse是同一个对象

    验证:filter传递的两个对象,在一次http请求中,与其后面的servlet/jsp中的request/response是同一对象

    1. 在ManageFilter中:

      image-20221128213637707
    2. 在admin.jsp中:

      image-20221128213656986

    想法:我们直接访问web应用下的/manage/admin.jsp,因为该资源在filter的url规则下,因此当访问时,会调用过滤器,而过滤器中又将ServletRequest传递给admin.jsp。

    我们同时在filter和admin.jsp中输出request,查看对象的hash值如何。

    后台输出如下:两者的哈希值是一样的。

    image-20221128213551783

    这说明在一个请求里面,过滤器里面的ServletRequest,Servletresponse,和后面目标资源拿到的request,response是同一个对象。是可以共用的。

    3.5.2关于过滤器中的ServletRequest

    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

    我们将过滤器中的ServletRequest强转为HttpServletRequest类型,由于HttpServletRequest中有很多方法,所以我们可以在过滤器中做一些日志记录。

    image-20221128221305241

    4.过滤器url-pattern

    1. url-pattern:Filter的拦截路径,即浏览器在请求什么位置的资源时,过滤器会进行拦截过滤

    2. 精确匹配:/a.jsp

      对应的请求地址:http://ip[域名]:port/工程路径/a.jsp会被拦截

    3. 目录匹配/manage/*

      对应的请求地址:http://ip[域名]:port/工程路径/manage/xxx,即web工程manage目录下所有资源会被拦截

    4. 后缀名匹配*.jsp,后缀名可变,比如*.action*.do*.css等。

      对应的请求地址:http://ip[域名]:port/工程路径/xx.jsp,后缀名为.jsp的请求会被拦截

    5. Filter过滤器只关心它请求的地址是否匹配,不关心请求的资源是否存在。

  • 相关阅读:
    Vue3.2 + Element-Plus 二次封装 el-table(Pro版)
    Linux下gdb调试命令介绍
    第3章-9 字符串转换成十进制整数
    【老师见打系列】:我只是写了一个自动回复讨论的脚本~
    [附源码]Python计算机毕业设计SSM旅游服务平台(程序+LW)
    利用Dockerfile创建指定镜像
    ArcGis打开影像显示全黑解决方法
    解释一下React中的钩子(hooks),例如useState和useEffect。
    性能测试基础
    springboot如何将http请求转换为https请求呢?
  • 原文地址:https://www.cnblogs.com/liyuelian/p/16933833.html