登录认证(5):过滤器:Filter

ops/2025/2/4 12:38:22/

统一拦截

上文我们提到(登录认证(4):令牌技术),现在大部分项目都使用JWT令牌来进行会话跟踪,来完成登录功能。有了JWT令牌可以标识用户的登录状态,但是完整的登录逻辑如图所示:

我们还需要一个统一拦截器,用于拦截用户的请求,在拦截器中,我们需要验证用户的JWT令牌,来判断用户是否登录,从而进行放行或拦截。统一拦截一般有两种解决方案:Filter过滤器Interceptor拦截器,本文先讲解Filter过滤器

Filter过滤器

工作原理

Filter的本意就是过滤器,在JavaWeb中是用于过滤请求的,是JavaWeb的三大核心组件之一 ServletFilterListenerFilter过滤器的工作原理就是将客户端发起的对服务端资源的请求拦截下来,进行处理,从而实现一些特殊功能

当程序使用了Filter过滤器之后,请求想要访问服务端的资源,就必须先经过过滤器,待过滤器处理完毕放行之后,才可以访问对应的资源。Filter过滤器一般用于完成一些通用的操作,比如:登录校验敏感字符处理等。

快速入门

Filter过滤器的快速入门十分简单,主要步骤分为两步:首先需要定义过滤器,定义一个类,实现Filter接口并重写其中所有方法;然后需要配置过滤器,在Filter类上添加@WebFilter注解,配置拦截资源的路径(只有请求对应的资源才会被过滤器拦截)。假如使用SpringBoot构建项目,还需要在引导类上添加@ServletComponentScan注解开启Servlet组件支持。

定义过滤器

定义Filter过滤器的代码:

java">public class DemoFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Init Filter");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {log.info("Filter 成功拦截请求");}@Overridepublic void destroy() {log.info("Destroy Filter");}
}

定义Filter过滤器的代码很简单,首先需要定义一个类实现Filter接口,然后重写Filter接口中的三个方法。

init方法

顾名思义,init方法Filter过滤器的初始化方法,在服务器启动时,会自动创建Filter过滤器对象,在创建过滤器对象的时候,就会自动调用init初始化方法初始化过滤器,并且这个方法只会被调用一次。

doFilter方法

doFilter方法是过滤器的核心部分,该方法在过滤器每一次拦截到了请求之后都会被调用,一般而言,这个方法会被调用多次,每一次拦截到请求,都会调用一次doFilter方法,而我们就可以在doFilter方法中实现一些功能,比如说对这次请求的JWT令牌进行解析,以此判断用户的登录状态。

destroy方法

destroy方法销毁过滤器的方法,当服务器关闭时,会自动调用destroy方法销毁过滤器,destroy方法也只会被调用一次。

配置过滤器

在定义Filter过滤器之后,过滤器并不会生效,此时还需要完成Filter过滤器的配置,过滤器的配置相对简单,只需要在Filter类上添加@WebFilter注解,并在其urlPatterns属性中指定需要拦截的请求路径。

java">@Slf4j
@WebFilter(urlPatterns = "/*") // 配置Filter过滤器的拦截请求路径,/*表示拦截所有
public class DemoFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Init Filter");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {log.info("Filter 成功拦截请求");}@Overridepublic void destroy() {log.info("Destroy Filter");}
}

通过/*通配符表示拦截所有请求(所有请求都会被过滤器所拦截,可以根据不同的需求更改路径),这样就完成了Filter过滤器的配置,当定义和配置都完成之后,Filter过滤器就可以真正开始工作了,此时启动服务器进行测试:

如图所示,在服务器启动的时候,自动创建Filter过滤器,并调用了其init方法初始化过滤器。此时向服务端发起请求: 

如图所示,发起的请求成功被Filter过滤器拦截,但是客户端并没有得到服务器响应的数据 

这是因为Filter过滤器,必须要执行放行操作,才可以访问到服务器中的资源,可以通过FilterChain实体类中的doFilter方法放行:

java">@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {log.info("Filter 成功拦截请求");filterChain.doFilter(servletRequest, servletResponse);
}

放行之后再发起请求,就可以成功访问到服务器中的资源了:

关闭服务器,发现Filter过滤器自动调用destroy方法销毁: 

登录校验过滤器

通过上文的快速入门,我们已经了解了如何创建并配置一个Filter过滤器,那么回归到主线,使用Filter过滤器完成登录校验功能。

实现逻辑

让我们回顾一下登录校验的实现逻辑:用户登录时需要访问登录接口login,当用户输入的用户名和密码验证成功之后,会生成一个JWT令牌,并且将JWT令牌返回给客户端,客户端会将JWT存储起来,然后在后续的每一次请求中,都携带这个JWT令牌到服务端,这个时候,就需要Filter过滤器进行工作了:在登录校验中,Filter过滤器必须要验证JWT令牌的有效性,如果令牌能够成功解析,则证明为有效令牌;反之则为无效令牌。若令牌有效,Filter过滤器则放行请求,让它访问服务端对应的资源;如果为无效令牌,则进行拦截,并给客户端响应一个错误的信息(401)。其流程如图所示:

这样,登录校验的实现逻辑已经比较清晰了,但需要注意一点:对于登录(login)请求,过滤器是不需要进行拦截的,因为用户发起登录请求,就是为了获得JWT令牌,其本身是没有JWT令牌的,如果进行拦截,那么永远都只会得到请求失败的结果

我们上述逻辑编写登录校验过滤器代码:

java">/*** 令牌校验过滤器(登录过滤器)*/
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {log.info("Init Filter");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;// 获取该请求的URLString url = httpServletRequest.getRequestURL().toString();// 判断该请求是否包含login,如果包含,说明该请求是登录,则放行if (url.contains("/login")) {log.info("login");filterChain.doFilter(httpServletRequest, httpServletResponse);return;}// 获取请求头中的token(JWT令牌)String jwt = httpServletRequest.getHeader("token");// 判断令牌是否存在,若不存在,则返回错误结果(未登录)if (jwt == null || jwt.isEmpty()) {log.info("JWT令牌为空,登录失败");httpServletResponse.setStatus(401);return;}// 解析JWT令牌,如果解析失败,则返回错误结果(未登录)try {JWTUtils.parseJWT(jwt);} catch (Exception e) {e.printStackTrace();log.info("JWT解析失败,登录失败");httpServletResponse.setStatus(401);return;}// 令牌解析成功,放行log.info("令牌解析成功,放行");filterChain.doFilter(httpServletRequest, httpServletResponse);}@Overridepublic void destroy() {log.info("Destroy Filter");}
}

我们配置的过滤器是会拦截所有的请求,但是这和我们分析的逻辑不一样,我们需要直接放行登录请求,所以说需要进行判断该请求是否是登录,可以根据请求路径进行判断。然后需要获取到这次请求的token,如果请求没有一个请求头token,那么可以判断该请求没有携带JWT令牌,表示该用户没有登录,所以说直接响应错误结果,无需进行判断最后才是解析token,假如在解析过程中报错,那么肯定意味着JWT令牌解析错误,意味着用户可能伪造或更改了令牌,也表示用户未登录,所以说不能放行,需要返回错误代码。如果成功解析JWT令牌,那么就可以调用FilterChain中的doFilter方法,放行该请求,让它访问服务端对应的资源。这就是 登录校验 的整个流程。此时让我们发起请求进行测试:

如图所示,发起了一个非登录的请求,由于此时还没有登录,所以说没有JWT令牌,所以说自然被Filter过滤器所拦截,请求失败。 

此时发起登录请求,由于我们的逻辑是登录请求直接放行,所以说这次请求就被Filter过滤器放行了,成功获得了JWT令牌。 

当我们登录后获得了JWT令牌,之后的请求就可以在请求头中携带JWT令牌发起请求,这样当我们的JWT令牌是正确的,被成功解析之后Filter过滤器就会放行,就可以访问到对应的资源了。

Filter过滤器使用细节

Filter过滤器执行流程

Filter过滤器执行流程如图所示:

当过滤器拦截到了请求之后,如果希望该请求访问到服务端资源,那么就需要执行doFilter方法进行放行,在doFilter方法所编写的代码就是放行前逻辑。但是值得注意的是——请求访问完资源之后,还会回到过滤器当中,如果有需求,回到过滤器之后还可以执行放行后逻辑,放行后逻辑就是doFilter方法之后的代码。

Filter过滤器拦截路径

可以根据不同的需求,配置不同的拦截路径,下面介绍几种常见的拦截路径:像/login这样的拦截路径属于具体拦截路径,只有请求路径为/login时,拦截器才会拦截;像/emps/*这样的拦截路径属于目录拦截,访问/emps下的所有资源的请求,都会被拦截;像/*这样的拦截路径十分简单粗暴,会拦截所有请求。可以根据不同的需求,灵活的配置拦截路径。

过滤器链

在一个web应用程序中,可以根据需求,配置多个拦截器,这样的多个拦截器就形成了一个过滤器链:

如图所示,在web服务端中,定义了两个Filter过滤器这两个过滤器就形成了一个过滤器链,过滤器链上的过滤器会一个一个的执行,先执行第一个;再执行第二个,必须要过滤器链上的最后一个过滤器放行后(也代表着所有过滤器都必须放行),请求才能访问到对应的资源。但是当请求返回时,会根据相反的顺序,通过过滤器链,先执行过滤器2;再执行过滤器1,最后给客户端响应数据。

并且,过滤器链的执行顺序,是根据过滤器名(字符串)的自然排序的,比如AbcFilter会先于DemoFilter执行。

总结

Filter过滤器Servlet中的技术,是用于统一拦截的,想要在web程序中使用过滤器,就需要定义一个过滤器,并且配置过滤器,定义其拦截路径,我们可以在Filter过滤器doFilter方法中编写一些逻辑决定该过滤器的放行逻辑,从而实现登录校验功能Spring框架中也提供了一个类似过滤器的Interceptor拦截器,且听下回分解。


http://www.ppmy.cn/ops/155565.html

相关文章

Linux:宏观搭建网络体系

一、计算机网络背景 1、独立模式:计算机之间相互独立 可是这样的话,如果我们想要做协作就必然需要交互数据,就必须得使用U盘进行拷贝,效率很低,所以我们需要网络互联,将计算机连向同一台服务器&#xff0c…

Flutter开发环境配置

下载 Flutter SDK 下载地址:https://docs.flutter.cn/get-started/install M1/M2芯片选择带arm64字样的Flutter SDK。 解压 cd /Applications unzip ~/Downloads/flutter_macos_arm64_3.27.3-stable.zip执行 /Applications/flutter/bin/flutterManage your Flut…

基于Spring Security 6的OAuth2 系列之八 - 授权服务器--Spring Authrization Server的基本原理

之所以想写这一系列,是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器,但当时基于spring-boot 2.3.x,其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0,结果一看Spring Security也升级…

1. 【.NET Aspire 从入门到实战】--理论入门与环境搭建--引言

在当前软件开发领域,云原生和微服务架构已经成为主流趋势,传统的单体应用正逐步向分布式系统转型。随着业务需求的不断变化与用户规模的迅速扩大,如何在保证高可用、高扩展性的同时,还能提高开发效率与降低维护成本,成…

LVS工作模式 DR 配置要点

LVS工作模式 DR direct routing 在DR模式中,所有的RS需要配置两个地址:RIP和VIP在DR模式中,LVS会通过arp获取到所有RS的IP地址和对应的MAC地址,因此LVS和RS需处于同一二层网络中当User发送请求到LVS后,LVS保持源目IP地址和端口号…

在 Ubuntu 上安装 Node.js 23.x

在 Ubuntu 上安装 Node.js 23.x 前提条件安装步骤1. 下载设置脚本2. 运行设置脚本3. 安装 Node.js4. 验证安装 参考链接总结 在现代 web 开发中,Node.js 是一个不可或缺的工具。它提供了一个强大的 JavaScript 运行时环境,使得开发人员可以在服务器端使用…

web前端13--动画

1、动画 语法:两种写法 csskeyframes big{0%{width: 200px;background-color: aqua;}50%{width: 500px;background-color: rgb(111, 255, 0);}100%{width: 300px;background-color: red;}}keyframes big{from{width: 200px;background-color: aqua;}to{width: 500…

利用matlab寻找矩阵中最大值及其位置

目录 一、问题描述1.1 max函数用法1.2 MATLAB中 : : :的作用1.3 ind2sub函数用法 二、实现方法2.1 方法一:max和find2.2 方法二:max和ind2sub2.3 方法对比 三、参考文献 一、问题描述 matlab中求最大值可使用函数max,对于一维向量&#xff0…