Java拦截器(Interceptor)和过滤器(Filter)实例详解

news2024/12/24 21:35:10

一、Java过滤器和拦截器

1.1、过滤器(Filter)

        Filter过滤器,是Servlet(Server Applet)技术中的技术,开发人员可以通过Filter技术,管理web资源,可以对指定的一些行为进行拦截,例如URL级别的权限访问控制等。在ServletRequest到达Servlet之前,拦截客户端的ServletRequest,可以根据需要检查ServletRequest,也可以修改ServletRequest中的头和数据;在ServletResponse到达客户端之前,可以拦截ServletResponse,可以根据需要检查ServletResponse,同样也可以修改。

        通过实现Filter接口(属于javax.servlet包),实现doFilter()方法,即可自定义过滤器。

1.2、拦截器(Interceptor)

        Interceptor拦截器是Spring MVC框架中对请求进行拦截和处理的组件,可以实现权限验证、日志记录、异常处理等功能,拦截器是在Spring MVC框架中执行的。在HttpServletRequest到达Controller之前,可以根据需要检查HttpServletRequest,也可以进行修改;在HttpServletResponse返回之前,可以根据需要检查HttpServletResponse,也可以进行修改。

        通过实现HandlerInterceptor接口(属于org.springframework.web.servlet包),实现preHandle(前置)、postHandle(后置)、afterCompletion(完成后)方法,即可自定义拦截器。

1.3、两者区别

相同点:

        1、拦截器和过滤器都体现了AOP的思想,都可以拦截请求方法;

        2、过滤器和拦截器可以定义多个,且可以通过Order自定义执行顺序;

不同点:

        1、运行位置不同:过滤器运行在Web服务器和Servlet容器之间,拦截器是针对具体控制器方法前进行拦截;

        2、功能不同:过滤器主要对请求进行预处理或者过滤;而拦截器主要对请求进行流程控制;

        3、依赖框架不同:过滤器是基于Servlet实现的,而拦截器是基于Spring MVC的。

二、代码应用

2.1、需求说明

        通过过滤器实现初步过滤,筛除掉一些非法IP访问,例如不允许本机(127.0.0.1)外的所有IP地址访问。通过拦截器实现登录信息或身份信息校验,并实现日志记录功能。

2.2、过滤器编写

import com.hao.dockertest.util.MyTimeUtil;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author Hao
 * @program: DockerTest
 * @description: 过滤器
 * @date 2023-10-21 21:11:28
 */
@Slf4j
public class MyFilter implements Filter {

    @Override
    public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
        log.info("Filter init!");
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("Filter Start! 实现初步过滤");

        // 从ServletRequest获取相关信息
        String ip = servletRequest.getRemoteAddr();
        if (ip.equals("0:0:0:0:0:0:0:1"))   ip = "127.0.0.1";
        String time = MyTimeUtil.sdf.format(System.currentTimeMillis());
        log.info("访问IP:{},访问时间:{}", ip, time);

        // 拦截非本机IP
        if(!ip.equals("127.0.0.1")){
            log.error("非本机IP({})禁止访问!", ip);
            // 自定义ServletResponse,返回前端提示信息
            PrintWriter printWriter = servletResponse.getWriter();
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.setContentType("application/json;charset=UTF-8");
            printWriter.write("Sorry, your IP address does not allow access!");
            return;
        }

        // 手动放行(如果上面加了过滤条件,最后必须手动放行!)
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        log.info("Filter destroy!");
        javax.servlet.Filter.super.destroy();
    }
}

注册自定义的过滤器

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

/**
 * @author Hao
 * @program: DockerTest
 * @description: 定义过滤器
 * @date 2023-10-21 21:18:05
 */
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<Filter> filterRegistrationBean(){

        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        // 注册自定义的过滤器
        filterFilterRegistrationBean.setFilter(new MyFilter());

        return filterFilterRegistrationBean;
    }
}

实现效果:

可以看到非本地IP的访问已经被拦截了。

本地IP的访问可以被正常放行。

2.3、拦截器编写

import com.hao.dockertest.util.JWTUtil;
import com.hao.dockertest.util.MyTimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;


/**
 * @author Hao
 * @program: DockerTest
 * @description: 拦截器
 * @date 2023-10-21 21:13:27
 */
@Slf4j
@Configuration
public class MyInterceptor implements HandlerInterceptor {
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("拦截器前置处理 preHandle");

        // 验证token
        String token = request.getHeader("token");
        if(token == null || !JWTUtil.checkToken(token)){
            log.error("未登录或身份信息验证失败");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json;charset=UTF-8");
            PrintWriter printWriter = response.getWriter();
            printWriter.write("您还未登录或身份验证失败,请重新登录!");
            return false;
        }

        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("拦截器后置处理 postHandle");

        // 从HttpServletRequest获取相关信息
        String ip = request.getRemoteAddr();
        if (ip.equals("0:0:0:0:0:0:0:1"))   ip = "127.0.0.1";
        String browser = request.getHeader("Sec-Ch-Ua-Platform");
        String httpMethod = request.getMethod();
        String time = MyTimeUtil.sdf.format(System.currentTimeMillis());

        // 日志存档
       //  Record record = new Record(null, ip, httpMethod, browser, time);
       // 访问日志记录逻辑

        // 从token中获取相关信息
        String token = request.getHeader("token");
        String userName = JWTUtil.getTokenInfo(token, "userName");
        String userId = JWTUtil.getTokenInfo(token, "userId");

        log.info("访问用户:{}-{},访问IP:{},访问时间:{},请求方式:{},访问设备:{}",userName, userId, ip, time, httpMethod, browser);

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("拦截器完成后 afterCompletion");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

配置自定义的拦截器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Hao
 * @program: DockerTest
 * @description: Interceptor配置
 * @date 2023-10-21 21:26:18
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); // 拦截所有请求,排除login
    }
}

2.4、JWT工具

首先引入JWT工具jjwt的pom依赖

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
</dependency>

自定义JWT工具类,包含构建Token,检验token有效性和解析token。

import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.UUID;

/**
 * @author Hao
 * @program: DockerTest
 * @description: JWT工具
 * @date 2023-10-23 10:44:36
 */
@Slf4j
public class JWTUtil {

    private static long time = 1000*60*60*10;
    // 签名
    private static final String signature = "test";

    public static String createToken(String userName, String userID){
        JwtBuilder jwtBuilder = Jwts.builder();//构建JWT对象
        return jwtBuilder
                // Header
                .setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
                // payload
                .claim("userName",userName)
                .claim("userId", userID)
                // 设置有效期(毫秒单位)
                .setExpiration(new Date(System.currentTimeMillis()+time))
                .setId(UUID.randomUUID().toString())
                // signature
                .signWith(SignatureAlgorithm.HS256, signature)
                // compact拼接三部分header、payload、signature
                .compact();
    }

    public static Boolean checkToken(String token){
        if(token == null){
            return false;
        }
        try {
            JwtParser jwtParser = Jwts.parser();
            jwtParser.setSigningKey(signature).parseClaimsJws(token);
            return true;
        }catch (Exception e){
            // log.error("token失效");
            return false;
        }
    }

    public static String getTokenInfo(String token, String key){
        if(token == null || key == null)   return null;
        JwtParser parser = Jwts.parser();
        Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(token);
        Claims payload = claimsJws.getBody();

        // 获取key对于的内容
        return payload.get(key).toString();
    }

}

2.5、模拟Controller

        主要模拟访问业务和登录业务

import com.hao.dockertest.util.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

/**
 * @author Hao
 * @program: DockerTest
 * @description: 模拟Controller
 * @date 2023-10-20 12:13:28
 */
@RestController
@RequestMapping
@Slf4j
public class RecordController {

    @GetMapping
    public String getInfo(){

        return "Welcome!";
    }

    @PostMapping("/login")
    public String login(){

        return JWTUtil.createToken("张三", "20210919"); // 模拟返回登录成功token
    }

}

其中getinfo()为模拟某个业务,需要登陆之后才能访问;而login()是登录方法,返回给前端token,这个方法拦截器默认放行。

2.6、测试

        我们使用PostMan模拟一下拦截器功能

可以看到,过滤器先启动,初步过滤掉非法IP,然后拦截器启动,验证token信息,在postman的Headers中我们并未设置token,拦截器检查到HttpServletRequest不包含token,会返回前端错误信息,提示用户登录。

        然后我们访问login:

可以看到这里只有过滤器起了作用,因为我们拦截器设置了排除/login这个路径,因此/login不会被拦截器拦截,也就可以正常的访问controller中的login方法,在postman中我们可以看到已经正常访问,并返回了token。

        将返回的token,我们手动加入到Headers的token中,再访问一次上面模拟的业务逻辑:

可以看到已经可以正常访问,再看一下控制台日志:

可以看到拦截器正常拦截,并从中获取到了关键信息,打印了日志,并成功放行,基于此就实现了基于拦截器的身份验证功能。

        此外,我们还可以模拟一下token失效或者被非法篡改,看一下拦截效果。我们随机把Headers的token删除几个字符,再试一下访问效果。

可以看到,即使携带token,但token已经实现,就无法对controller中的方法进行访问,被拦截器成功拦截。

三、应用场景

        常见的过滤器用途主要包括:对用户请求进行统一认证、对用户访问的请求进行记录和审核,对用户发送的数据进行过滤或替换,转换图像格式、对响应内容进行压缩,对请求或响应进行加密解密等。     

        拦截器采用了AOP的设计思想,可以用来拦截处理方法在之前和之后执行的一些跟主业务没有关系的公共功能,例如权限控制,日志、异常记录,记录方法执行时间等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1123682.html

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

相关文章

程序员的金饭碗在哪里?这几个网站建议收藏!帮助你一步登天

俗话说的好&#xff0c;一个趁手的工具抵过诸葛亮。尤其是在程序员这个领域&#xff0c;不仅是一个非常和科技挂钩的领域&#xff0c;而且更新速度非常的迅速。 连java python都在更新&#xff0c;手头上写码的工具却还是老三样怎可行&#xff1f;这就需要我们跟上时代的脚步&…

全局下载报错怎么办

举个例子&#xff0c;当你要全局下载create-react-app&#xff0c;报如下图所示的错 这个时候&#xff0c;关闭掉git base,再以管理员身份运行 你再次下载&#xff0c;ok了

【网络】对于我前面UDP博客的补充

UDP 前言正式开始UDP报文UDP报文如何将UDP报文和报头进行分离和封装UDP如何将有效载荷交付给上层如何提取出完整报文报头是啥报头中的检验和 UDP的特点IO接口乱序问题UDP是全双工的注意事项基于UDP的应用层协议 再次谈论端口五元组端口号范围划分netstatxargs 前言 本篇比较偏…

C#调用C++ 的DLL传送和接收中文字符串

1 c#向c传送中文字符串 设置&#xff1a;将 字符集 改为 使用多字节字符集 cpp代码&#xff1a; extern "C"_declspec(dllexport) int input_chn_str(char in_str[]) {cout<<in_str<<endl;return 0; }c#代码&#xff1a; [DllImport("Demo.dll…

uni-app:引用文件的方法

绝对定位 ①import common from "/utils/common.js" ②import common from "utils/common.js" <template><view></view> </template> <script>import common from "/utils/common.js"export default {data() {ret…

分享一下抽奖活动小程序怎么做

在当今数字化时代&#xff0c;抽奖活动小程序已成为一种高效、创新的营销方式。它不仅能够吸引用户的注意力&#xff0c;提高品牌知名度&#xff0c;还能促进用户参与度&#xff0c;增强用户对品牌的忠诚度。本文将详细介绍如何制作一个成功的抽奖活动小程序&#xff0c;以及它…

[yolo系列:如何固定随机种子(以yolov7为例)]

文章目录 概要随机种子&#xff08;Random Seed&#xff09;第一步第二步第三步第四步 概要 在计算机科学和深度学习领域&#xff0c;随机性是一个常见而重要的概念。在一些情况下&#xff0c;我们需要确保代码每次运行时都能得到相同的随机结果&#xff0c;以便进行模型的可重…

SAR ADC:10 bit 串口控制的模数转换器MS1549,可替TLV1549

MS1549 是一个 10 位开关电容器、逐次逼近型的模数转换 器。此芯片有 2 个数字输入端、 1 个三态输出口&#xff08;包括片选端 口 ( CS ) 、 1 个 I/O CLOCK 端口和 1 个数字输出端 (DATA OUT) &#xff09;&#xff0c; 可以实现三总线接口到总控制器的串行口的数据传输…

正点原子嵌入式linux驱动开发——异步通知

上一篇笔记中使用阻塞或者非阻塞的方式来读取驱动中按键值都是应用程序主动读取的&#xff0c;对于非阻塞方式来说还需要应用程序通过poll函数不断的轮询。最好的方式就是驱动程序能主动向应用程序发出通知&#xff0c;报告自己可以访问&#xff0c;然后应用程序再从驱动程序中…

Redis持久化解析:全面了解Redis的数据持久化机制

文章目录 &#x1f34a; Redis持久化&#x1f389; 什么是持久化&#xff1f;&#x1f389; 持久化的原理&#x1f4dd; RDB持久化&#x1f4dd; AOF持久化&#x1f4dd; 混合持久化&#x1f4dd; save与bgsave &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领…

使用 VS 2022 开发C#项目的tips

代码操作 删除注释或空行 参考C#【必备技能篇】Visual Studio删除所有的注释和空行 删除所有行注释&#xff1a;包括行内和行外&#xff0c;如下所示 Ctrl H 替换&#xff0c;第一行输入&#xff1a;//[^\n]*\n, 第二行输入&#xff1a;\n。替换即可。 这一步可能出现很多空…

数据结构题型20-第七章 查找

文章目录 1 考察重点2 知识框架3 考察重点4 顺序查找和折半查找4.1 顺序查找4.1.1 一般线性表的顺序查找4.1.2 有序表的顺序查找 4.2 折半查找4.3 分块查找 1 考察重点 2 知识框架 3 考察重点 4 顺序查找和折半查找 4.1 顺序查找 4.1.1 一般线性表的顺序查找 4.1.2 有序表的顺…

基于Django开发的图书管理推荐、电影推荐、课程推荐系统、大众点评店铺推荐系统、健身课程管理系统

基于Django开发的图书管理推荐、电影推荐、课程推荐系统、大众点评店铺推荐系统、健身课程管理系统、资讯推荐系统 一、简介 推荐系统的目的是信息过载所采用的措施&#xff0c;面对海量的数据&#xff0c;从中快速推荐出用户可能喜欢的物品。 推荐系统的方法有以下几种&…

顺序栈的实现----数据结构

栈的概念 对于栈&#xff08;Stack&#xff09;&#xff0c;后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;&#xff0c;栈也是一种线性表&#xff0c;只不过是一种操作受限的线性表&#xff0c;只能在一端操作&#xff0c;也就是不允许在中间进行查找、…

javaEE -4(11000字详解多线程)

一&#xff1a;常见的锁策略 1.1 乐观锁 vs 悲观锁 乐观锁和悲观锁是并发控制的两种不同策略&#xff0c;用于处理多个线程同时访问共享资源的情况。它们的主要区别在于对并发冲突的处理方式。 悲观锁是一种较保守的并发控制策略&#xff0c;它假设在整个事务过程中会发生冲…

什么是云迁移?迁移到云的基本指南

企业可以执行多种类型的云迁移。一种常见的模型是将数据和应用从本地数据中心传输到公共云。然而&#xff0c;云迁移还可能需要将数据和应用从一个云平台或提供商迁移到另一个云平台或提供商;这种模式称为云到云(C2C)迁移。第三种类型的迁移是反向云迁移、云遣返或云退出&#…

如何提高webpack的构建速度?

一、背景 随着我们的项目涉及到页面越来越多&#xff0c;功能和业务代码也会随着越多&#xff0c;相应的 webpack 的构建时间也会越来越久 构建时间与我们日常开发效率密切相关&#xff0c;当我们本地开发启动 devServer 或者 build 的时候&#xff0c;如果时间过长&#xff…

Linux下的IMX6ULL——环境搭建与软件安装(一)

前言&#xff1a; 从今天开始我们就要用到IMX6ULL这块开发板进行linux的学习了&#xff0c;对于初学者&#xff0c;下载好资料、搭建好开发环境后&#xff0c;按照下面顺序学习即可&#xff1a; 先应用&#xff0c;再驱动&#xff0c;最后做项目。应用、驱动、项目这三个慢慢走…

酷开科技丨大屏购物买买买,酷开系统助力网购模式再升级

随着技术的发展和家庭场景智能化的趋势&#xff0c;消费者对品质和体验的需求不断提高。在这一背景下&#xff0c;OTT大屏营销生态得到了快速发展&#xff0c;大屏的购物功能进一步被释放&#xff0c;已经具备更强的“转化”能力。电视的智能化、交互简单化、账号同步化等特性使…

如何利用验证链技术减少大型语言模型中的幻觉

一、前言 随着大型语言模型在自然语言处理领域取得了惊人的进步。相信深度使用过大模型产品的朋友都会发现一个问题&#xff0c;就是有时候在上下文内容比较多&#xff0c;对话比较长&#xff0c;或者是模型本身知识不了解的情况下与GPT模型对话&#xff0c;模型反馈出来的结果…