【Spring Boot 快速入门】八、登录认证(二)统一拦截

发布于:2025-08-11 ⋅ 阅读:(17) ⋅ 点赞:(0)

统一拦截

Filter 过滤器

快速入门

Filter 是 Java Web 三大组件之一,另外两个是 Servlet 和 Listener(监听器),目前 Filter 使用较多。

过滤器(Filter)可拦截对资源的请求,执行特殊逻辑操作后再放行,能完成登录校验、统一编码处理、敏感字符处理等通用性操作,避免重复编写相同逻辑。

Filter 使用步骤:

  1. 定义 Filter:定义一个类,实现 FIlter 接口,并重写其所有方法
  2. 配置 Filter:Filter 类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启 Servlet 组件支持
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("DemoFilter doFilter");
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("DemoFilter init");
    }

    @Override
    public void destroy() {
        System.out.println("DemoFilter destroy");
    }
}

init 方法在 web 服务器启动时创建 Filter 对象后调用,仅一次,用于资源和环境准备;destroy 方法在关闭服务器时调用,仅一次,用于资源释放和环境清理;doFilter 方法在每次拦截到请求时调用,是核心方法。

详解

过滤器的执行流程:拦截到请求后,先执行放行前的逻辑(写在调用 doFilter 方法之前),然后调用 FilterChain 对象的 doFilter 方法执行放行,访问对应的 web 资源,之后会回到过滤器执行放行后的逻辑(写在 doFilter 方法之后),最后给浏览器响应数据。

过滤器拦截路径的配置方式常见的有三种:

  • 拦截具体路径(如拦截 /log,只拦截该路径请求)
  • 目录拦截(如拦截 /dpts/,拦截以 /dpts 开头的路径请求)
  • 拦截所有(用 /* 表示)

在 web 应用中配置多个过滤器形成过滤器链。执行时按顺序依次执行每个过滤器的放行前逻辑,全部放行后访问 web 资源;web 资源访问完毕后,按相反顺序执行各过滤器放行后的逻辑,最后响应浏览器。

过滤器执行优先级的决定因素:以注解方式配置的过滤器,其执行优先级由类名的自然排序决定,类名越靠前,优先级越高。通过修改类名可改变执行顺序。

Filter 进行登录校验

登录校验基本流程:访问后台管理系统需先登录,登录成功后服务端生成 JWT 令牌返回给前端,前端后续每次请求都会携带该令牌,服务端通过过滤器校验令牌有效性,有效则放行,无效则返回错误信息。

登录校验过滤器实现思路:拦截所有请求后,先判断是否为登录请求,是则直接放行;不是则获取请求头中的 JWT 令牌,判断其是否存在,不存在则返回未登录结果;存在则解析令牌,解析失败返回未登录结果,成功则放行。

在这里插入图片描述

Filter 过滤器完整实现代码:

import com.alibaba.fastjson.JSONObject;
import com.example.demo.responed.Result;
import com.example.demo.utils.JwtUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.StringUtils;

import java.io.IOException;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 1.获取请求url
        String url = req.getRequestURL().toString();

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

        // 3.获取请求头中的令牌(token)
        String jwt = req.getHeader("token");

        // 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt)){
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString( error);
            resp.getWriter().write(notLogin);
            return;
        }

        // 5.如果令牌存在,需要查询数据库,查询用户是否存在
        try {
            JwtUtils.parseJwt(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("解析令牌失败");
            Result error = Result.error("未登录");
            String notLogin = JSONObject.toJSONString( error);
            resp.getWriter().write(notLogin);
            return;
        }

        // 6.放行
        chain.doFilter(request, response);
    }
}

需要在 pom.xml 文件中引入 fastjson 工具包将结果转为 json 字符串并响应:

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

当未登录(没有 JWT 令牌)时,显示 NOT_LOGIN:

在这里插入图片描述

而当登录后(携带 JWT 令牌),显示出数据:

在这里插入图片描述

Interceptor 拦截器

快速入门

拦截器是 Spring 框架提供的动态拦截控制器方法执行的机制,类似过滤器,可在指定方法运行前后执行预先定义的代码,常用于登录校验等通用性操作,如校验用户是否携带合法的 gwt 令牌,决定是否放行或响应未登录信息。

Interceptor 使用步骤:

  1. 定义拦截器需创建类实现 handler interceptor 接口,重写其中的三个方法

    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    @Component
    public class LoginCheckInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion");
        }
    }
    
  2. 注册拦截器

    import com.example.demo.interceptor.LoginCheckInterceptor;
    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 LoginCheckInterceptor loginCheckInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(loginCheckInterceptor)
                    .addPathPatterns("/**") // 拦截所有请求
                    .excludePathPatterns("/login"); // 登录接口不拦截
            WebMvcConfigurer.super.addInterceptors(registry);
        }
    }
    

详解

拦截器拦截路径配置:

拦截路径配置 含义 示例匹配情况
/* 仅匹配一级路径 - 可匹配:/depts/emps/log - 不可匹配:/depts/1(两级路径)
/** 匹配任意级路径 - 可匹配:/depts(一级)、/depts/1(两级)、/depts/1/2(三级)
/depts/* 匹配以/depts开头的一级子路径 - 可匹配:/depts/1 - 不可匹配:/depts/1/2(两级子路径)、/emps(非/depts前缀)
/depts/** 匹配以/depts开头的任意级子路径 - 可匹配:/depts/1/depts/1/2 - 不可匹配:/emps(非/depts前缀)

拦截器执行流程:浏览器访问 web 应用时,过滤器先拦截请求,执行放行前逻辑并放行,请求进入 Spring 环境后经前端控制器 DispatcherServlet 转发,拦截器在执行 Controller 接口方法前拦截,先执行 preHandle 方法,返回 true 则放行,Controller 方法执行后执行 postHandle 和 afterCompletion方法,最后执行过滤器放行后逻辑并响应浏览器。

拦截器与过滤器的区别:

对比维度 过滤器(Filter) 拦截器(Interceptor)
接口规范 实现javax.servlet.Filter接口 实现org.springframework.web.servlet.HandlerInterceptor接口
拦截范围 拦截所有资源(包括静态资源、非 Spring 管理的请求等) 仅拦截进入 Spring 环境的资源(如 Controller 接口请求)
执行时机 在请求进入 Servlet 容器后、Spring 处理前执行 在 Spring 的DispatcherServlet转发请求后、Controller 方法执行前后执行

Interceptor 进行登录校验

Interceptor 拦截器与 Filter 过滤器的基本流程和实现思路一致,只有代码上的细微区分:

将 LoginCheckInterceptor 改为以下:

import com.alibaba.fastjson.JSONObject;
import com.example.demo.responed.Result;
import com.example.demo.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求url
        String url = request.getRequestURL().toString();

        // 2.判断请求url是否包含login,如果包含,放行
        if(url.contains("login")){
            return true;
        }

        // 3.获取请求头中的令牌(token)
        String jwt = request.getHeader("token");

        // 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(jwt)){
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString( error);
            response.getWriter().write(notLogin);
            return false;
        }

        // 5.如果令牌存在,需要查询数据库,查询用户是否存在
        try {
            JwtUtils.parseJwt(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("解析令牌失败");
            Result error = Result.error("未登录");
            String notLogin = JSONObject.toJSONString( error);
            response.getWriter().write(notLogin);
            return false;
        }
        // 6.放行
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}

全局异常处理

目前案例中未对异常做任何处理,在三层架构调用(control 调用 service,service 调用 map)过程中,异常会逐层上抛,最终由框架返回不符合规范的错误信息 JSON 数据。

异常处理有两种方案,一是在 control 的每个方法中进行 try catch 捕获异常,但该方案操作繁琐、代码臃肿,不推荐;二是定义全局异常处理器,可捕获整个项目中的所有异常,更简单优雅,是项目推荐方式。

定义一个类,在类上添加@RestControllerAdvice注解,代表这是全局异常处理器。在类中定义方法,方法上添加@ExceptionHandler注解,通过其 value 属性指定要捕获的异常类型(如Exception.class代表捕获所有异常)。方法中可输出异常堆栈信息,并返回符合规范的Result对象封装错误信息。@RestControllerAdvice等同于@ControllerAdvice@ResponseBody,能将方法返回值转为 JSON 响应给前端

import com.example.demo.responed.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result error(Exception e) {
        e.printStackTrace();
        return Result.error("操作失败");
    }
}

这样就完成了基础的全局异常处理


网站公告

今日签到

点亮在社区的每一天
去签到