spring boot学习--简易web服务--拦截器与过滤器

264 阅读4分钟

上文中不同用户通过数据库查询解决了登录验证的问题。那已经登录的用户访问例如用户中心的时候,希望通过token(成功登陆发放的临时token)来维持登录状态。访问登录资源。那么这个token的校验就应该在每次处理请求之前。于是就有了拦截器与过滤器

前置条件

  1. java基础知识,maven的使用
  2. jdk的基本了解
  3. mysql的使用
  4. spring boot 框架总体设计概念
  5. http协议的基本了解
  6. redis基本使用

Redis的使用

在实现拦截器与过滤之前,我们先简单接入一下redis 新建 utils 目录 现在的目录结构如下

image.png

redis工具类代码(简单接入)

package com.example.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key     键
     * @param value   值
     * @param seconds 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long seconds) {
        try {
            if (seconds > 0) {
                redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

简单测试一下工具类是否可用

package com.example.controller;


import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.utils.RedisUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;

@RestController
public class LoginController {
    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
    @Resource
    UserMapper userMapper;

    @Resource
    RedisUtils redisUtils;

    @GetMapping("/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
        User user = userMapper.getUserByUsername(username);
        logger.warn(user.toString());
        if (!Objects.equals(password, user.getPassword())) {
            return "密码错误";
        }
        redisUtils.set("name",user.getNickname());
        String name = (String)redisUtils.get("name");
        logger.warn("缓存中的数据是:" + name);
        return "你好:" + user.getNickname();
    }
}

运行结果

image.png

接下来我们开始使用spring boot 的拦截器与过滤器 通过判断token是否有效

注册拦截器与过滤器

借用知乎上的一张图解析过滤器与拦截器在web服务中的执行顺序

image.png

设置过滤器 过滤只简单打印一下请求的生命周期即可,本次是通过注解进行注册的

package com.example.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;



@WebFilter(urlPatterns = "/*") // 指定拦截路径
public class SysFilter implements Filter {


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("Filter: 请求处理开始");

        chain.doFilter(request, response);

        System.out.println("Filter: 响应处理完成");
    }


}

设置拦截器

package com.example.interceptor;


import com.example.utils.RedisUtils;
import com.example.utils.ResultUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import javax.annotation.Resource;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Objects;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    private final String tokenName = "token";

    @Resource
    private Jackson2ObjectMapperBuilder builder;

    @Resource
    RedisUtils redisUtils;
    private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("请求处理开始前执行了");
        String token = request.getParameter(tokenName);
        ObjectMapper objectMapper = builder.build();
        String userStr = (String) redisUtils.get(token);
        if(Objects.equals(userStr, "")) {
            tokenInvalid(response);
        }
        Map<String, Object> userInfo = objectMapper.readValue(userStr, new TypeReference<>() {});
        request.setAttribute("userinfo", userInfo);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           org.springframework.web.servlet.ModelAndView modelAndView) throws Exception {
        logger.info("Interceptor: 请求处理完成");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {
        logger.info("Interceptor: 请求完成后清理");
    }

    public void tokenInvalid(HttpServletResponse httpServletResponse) {
        try {
            httpServletResponse.setContentType("application/json;charset=UTF-8");
            httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = httpServletResponse.getWriter();
            out.write(ResultUtil.tokenInvalid().toJSONString(0));
            out.flush();
            out.close();
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }
}

通过配置,注册拦截器 访问/user时粗发拦截

package com.example.config;


import com.example.interceptor.LoginInterceptor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;



@Configuration
public class WebMvcConfig implements WebMvcConfigurer {


    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user");
    }
}

改造控制器

package com.example.controller;


import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.utils.HashUtil;
import com.example.utils.RedisUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;

@RestController
public class LoginController {
    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
    @Resource
    UserMapper userMapper;

    @Resource
    RedisUtils redisUtils;



    @GetMapping("/login")
    public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
        User user = userMapper.getUserByUsername(username);
        logger.warn(user.toString());
        if (!Objects.equals(password, user.getPassword())) {
            return "密码错误";
        }
        long timestamp = System.currentTimeMillis();
        String token = HashUtil.md5(timestamp+"");
        // 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 将对象转换为 JSON 字符串
        try {
            String jsonString = objectMapper.writeValueAsString(user);
            redisUtils.set(token,jsonString);
        } catch (Exception e) {
            e.printStackTrace();
        }


        return "你好:" + user.getNickname() + "token是:" + token;
    }

    @GetMapping("/user")
    public String user(@RequestParam("token") String token){
        String userStr = (String) redisUtils.get(token);
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            User user = objectMapper.readValue(userStr, User.class);
            return user.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "获取用户信息失败";
    }
}

最终项目目录结构如下

image.png

执行演示 直接访问/user

image.png 执行登录

image.png 带着token去访问/user

image.png 对应日志

image.png

至此,spring boot的拦截器与过滤器的简单接入完成