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

统一拦截

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

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

Filter过滤器

工作原理

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

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

快速入门

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

定义过滤器

定义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方法

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

doFilter方法

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

destroy方法

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

配置过滤器

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

@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方法放行:

@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令牌的,如果进行拦截,那么永远都只会得到请求失败的结果

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

/*** 令牌校验过滤器(登录过滤器)*/
@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.mzph.cn/bicheng/70048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 网络爬虫实战:从基础到高级爬取技术

📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 1. 引言 网络爬虫(Web Scraping)是一种自动化技术,利用程序从网页中提取数据,广泛…

MySQL锁类型(详解)

锁的分类图,如下: 锁操作类型划分 读锁 : 也称为共享锁 、英文用S表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。 写锁 : 也称为排他锁 、英文用X表示。当前写操作没有完成前,它会…

93,【1】buuctf web [网鼎杯 2020 朱雀组]phpweb

进入靶场 页面一直在刷新 在 PHP 中,date() 函数是一个非常常用的处理日期和时间的函数,所以应该用到了 再看看警告的那句话 Warning: date(): It is not safe to rely on the systems timezone settings. You are *required* to use the date.timez…

51单片机 01 LED

一、点亮一个LED 在STC-ISP中单片机型号选择 STC89C52RC/LE52RC;如果没有找到hex文件(在objects文件夹下),在keil中options for target-output- 勾选 create hex file。 如果要修改编程 :重新编译-下载/编程-单片机重…

【Rust自学】19.2. 高级trait:关联类型、默认泛型参数和运算符重载、完全限定语法、supertrait和newtype

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 19.2.1. 在trait定义中使用关联类型来指定占位类型 我们首先在第10章的10.3. trait Pt.1&a…

Elasticsearch:如何搜索含有复合词的语言

作者:来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战,因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名:Rindfleischetik…

web-SQL注入-CTFHub

前言 在众多的CTF平台当中,作者认为CTFHub对于初学者来说,是入门平台的不二之选。CTFHub通过自己独特的技能树模块,可以帮助初学者来快速入门。具体请看官方介绍:CTFHub。 作者更新了CTFHub系列,希望小伙伴们多多支持…

WPS动画:使图形平移、围绕某个顶点旋转一定角度

1、平移 案例三角形如下图,需求:该三角形的A点平移至原点 (1)在预想动画结束的位置绘制出图形 (2)点击选中原始图像,插入/动画/绘制自定义路径/直线 (3)十字星绘制的直线…

xmind使用教程

xmind使用教程 前言xmind版本信息“xmind使用教程”的xmind思维导图 前言 首先xmind是什么?XMind 是一款思维导图和头脑风暴工具,用于帮助用户组织和可视化思维、创意和信息。它允许用户通过图形化的方式来创建、整理和分享思维导图,可以用于…

KNIME:开源 AI 数据科学

KNIME(Konstanz Information Miner)是一款开源且功能强大的数据科学平台,由德国康斯坦茨大学的软件工程师团队开发,自2004年推出以来,广泛应用于数据分析、数据挖掘、机器学习和可视化等领域。以下是对KNIME的深度介绍…

2025年01月27日Github流行趋势

项目名称:onlook项目地址url:https://github.com/onlook-dev/onlook项目语言:TypeScript历史star数:5340今日star数:211项目维护者:Kitenite, drfarrell, iNerdStack, abhiroopc84, apps/dependabot项目简介…

TCL C++开发面试题及参考答案

进程和线程的区别 进程和线程都是操作系统中重要的概念,它们在很多方面存在着明显的区别。 从概念上来说,进程是资源分配的基本单位,每个进程都有自己独立的地址空间、内存、文件描述符等资源。例如,当我们在计算机上同时运行多个应用程序,像浏览器、文本编辑器等,每个应…

本地部署DeepSeek-R1模型(新手保姆教程)

背景 最近deepseek太火了,无数的媒体都在报道,很多人争相着想本地部署试验一下。本文就简单教学一下,怎么本地部署。 首先大家要知道,使用deepseek有三种方式: 1.网页端或者是手机app直接使用 2.使用代码调用API …

VS Code 复制正确格式的文件路径/文件夹路径 (绝对路径,相对路径, 斜杠 /, 反斜杠\\ 等)

VS Code 搜索 : baincd.copy-path-unixstyle Github : https://github.com/baincd/vscode-copy-path-unixstyle 插件市场: https://marketplace.visualstudio.com/items?itemNamebaincd.copy-path-unixstyle 支持复制各种格式的路径 格式 GitBash /c/chris/project-name/sr…

每天学点小知识之设计模式的艺术-策略模式

行为型模式的名称、定义、学习难度和使用频率如下表所示: 1.如何理解模板方法模式 模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基…

python 中的堆

文章目录 小根堆的特点Python 中的 heapq 模块1. heapq.heappush(heap, item)2. heapq.heappop(heap)3. heapq.heapify(x)4. heapq.heappushpop(heap, item)5. heapq.heapreplace(heap, item)6. heapq.nsmallest(n, iterable)7. heapq.nlargest(n, iterable) 小根堆的应用场景示…

深度学习 Pytorch 基础网络手动搭建与快速实现

为了方便后续练习的展开,我们尝试自己创建一个数据生成器,用于自主生成一些符合某些条件、具备某些特性的数据集。 导入相关的包 # 随机模块 import random# 绘图模块 import matplotlib as mpl import matplotlib.pyplot as plt# 导入numpy import nu…

【RocketMQ】RocketMq之IndexFile深入研究

一:RocketMq 整体文件存储介绍 存储⽂件主要分为三个部分: CommitLog:存储消息的元数据。所有消息都会顺序存⼊到CommitLog⽂件当中。CommitLog由多个⽂件组成,每个⽂件固定⼤⼩1G。以第⼀条消 息的偏移量为⽂件名。 ConsumerQue…

注解与反射基础

注解 概述 注解(Annotation),从jdk5.0引入。 作用 不是程序本身,可以对程序作出解释(这一点和注释没什么区别)可以被其他程序读取 格式 注释是以“注释名”在代码中存在的,还可以添加一些…

SliverAppBar的功能和用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容,本章回中将介绍SliverAppBar组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似,它们的…