【Spring】拦截器详解

文章目录

强制登录的功能,后端程序根据 Session 来判断用户是否登录,但是实现方法是比较麻烦的

  • 需要修改每个接口的处理逻辑
  • 需要修改每个接口的返回结果
  • 接口定义修改,前端代码也需要跟着修改

有没有更简单的方法,同意拦截所有的请求,并进行 Session 校验呢,这里我们才有一种新的方法------拦截器

快速入门

什么是拦截器?

拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码

  • 也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止其运行
  • 在拦截器中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器前端发来的请求,判断 Session 中是否有登录用户的信息。如果有就可以放心,如果没有就进行拦截

比如我们去银行办理业务,在办理业务前后,就可以加一些拦截操作

  • 办理业务之前,先取号,如果带了身份证就取号成功
  • 业务办理结束,给业务办理人员的服务进行评价
  • 这些就是"拦截器"做的工作

基本使用

拦截器的使用步骤分为两步

  1. 定义拦截器
  2. 注册配置拦截器

定义拦截器

实现 HandlerInterceptor 接口,并重写其所有方法

java 复制代码
@Slf4j  
@Component
public class LoginInterceptor implements HandlerInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        log.info("LoginInterceptor 目标方法执行前执行...");  
        return true;
    }  
  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        log.info("LoginInterceptor 目标方法执行后执行...");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        log.info("LoginInterceptor 视图渲染完毕后执行,最后执行");  
    }  
}
  • preHandle() 方法:目标方法执行前执行
    • 返回 true:继续执行后续操作
    • 返回 false:中断后续操作
  • postHandle() 方法:目标方法执行后执行
  • afterCompletion() 方法:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图,暂不了解)

注册配置拦截器

实现 webMvcConfigurer 接口,并重写 addInterceptors 方法

java 复制代码
@Configuration  
public class WebConfig implements WebMvcConfigurer {  
  
    @Autowired  
    private LoginInterceptor loginInterceptor;  
  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        // 注册自定义拦截器对象  
        registry.addInterceptor(loginInterceptor)  
                .addPathPatterns("/**");  
        // 设置拦截器拦截的请求路径( /** 为拦截所有请求)  
    }  
}

启动服务,试试访问任意请求,观察后端日志

  • 可以看到 preHandle 方法执行之后就放行了,开始执行目标方法,目标方法执行完成之后执行 postHandleafterCompletion 方法

我们把拦截器中 preHandle 方法的返回值改为 false,再观察运行结果

  • 可以看到,拦截器拦截了请求,没有进行响应

拦截器详解

拦截器的入门程序完成之后,接下来我们来介绍拦截器的使用细节。拦截器的使用细节我们主要介绍两个部分:

  1. 拦截器的拦截路径配置
  2. 拦截器实现原理

拦截路径

拦截路径是指我们定义的这个拦截器,对哪些请求生效

我们在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些请求。也可以通过 excludePathPatterns() 指定不拦截哪些请求

  • 上述代码中,我们的配置是 /**,表示拦截所有请求

比如用户登录校验,我们希望可以对除了登录之外所有的路径生效

java 复制代码
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;  
  
@Configuration  
public class WebConfig implements WebMvcConfigurer {  
  	// 自定义拦截器对象
    @Autowired  
    private LoginInterceptor loginInterceptor;  
  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        // 注册自定义拦截器对象  
        registry.addInterceptor(loginInterceptor)  
                .addPathPatterns("/**")  
                .excludePathPatterns("user/login");  
        // 设置拦截器拦截的请求路径( /** 为拦截所有请求)  
    }  
}

常见路径

在拦截器中除了设置 /** 拦截所有资源外,还有一些常见拦截路径设置:

拦截路径 含义 举例
/* 一级路径 能匹配 /user/boook/login,不能匹配 /user/login
/** 任意级路径 能匹配 /user/user/login/user/reg
/book/* /book 下的一级路径 能匹配 /book/addBook,不能匹配 /Book/addBook1/book
/book/** /book 下的任意级路径 能匹配 /book/book/addBook//book/addBook2,不能匹配 /user/login

以上拦截器规则可以拦截项目中使用的 URL,包括静态文件(图片文件,JSCSS 等文件)

拦截器执行流程

正常调用顺序:

有了拦截器之后,会在调用 Controller 之前进行响应的业务处理,执行的流程为:

  1. 添加拦截器之后,执行 Controller 的方法之前,请求会被拦截器拦截。执行 preHandle() 方法,这个方法需要返回一个布尔类型的值

    • 返回 true:就表示放行本次操作,继续访问 Controller 中的方法
    • 返回 false:就表示不会放行本次操作 Controller 中的方法也不会执行
  2. controller 中的方法执行完毕之后,再回过来执行 postHandle() 方法以及 afterCompletion() 方法,执行完毕之后,最终给浏览器响应数据

登录校验

学习拦截器的基本操作之后,接下来我们需要完成最后一步操作:通过拦截器来完成图书管理系统中的登录校验功能

定义拦截器

session 中获取用户信息,如果 session 中不存在,则返回 false,并设置 HTTP 状态码为 401,否则返回 true

java 复制代码
import com.bite.book.constant.Constants;  
import com.bite.book.model.UserInfo;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import jakarta.servlet.http.HttpSession;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.stereotype.Component;  
import org.springframework.web.servlet.HandlerInterceptor;  
import org.springframework.web.servlet.ModelAndView;  
  
@Slf4j  
@Component  
public class LoginInterceptor implements HandlerInterceptor {  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        //true  表示放行   false表示拦截  
        log.info("LoginInterceptor preHandle....");  
        //获取session, 并且判断session中存储的userinfo信息是否为空  
        HttpSession session = request.getSession();  
        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);  
        if (userInfo==null || userInfo.getId()<=0){  
            //用户未登录  
            response.setStatus(401);  
            return false;  
        }  
        return true;  
    }  
  
}

http 状态码 401Unauthorized

  • 未经过认证。指身份验证是必须的,没有提供身份验证或身份验证失败
  • 如果请求已经包含授权凭证,那么 401 状态码表示不接受这些凭证

注册配置拦截器

java 复制代码
import com.bite.book.interceptor.LoginInterceptor;  
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;  
  
@Configuration  
public class WebConfig implements WebMvcConfigurer {  
    @Autowired  
    private LoginInterceptor loginInterceptor;  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(loginInterceptor)  
                .addPathPatterns("/**")  
                .excludePathPatterns("/user/login")  
                .excludePathPatterns("/css/**")  
                .excludePathPatterns("/js/**")  
                .excludePathPatterns("/pic/**")  
                .excludePathPatterns("/**/*.html")  
        ;  
    }  
}

删除之前的登录校验代码:

java 复制代码
/**  
     * 查询图书列表  
     */  
    @RequestMapping("/getBookListByPage")  
    public Result<PageResult<BookInfo>> getBookListByPage(PageRequest pageRequest, HttpSession session){  
        log.info("查询图书列表, 请求参数pageRequest: {}", pageRequest);  
//        //从session获取用户信息  
//        //如果用户信息为空, 说明用户未登录  
//        UserInfo loginUserInfo = (UserInfo) session.getAttribute(Constants.USER_SESSION_KEY);  
//        if (loginUserInfo==null || loginUserInfo.getId()<=0){  
//            return Result.nologin();  
//        }  
        //参数校验, 自己补充  
        PageResult<BookInfo> bookList = bookService.getBookListByPage(pageRequest);  
        return Result.success(bookList);  
    }
相关推荐
摇滚侠4 分钟前
SpringMVC 入门到实战 SpringMVC 的执行流程 96
java·后端·spring·maven·intellij-idea
唐青枫5 分钟前
Java Liquibase 实战指南:让数据库变更像代码一样可追踪
java
qq_422152579 分钟前
PDF 解密工具怎么选?2026 年文档密码移除方案与注意事项
java·前端·pdf
布朗克16818 分钟前
38 Spring Boot入门——自动配置、核心注解与Starter机制
java·spring boot·后端
程序员老申23 分钟前
外呼突然全挂了,追查 24 分钟后我发现了 etcd 最阴的一颗雷
后端·程序员
何以解忧,唯有..23 分钟前
Go语言变量的声明方式详解
开发语言·后端·golang
长栎24 分钟前
MyBatis 缓存为啥总是失效?装饰器模式套娃的代价
后端
bright_ye26 分钟前
setjmp & longjmp 深度详解 + 代码示例
后端
To_OC26 分钟前
我一直以为 Ajax 是个黑盒,直到我写了这 50 行代码
前端·后端·全栈
沪漂阿龙26 分钟前
LangChain 系列:Structured Output结构化输出与源码解析
java·人工智能·架构·langchain