0


理解Web登录机制:会话管理与跟踪技术解析(三)-过滤器Filter

在Java Web应用中,Filter(过滤器)是实现登录校验的常见方式。通过Filter,我们能够在请求到达实际的业务逻辑之前,对其进行拦截和处理,从而完成身份校验、权限验证等操作。本文将深入探讨登录校验的实现方法,并详细讲解如何利用Filter来实现高效、灵活的登录校验机制。


前言

在Java Web应用中,Filter(过滤器)是实现登录校验的常见方式。通过Filter,我们能够在请求到达实际的业务逻辑之前,对其进行拦截和处理,从而完成身份校验、权限验证等操作。本文将深入探讨登录校验的实现方法,并详细讲解如何利用Filter来实现高效、灵活的登录校验机制。


提示:以下是本篇文章正文内容,下面案例可供参考

过滤器Filter

通过浏览器的开发者工具,我们可以看到在后续的请求当中,都会在请求头中携带JWT令牌到服务端,而服务端需要统一拦截所有的请求,从而判断是否携带的有合法的JWT令牌。
那怎么样来统一拦截到所有的请求校验令牌的有效性呢?这里我们会学习两种解决方案:

  1. Filter过滤器
  2. Interceptor拦截器

快速入门

什么是Filter?
Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能

  • 使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后,才可以访问对应的资源。

过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

下面我们通过Filter快速入门程序掌握过滤器的基本使用操作:

  • 第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
  • 第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加@ServletComponentScan 开启Servlet组件支持。

定义过滤器

//定义一个类,实现一个标准的Filter过滤器的接口
public class DemoFilter implements Filter {
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws
    ServletException {
        System.out.println("init 初始化方法执行了");
     }
    @Override //拦截到请求之后调用, 调用多次
    public void doFilter(ServletRequest request, ServletResponse
        response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo 拦截到了请求...放行前逻辑");
        //放行
        chain.doFilter(request,response);
     }
    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
     }
}
  • init方法:过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对 象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。
  • doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调 用多次的,每拦截到一次请求就会调用一次doFilter()方法。
  • destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法 destroy,而这个销毁方法也只会被调用一次。

在定义完Filter之后,Filter其实并不会生效,还需要完成Filter的配置,Filter的配置非常简
单,只需要在Filter类上添加一个注解:@WebFilter,并指定属性urlPatterns,通过这个属性指
定过滤器要拦截哪些请求

@WebFilter(urlPatterns = "/*") //配置过滤器要拦截的请求路径( /* 表示拦
截浏览器的所有请求 )
public class DemoFilter implements Filter {
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws
    ServletException {
        System.out.println("init 初始化方法执行了");
     }
    @Override //拦截到请求之后调用, 调用多次
    public void doFilter(ServletRequest request, ServletResponse
    response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo 拦截到了请求...放行前逻辑");
        //放行
        chain.doFilter(request,response);
     }
    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
     }
}

当我们在Filter类上面加了@WebFilter注解之后,接下来我们还需要在启动类上面加上一个注解
@ServletComponentScan,通过这个@ServletComponentScan注解来开启SpringBoot项目对于
Servlet组件的支持。

@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {
    public static void main(String[] args) {
        SpringApplication.run(TliasWebManagementApplication.class,
        args);
     }
}

重新启动服务,打开浏览器,执行部门管理的请求,可以看到控制台输出了过滤器中的内容:

注意事项:

  • 在过滤器Filter中,如果不执行放行操作,将无法访问后面的资源。 放行操作:chain.doFilter(request, response);

现在我们已完成了Filter过滤器的基本使用,下面我们将学习Filter过滤器在使用过程中的一些细
节。

Filter详解

在完成了 Filter 过滤器的基本使用后,接下来我们将深入探讨一些 Filter 在实际使用中的重要细节。主要包括以下几个方面:

  1. 过滤器的执行顺序
  2. 如何配置过滤器的拦截路径
  3. 过滤器链的工作原理

执行流程

首先我们先来看下过滤器的执行

过滤器当中我们拦截到了请求之后,如果希望继续访问后面的web资源,就要执行放行操作,放行就是调用 FilterChain对象当中的doFilter()方法,在调用doFilter()这个方法之前所编写的代码属
于放行之前的逻辑。
在放行后访问完 web 资源之后还会回到过滤器当中,回到过滤器之后如有需求还可以执行放行之后的逻辑,放行之后的逻辑我们写在doFilter()这行代码之后。

@WebFilter(urlPatterns = "/*")
public class Demo1Filter implements Filter {
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws
    ServletException {
        System.out.println("init 初始化方法执行了");
     }
    @Override
    public void doFilter(ServletRequest servletRequest,
    ServletResponse servletResponse, FilterChain filterChain) throws
    IOException, ServletException {
        System.out.println("DemoFilter 放行前逻辑.....");
        //放行请求
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("DemoFilter 放行后逻辑.....");
     }
    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
     }
}

拦截路径

执行流程我们搞清楚之后,接下来再来介绍一下过滤器的拦截路径,Filter可以根据需求,配置不同的拦截资源路径:
拦截路径urlPatterns值 含义拦截具体路径/login只有访问 /login 路径时,才会被拦截目录拦截/emps/* 访问/emps下的所有资源,都会被拦截拦截所有/* 访问所有资源,都会被拦截
下面我们来测试"拦截具体路径":

@WebFilter(urlPatterns = "/login") //拦截/login具体路径
public class Demo1Filter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest,
    ServletResponse servletResponse, FilterChain filterChain) throws
    IOException, ServletException {
        System.out.println("DemoFilter 放行前逻辑.....");
        //放行请求
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("DemoFilter 放行后逻辑.....");
     }
    @Override
    public void init(FilterConfig filterConfig) throws
    ServletException {
        Filter.super.init(filterConfig);
     }
    @Override
    public void destroy() {
        Filter.super.destroy();
     }
}

测试1:访问部门管理请求,发现过滤器没有拦截请求

测试2:访问登录请求/login,发现过滤器拦截请求

过滤器链

最后我们在来介绍下过滤器链,什么是过滤器链呢?所谓过滤器链指的是在一个web应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链。

当我们在 web 应用中配置了多个过滤器时,这些过滤器会形成一个过滤器链。每个过滤器都会按照一定的顺序依次执行。具体来说,当请求到达时,过滤器会按照配置顺序依次处理请求,直到最后一个过滤器。只有当所有过滤器都成功放行请求后,才能访问最终的 web 资源。

一旦请求被处理完毕并返回响应,过滤器链会按照相反的顺序执行它们的后处理逻辑。也就是说,响应首先会回到最后一个过滤器,然后依次回到前面的过滤器,在这个过程中每个过滤器都可以对响应做出相应的修改或处理。

举个例子,如果我们在 web 应用中配置了两个过滤器,它们会形成如下的处理流程:

  1. 请求到达:请求首先进入过滤器链,第一个过滤器会处理请求。如果该过滤器调用了 chain.doFilter(request, response),请求会被传递给下一个过滤器,直到最后一个过滤器。
  2. 请求处理完毕:当请求被最终的 web 资源(如 servlet 或 controller)处理后,响应开始返回。在此过程中,过滤器会按照反向顺序执行它们的放行后逻辑,首先是最后一个过滤器,接着是前一个过滤器,直到第一个过滤器。

这种过滤器链的机制使得每个过滤器既可以在请求到达目标资源之前进行预处理,也可以在响应返回客户端之前进行后处理。通过这种方式,可以轻松实现对请求和响应的全方位控制。

验证步骤:

  1. 在filter包下再来新建一个Filter过滤器类:AbcFilter
  2. 在AbcFilter过滤器中编写放行前和放行后逻辑
  3. 配置AbcFilter过滤器拦截请求路径为:/*
  4. 重启SpringBoot服务,查看DemoFilter、AbcFilter的执行日志

AbcFilter过滤器

@WebFilter(urlPatterns = "/*")
public class AbcFilter1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse
    response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Abc 拦截到了请求... 放行前逻辑");
        //放行
        chain.doFilter(request,response);
        System.out.println("Abc 拦截到了请求... 放行后逻辑");
     }
}

DemoFilter过滤器

@WebFilter(urlPatterns = "/*")
public class DemoFilter1 implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest,
    ServletResponse servletResponse, FilterChain filterChain) throws
    IOException, ServletException {
        System.out.println("DemoFilter 放行前逻辑.....");
        //放行请求
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("DemoFilter 放行后逻辑.....");
     }
}

打开浏览器访问登录接口:

通过控制台日志的输出,大家发现AbcFilter先执行DemoFilter后执行,这是为什么呢?
其实是和过滤器的类名有关系。以注解方式配置的Filter过滤器,它的执行优先级是按时过滤器类名的自动排序确定的,类名排名越靠前,优先级越高。
假如我们想让DemoFilter先执行,怎么办呢?答案就是修改类名。

测试:修改AbcFilter类名为XbcFilter,运行程序查看控制台日志

@WebFilter(urlPatterns = "/*")
public class XbcFilter1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse
    response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Xbc 拦截到了请求...放行前逻辑");
        //放行
        chain.doFilter(request,response);
        System.out.println("Xbc 拦截到了请求...放行后逻辑");
     }
}

登录校验-Filter

在开发登录校验过滤器时,我们需要先理清一些基本的操作流程,下面是一个简化的描述:

登录流程回顾:

  1. 登录请求: 用户在后台管理系统中进行登录,客户端(通常是浏览器或移动端应用)将用户名和密码发送给后端登录接口(如/login)。
  2. 生成JWT令牌: 后端验证用户凭证(用户名和密码)是否有效。如果验证通过,服务器生成一个JWT(JSON Web Token)令牌,并将该令牌返回给客户端。
  3. 存储JWT令牌: 客户端将接收到的JWT令牌存储在本地存储(如localStoragesessionStorage)中,并在后续的请求中携带该令牌。
  4. 令牌校验: 对于后续的所有请求,客户端需要将JWT令牌包含在HTTP请求的头部(通常是Authorization字段)中。服务端接收到请求后,必须校验JWT令牌的有效性,确保令牌未过期并且未被篡改。
  5. 访问资源: 如果JWT令牌合法并且有效,过滤器会放行请求,让它继续向下执行,最终访问到业务功能(如数据库查询、数据修改等)。否则,返回一个未登录或权限错误的响应。

在实现登录校验过滤器时,我们需要考虑以下两个关键问题:

  1. 是否所有请求都需要校验令牌?- 答案是 不是所有请求都需要校验令牌,登录请求(/login)是一个例外。因为用户在登录时需要提供用户名和密码来获取JWT令牌,所以登录接口不需要进行令牌校验。
  2. 什么情况下请求才可以放行,执行后续业务操作?- 请求只有在 存在有效的JWT令牌 并且 令牌校验通过 的情况下,才能放行。这意味着: - 如果请求的头部没有携带JWT令牌,或者令牌无效(如已过期、篡改等),过滤器会拦截请求并返回未登录错误。- 如果JWT令牌合法且有效,过滤器会放行请求,允许它继续执行后续的业务逻辑。

开发登录校验过滤器时,首先需要理解请求流程和令牌的作用。我们需要保证:

  • 登录接口不需要令牌校验
  • 只有携带有效令牌的请求才会被放行,其他请求则需要被拒绝并返回未登录错误。

通过这个流程,我们确保了每个请求在执行时都经过了适当的验证,保障了系统的安全性。

具体流程

我们要完成登录校验,主要是利用Filter过滤器实现,而Filter过滤器的流程步骤:

基于上面的业务流程,我们分析出具体的操作步骤:

  1. 获取请求url
  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  3. 获取请求头中的令牌(token)
  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
  5. 解析token,如果解析失败,返回错误结果(未登录)
  6. 放行

代码实现

基本信息

请求路径:/login
请求方式:POST
接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发
JWT令牌。

请求数据样例:

{
 "username": "jinyong",
 "password": "123456"
}

响应数据样例:

{
"code": 1,
"msg": "success",
"data":
"eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1l
IjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojf
rr6XMJ-yVzaWCVZCo"
}

备注说明
用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header
中携带到服务端,请求头的名称为 token ,值为 登录时下发的JWT令牌。
如果检测到用户未登录,则会返回如下固定错误信息:

{
 "code": 0,
 "msg": "NOT_LOGIN",
 "data": null
}

登录校验过滤器:LoginCheckFilter

@Slf4j
@WebFilter(urlPatterns = "/*") // 拦截所有请求
public class LoginCheckFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 可以在此进行一些初始化工作,如果不需要可以忽略
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) 
            throws IOException, ServletException {

        // 强制转换为 HTTP 协议的请求对象、响应对象
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 1. 获取请求的URL
        String url = request.getRequestURL().toString();
        log.info("请求路径:{}", url);

        // 2. 判断是否是登录请求,如果是,直接放行
        if (url.contains("/login")) {
            chain.doFilter(request, response); // 放行登录请求
            return;
        }

        // 3. 从请求头获取 token
        String token = request.getHeader("token");
        log.info("获取的 token:{}", token);

        // 4. 判断 token 是否存在,如果不存在,返回未登录错误
        if (StringUtils.isEmpty(token)) {
            log.info("Token 不存在,用户未登录");
            sendErrorResponse(response, "NOT_LOGIN");
            return;
        }

        // 5. 验证 token 是否有效,如果无效,返回未登录错误
        try {
            JwtUtils.parseJWT(token); // 假设这个方法会抛出异常如果 token 无效
        } catch (Exception e) {
            log.error("令牌解析失败:{}", e.getMessage());
            sendErrorResponse(response, "INVALID_TOKEN");
            return;
        }

        // 6. 如果通过验证,继续执行后续的请求处理
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 可以在此进行资源清理,如果不需要可以忽略
    }

    // 发送错误响应的方法
    private void sendErrorResponse(HttpServletResponse response, String errorCode) throws IOException {
        Result result = Result.error(errorCode);
        String jsonResponse = JSONObject.toJSONString(result);

        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(jsonResponse);
    }
}

在上述过滤器的功能实现中,我们使用到了一个第三方json处理的工具包fastjson。我们要想使用,需要引入如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

登录校验的过滤器我们编写完成了,接下来我们就可以重新启动服务来做一个测试:

测试前先把之前所编写的测试使用的过滤器,暂时注释掉。直接将@WebFilter注解给注释掉即可。

测试:未登录是否可以访问部门管理页面
首先关闭浏览器,重新打开浏览器,在地址栏中输入:http://localhost:9528/#/system/
dept。
由于用户没有登录,登录校验过滤器返回错误信息,前端页面根据返回的错误信息结果,自动跳转
到登录页面了。


总结

通过本文的探讨,我们深入了解了登录校验的原理和实现方式,以及如何通过Filter来简化这一过程。在实际开发中,根据应用的不同需求,合理选择和实现登录校验方案,可以帮助我们构建更安全、更高效的Web应用。

标签: java 后端

本文转载自: https://blog.csdn.net/m0_63267251/article/details/143606456
版权归原作者 程序猿零零漆 所有, 如有侵权,请联系我们删除。

“理解Web登录机制:会话管理与跟踪技术解析(三)-过滤器Filter”的评论:

还没有评论