Spring 之 jwt,过滤器,拦截器,aop,监听器,参数校验

2023-11-10

Spring 之 jwt,过滤器,拦截器,aop,监听器

一、jwt编写

1.1 pom

  • 第二个pom为了生成公私钥
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.1.0</version>
    </dependency>

1.2 JwtUtils

package org.example.JWT;

import cn.hutool.core.io.FileUtil;
import io.jsonwebtoken.*;
import org.example.Entity.User;
import org.springframework.beans.BeanUtils;


import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class JWTUtil {
    public static String keyPath = "D:\\";
    public static int Expire = 1000 * 1000;
    public static <T> String getToken(String id, T t) throws Exception {
        HashMap head = new HashMap<>();
        //添加jwt头
        head.put("alg",  SignatureAlgorithm.RS256.getValue());//不使用签名算法
        head.put("typ", "JWT");
        //JWT体结构
        HashMap body = new HashMap();
        Field[] declaredFields = t.getClass().getDeclaredFields();
        for (Field field: declaredFields){
            field.setAccessible(true);
            body.put(field.getName(), field.get(t));
            System.out.println(field.getName() + " " + field.get(t));
        }
        //生成JWT
        String jwt = Jwts.builder()
                .setHeader(head)
                .setClaims(body)
                .setId(id)
                //.setExpiration(new Date(System.currentTimeMillis() + Expire))
                .signWith(SignatureAlgorithm.RS256,getPriKey())
                .compact();
        return jwt;
    }
    public  static <T> Map<String, Object> parseToken(String jwt, T t) throws Exception {
        try {
            Jwt result = Jwts.parser().setSigningKey(getPubKey()).parse(jwt);
            Header header = result.getHeader();
            Claims body = (Claims) result.getBody();
            System.out.println(body);
            HashMap<String, Object> map = new HashMap<>();
            String subject = (String)body.get("jti");
            map.put("id",subject);
            System.out.println(subject);
            T o = (T) t.getClass().newInstance();
            Field[] declaredFields = t.getClass().getDeclaredFields();
            for(Field field: declaredFields){
                field.setAccessible(true);
                field.set(o, body.get(field.getName()));
            }
            System.out.println(o);
            map.put("obj",o);
            return map;
        }catch (Exception e){
            e.getMessage();
            System.out.println(e.getMessage());
            return null;
        }
    }
    public static void getKey(String password) throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(password.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();

        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();

        FileUtil.writeBytes(publicKeyBytes, keyPath +"pub.key");
        FileUtil.writeBytes(privateKeyBytes, keyPath +"pri.key");
    }
    //获取私钥
    public static PrivateKey getPriKey() throws Exception{
        InputStream resourceAsStream =
                new FileInputStream(keyPath +"pri.key");
        DataInputStream dis = new DataInputStream(resourceAsStream);
        byte[] keyBytes = new byte[resourceAsStream.available()];
        dis.readFully(keyBytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePrivate(spec);
    }

    //获取公钥
    public static PublicKey getPubKey() throws Exception{
        InputStream resourceAsStream =
                new FileInputStream(keyPath + "pub.key");
        DataInputStream dis = new DataInputStream(resourceAsStream);
        byte[] keyBytes = new byte[resourceAsStream.available()];
        dis.readFully(keyBytes);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setAge(12);
        user.setId(1);
        user.setUserName("jack");
        String token = getToken("123", user);
        User user1 = new User();
        Map<String, Object> map = parseToken(token, new User());
        User obj = (User)map.get("obj");
        System.out.println(map.get("sub"));
        System.out.println(obj);
    }
}

1.3 注意

  • jwt过期会返回null
  • 设置jwt过期时间,参数是到期的时间点
  • jwt本质上就是将用户的个人信息加密,将加密后的信息通过cookie形式传递。
.setExpiration(new Date(System.currentTimeMillis() + Expire))

1.4 用法

  • getKey(“123456”);生成公私钥文件
  • getToken(String key,T t):传入对象实例,获得token
  • parseToken(String jwt, T t):传入jwt token,得到map,第一个是id,第二个是对象t。

1.5 jwt实战

1.5.1 过滤器判断jwt

  • jwt过期时间设置
设置一个字段用来保存jwt的过期时间
  • 放行登录和获取验证码的路径
获取验证码和登录请求进行放行,不需要进行jwt有效判断
  • 其他路径进行有效jwt的判断
判断jwt是否存在,jwt是否过期,出错的时候重定向到错误处理controller。
 request.getRequestDispatcher("/error/many/reLogin").forward(request, response);

1.5.2 过滤器注入Bean

  • 过滤器拿到service
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
        //从spring容器中取
        PdAuthUserServiceImpl pdAuthUserService = (PdAuthUserServiceImpl)ctx.getBean(PdAuthUserService.class);
  • 过滤器拿到StringRedisTemplate
  StringRedisTemplate redisTemplate = (StringRedisTemplate)ctx.getBean(StringRedisTemplate.class);

1.5.3 token刷新

  • 思路:用户登陆时给用于2个token,一个短token,一个长token,且在redis中以长token为key,存储长token的信息,过期时间与长token的过期时间一致,在过滤器验证时:
  • redis未取到长token的信息,则重新登陆
  • 短过期,长未过期,刷新短token,封装到response中,返回
  • 短未过期,长未过期,则不刷新token
  • 长过期,则重新登录

StringRedisTemplate/RedisTemplate设置过期时间

redisTemplate.opsForValue().set(pdAuthUser.getAccount()+"token","hhh",30*60, TimeUnit.SECONDS);

1.5.4 退出登录

  • 删除长token对应的在redis中的值,每次在过滤器中校验时,需要检验长token对应的在redis中的值,所以退出登录长token便会无效。

二、过滤器

参考: 拦截器与过滤器详解,使用方式与注意事项,使用场景以及区别与联系

2.1 原理

依赖于servlet容器。在实现上基于函数回调

2.2 使用场景

统一设置编码
过滤敏感字符
登录校验
URL级别的访问权限控制
数据压缩
Filter可以拦截所有请求,包括静态资源

2.3 使用步骤

2.3.1 自定义过滤器类implements Filter

package org.example.Basic;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class myFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("过滤器2初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("过滤器2前执行");
        HttpServletRequest request1 = (HttpServletRequest) request;
        System.out.println(request1.getRequestURI());
        System.out.println(request1.getRequestURL());
        chain.doFilter(request,response);
        System.out.println("过滤器2后执行");
    }

    @Override
    public void destroy() {
        System.out.println("过滤器2毁灭");
    }
}


2.3.2 配置类

  • 注册过滤器对象,并设置过滤器顺序。
package org.example.Basic;

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

@Configuration
public class MyFilterConfiguration {
    @Bean
    public FilterRegistrationBean filterRegistrationBean1(){
        //创建一个注册过滤器对象
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        //设置自定义过滤器
        registrationBean.setFilter(new myFilter1());
        //设置过滤拦截匹配规则,/*是匹配所有
//        registrationBean.addUrlPatterns("/*");
        //只拦截testController下面的接口
        registrationBean.addUrlPatterns("/cache/*");
        //存在多个过滤器时,设置执行顺序,值越大,执行顺序越靠后
        registrationBean.setOrder(1);
        //返回这个注册过滤器对象
        return registrationBean;
    }
    @Bean
    public FilterRegistrationBean filterRegistrationBean2(){
        //创建一个注册过滤器对象
        FilterRegistrationBean registrationBean1 = new FilterRegistrationBean();
        //设置自定义过滤器
        registrationBean1.setFilter(new myFilter2());
        //设置过滤拦截匹配规则,/*是匹配所有
//        registrationBean.addUrlPatterns("/*");
        //只拦截testController下面的接口
        registrationBean1.addUrlPatterns("/cache/*");
        //存在多个过滤器时,设置执行顺序,值越大,执行顺序越靠后
        registrationBean1.setOrder(2);
        //返回这个注册过滤器对象
        return registrationBean1;
    }
}

2.3.3 过滤器使用场景

  • 过滤参数,参数校验
  • 生成日志等等

2.4 问题

  • 过滤器的路径如果设置为/*可能会执行2次,有一次会访问/favicon.ico,设置访问路径即可解决。

2.5

三、拦截器

3.1 特点

  • 拦截器依赖于SpringMvc的,需要导入Mvc的依赖

preHandle() 在目标请求完成之前执行。有返回值Boolean类型,true:表示放行 postHandle()
在目标请求之完成后执行。 afterCompletion() 在整个请求完成之后【modelAndView已被渲染执行】。

  • 拦截器只能拦截action请求,不包括静态资源(有待验证)

  • 基于java反射机制实现

  • 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

3.2 使用

3.2.1 拦截器实现方式

  • AOP切面方式实现
  • 使用Spring的拦截器相关接口来自定义拦截器

3.3.1 自定义拦截器

3.3.2 拦截器配置信息

package org.example.Intecepter;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyInterceptorConfiguration implements WebMvcConfigurer {

    /**
     * 重写addCorsMappings()解决跨域问题
     * 配置:允许http请求进行跨域访问
     *
     * @param registry
     * @Author 有梦想的肥宅
     */
//    @Override
//    public void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**")//指哪些接口URL需要增加跨域设置
//                .allowedOrigins("*")//指的是前端哪些域名被允许跨域
//                .allowCredentials(true)//需要带cookie等凭证时,设置为true,就会把cookie的相关信息带上
//                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")//指的是允许哪些方法
//                .maxAge(3600);//cookie的失效时间,单位为秒(s),若设置为-1,则关闭浏览器就失效
//    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册Interceptor拦截器(Interceptor这个类是我们自己写的拦截器类)
        InterceptorRegistration registration = registry.addInterceptor(new MyIntercrptor());
        //addPathPatterns()方法添加需要拦截的路径
        registration.addPathPatterns("/**");                      //所有路径都被拦截
        //excludePathPatterns()方法添加不拦截的路径
        registration.excludePathPatterns(                         //添加不拦截路径
                "/demo/loginPage",            //登录页面的地址【不拦截】
                "/**/*.html",            //html静态资源
                "/**/*.js",              //js静态资源
                "/**/*.css"              //css静态资源
        );
    }
}

3.4 使用场景

  • 配置跨域
  • 登录校验

3.5 拦截器案例

3.5.1 preHandle方法获取所需参数

  • 执行方法名称
  • 是否有某个注解
  • 方法所需参数名称
package org.example.Intecepter;

import lombok.Data;
import org.checkerframework.checker.units.qual.A;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.invoke.MethodHandle;

@Data
public class MyIntercrptor implements HandlerInterceptor {
    //return true才进行下一步操作
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("使用拦截器1进行操作");
        HandlerMethod hand = (HandlerMethod)handler;
        //执行的方法名称
        System.out.println(hand.getMethod().getName());
        //获取执行的参数信息
        MethodParameter[] methodParameters = hand.getMethodParameters();
        for(MethodParameter methodParameter : methodParameters){
            System.out.println(methodParameter.getParameter().getName());
            System.out.println(methodParameter.getParameter().getType());
        }
        //判断类上有没有某个注解
        GetMapping annotation = hand.getBeanType().getAnnotation(GetMapping.class);
        boolean annotation1 = hand.getBeanType().isAnnotationPresent(GetMapping.class);
        //获取方法的注解,判断方法上有没该注解
        GetMapping annotation2 = hand.getMethod().getAnnotation(GetMapping.class);
        boolean annotationPresent = hand.getMethod().isAnnotationPresent(GetMapping.class);
        System.out.println(annotation1 + " " + annotationPresent);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//        HandlerMethod handler1 = (HandlerMethod) handler;
//        System.out.println(handler1.getReturnType().getMember().getName());
//        String viewName = modelAndView.getViewName();
//        System.out.println(viewName);


    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

3.6 执行过程

如果在preHandle()阶段就有某个拦截器校验不通过,会从上一个拦截器开始执行afterCompletion()进行返回

3.7 HandlerMethod类

spring mvc的HandlerMethod简介

四、aop

4.1 区别

Java AOP篇
过滤器,拦截器拦截的是URL。AOP拦截的是类的元数据(包、类、方法名、参数等)。
过滤器并没有定义业务用于执行逻辑前、后等,仅仅是请求到达就执行。
拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。
AOP针对具体的代码,能够实现更加复杂的业务逻辑。
三者功能类似,但各有优势,从过滤器 -> 拦截器 -> 切面,拦截规则越来越细致。
执行顺序依次是过滤器、拦截器、切面。

4.2 案例

  • 自定义注解
package org.example.AOP;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnn {
    String value() default "";
}

  • aop案例
package org.example.AOP;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.Event.OptLogDTO;
import org.example.Event.SysLogEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

@Component
@Aspect
public class MyAop {
    @Autowired
    private ApplicationContext applicationContext;
    private static final ThreadLocal<OptLogDTO> THREAD_LOCAL = new ThreadLocal<>();

    /***
     * 定义controller切入点拦截规则,拦截SysLog注解的方法
     */
    @Pointcut("@annotation(MyAnn)")
    public void myAnnTest() {

    }

    @Before(value = "myAnnTest()")
    public void doBefore(JoinPoint joinPoint) {
        //得到连接点执行的方法对象
        MethodSignature signature= (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //得到方法上的注解
        MyAnn annotation = method.getAnnotation(MyAnn.class);
        if (annotation!=null){
            //获取注解属性的value值
            String value = annotation.value();
            System.out.println("自定义注解的值" + " " + value);
        }

    }

    @Around(value = "myAnnTest()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        // 类名
        String className = joinPoint.getTarget().getClass().getName();
        System.out.println(className);
        //获取执行的方法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        System.out.println(methodName);
        //方法的返回类型
        String name = signature.getReturnType().getName();
        System.out.println(name);
        //获取方法的参数,这里的参数可能包含有文件
        Object[] args1 = joinPoint.getArgs();
        for (int i = 0; i < args1.length; i++) {
            System.out.println(args1.toString());
        }
        Object proceed = joinPoint.proceed();
        if(proceed instanceof String){
            System.out.println(proceed.toString());
        }
        System.out.println(response.getStatus());
        return proceed;
    }
}

package org.example.Service.controller.Aop.OptAop;

import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.Demo.util.JwtUtils;
import org.example.Demo.util.LoginInfo;
import org.example.Service.Util.DateUtil;
import org.example.Service.entity.PdAuthUser;
import org.example.Service.entity.PdCommonOptLog;
import org.example.Service.service.PdAuthUserService;
import org.example.Service.service.PdCommonLoginLogService;
import org.example.Service.service.impl.PdCommonOptLogServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;

@Component
@Aspect
public class OptLogAspect {
    @Autowired
    PdCommonOptLogServiceImpl pdCommonOptLogService;
    @Autowired
    PdAuthUserService pdAuthUserService;
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getOpt(){
    }
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping)")
    public void putOpt(){
    }
    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void postOpt(){
    }
    @Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void DelOpt(){
    }
    @Around(value = "getOpt()||putOpt()||postOpt()||DelOpt()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        LocalDateTime nowStart = LocalDateTime.now();
        String start = DateUtil.getNowDateStr(nowStart);
        Long startInstant = DateUtil.getNowInstant(nowStart);
        Object proceed = joinPoint.proceed();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        if(request.getRequestURL().toString().contains("login")){
            return proceed;
        }
        LocalDateTime nowEnd = LocalDateTime.now();
        String end = DateUtil.getNowDateStr(nowEnd);
        Long endInstant = DateUtil.getNowInstant(nowEnd);

        String token = request.getHeader("token");
        LoginInfo memberIdByJwtToken = JwtUtils.getMemberIdByJwtToken(token);
        if(memberIdByJwtToken == null){
            return proceed;
        }
        String account = memberIdByJwtToken.getAccount();
        System.out.println(account);
        PdAuthUser user = pdAuthUserService.getByAccout(account);
        PdCommonOptLog pdCommonOptLog = new PdCommonOptLog();
        pdCommonOptLog.setRequestIp(request.getRemoteHost());
        pdCommonOptLog.setUserName(user.getNamed());
        pdCommonOptLog.setType("OPT");
        pdCommonOptLog.setRequestUri(request.getRequestURI());
        Map<String, String[]> parameterMap = request.getParameterMap();
        StringBuilder param = new StringBuilder();
        parameterMap.forEach((k,v) -> {
            param.append(k).append(":").append(v).append(",");
        });
        pdCommonOptLog.setParams(new String(param));

        pdCommonOptLog.setStartTime(nowStart);
        pdCommonOptLog.setFinishTime(nowEnd);
        pdCommonOptLog.setConsumingTime(endInstant - startInstant);
        pdCommonOptLog.setUa(request.getHeader("User-Agent"));
        pdCommonOptLog.setCreateUser(user.getId());

        // 类名
        Class<?> aClass = joinPoint.getTarget().getClass();
        String className = aClass.getName();
        System.out.println(className);
        //获取执行的方法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取注解
        //Annotation[] annotations = method.getAnnotations();
        //获取指定注解
        ApiOperation annotation = method.getAnnotation(ApiOperation.class);
        //方法的返回类型
//        String name = signature.getReturnType().getName();
//        System.out.println(name);
        System.out.println(response.getStatus());
        pdCommonOptLog.setDescription1(annotation.value());
        pdCommonOptLog.setClassPath(aClass.getName());
        //方法名称
        pdCommonOptLog.setActionMethod(signature.getName());
        //请求类型
        pdCommonOptLog.setHttpMethod(request.getMethod());
        //获取方法的参数,这里的参数可能包含有文件
        Object[] args1 = joinPoint.getArgs();
        String string = "";
        System.out.println("返回值的返回类型" + response.getContentType());
        if(!(response == null) && !(response.getContentType() == null) && !(request.getContentType() == "") && !request.getContentType().contains("multipart/form-data")){
             string = JSONObject.toJSONString(args1);
        }
        pdCommonOptLog.setResult(string);
        pdCommonOptLog.setExDesc(null);
        pdCommonOptLogService.save(pdCommonOptLog);
        return proceed;
    }
}

五、自定义参数解析器

5.1 自定义注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentUser {
}

5.2 配置自定义参数构造器

package org.example.Args;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class ArgumentResolverConfiguration implements WebMvcConfigurer {
    @Override
    //注册自定义参数解析器
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new CurrentUserMethodArgumentResolver());
    }
}

5.3 自定义参数解析器类

package org.example.Args;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
    public CurrentUserMethodArgumentResolver() {
        System.out.println("CurrentUserMethodArgumentResolver自定义参数解析器初始化...");
    }

    //判断自定义注解 注解的参数是否正确
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //如果Controller的方法参数类型为User同时还加入了CurrentUser注解,则返回true
        if (parameter.getParameterType().equals(User.class) &&
                parameter.hasParameterAnnotation(CurrentUser.class)) {
            return true;
        }
        return false;
    }

    //当supportsParameter方法返回true时执行此方法
    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        System.out.println("参数解析器...");
        //此处直接模拟了一个User对象,实际项目中可能需要从请求头中获取登录用户的令牌然后进行解析,
        //最终封装成User对象返回即可,这样在Controller的方法形参就可以直接引用到User对象了
        User user = new User("jack","admin");

        return user;
    }
}

六、监听器

Spring监听器

6.1 自定义事件源

public class SysLogEvent extends ApplicationEvent {
    public SysLogEvent(OptLogDTO optLogDTO) {
        super(optLogDTO);
    }
}

6.2 自定义事件监听器

  • @EventListener(SysLogEvent.class)定义监听的类
@Component
public class SysLogListener {
    @Async//异步处理
    @EventListener(SysLogEvent.class)
    public void saveSysLog(SysLogEvent event) {
        OptLogDTO sysLog = (OptLogDTO) event.getSource();
        long id = Thread.currentThread().getId();
        System.out.println("监听到日志操作事件:" + sysLog + " 线程id:" + id);
        //将日志信息保存到数据库...
    }
}

6.3 发布事件

  • 可在aop中监听事件
@Autowired
    private ApplicationContext applicationContext;
//构造事件对象
        ApplicationEvent event = new SysLogEvent(logInfo);

        //发布事件
        applicationContext.publishEvent(event);

七、servlet对象

Servlet基础之HttpServletRequest详解

7.1 request获取参数

7.1.1 获取url上的参数

  • getParameter(String name)
  • getParameterMap()
http://localhost:9000/cache/hello?name=%22jack%22&&age=12
System.out.println("name参数" + request1.getParameter("name"));
        System.out.println("param参数");
        Map<String, String[]> parameterMap = request1.getParameterMap();
        parameterMap.forEach((k,v)-> {System.out.println(k + " " + v[0]);});

7.1.2 获取请求头参数

  • 请求头中会包含cookie信息,cookie : cookie=qwqdqdq; us=qweq
 //获取请求头参数
        String myHead = request1.getHeader("myHead");
        System.out.println("请求头参数" + myHead);
        Enumeration headerNames = request1.getHeaderNames();
        // 使用循环遍历所有请求头,并通过getHeader()方法获取一个指定名称的头字段
        while (headerNames.hasMoreElements()) {
            String headerName = (String) headerNames.nextElement();
            System.out.println((headerName + " : " + request1.getHeader(headerName)));
        }

7.1.3 请求体参数

7.1.3.1 form表单key-value数据
  • form-data,和获取url上的参数方式一致
  HttpServletRequest request1 = (HttpServletRequest) request;
        //获取请求体参数
        System.out.println("----------------------------");
        System.out.println(request1.getParameter("form2"));
        String[] form1s = request1.getParameterValues("form1");
        System.out.println(form1s[0]);
        Map<String, String[]> parameterMap = request1.getParameterMap();
        parameterMap.forEach((k,v)-> {System.out.println(k + " " + v[0]);});
7.1.3.1 form表单文件类型数据
HttpServletRequest req = (HttpServletRequest) request;
        System.out.println(request.getContentType());
        System.out.println(req.getCharacterEncoding());
        Collection<Part> parts = req.getParts();
        for (Part part:parts) {
            //提交文件添加的名称
            System.out.println("-----类型名称------->"+part.getName());
            System.out.println("-----类型------->"+part.getContentType());
            //文件的原名称
            System.out.println("-----提交的类型名称------->"+part.getSubmittedFileName());
            System.out.println("----流-------->"+part.getInputStream());
        }

7.1.4 获取cookie信息

Cookie[] cookies = request1.getCookies();
        for (int i = 0; i < cookies.length; i++) {
            System.out.println(cookies[i].getName() + " " + cookies[i].getValue());
        }

7.1.5 修改request参数信息

修改request的parameter的几种方式总结

  • 自定义MyRequestWrapMapper对象
package org.example.Basic;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class MyRequestWrapMapper extends HttpServletRequestWrapper {
    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public MyRequestWrapMapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String getParameter(String name) {
        String parameter = super.getParameter(name);
        if(parameter != null){
            parameter = parameter + "hahahah";
        }
        return parameter;
    }
}

  • 包装原对象即可,原对象是HttpServletRequest
new MyRequestWrapMapper(req)

7.2 response

Servlet之Response

在这里插入图片描述

7.2.1 常用方法

  • 过滤器前:只是新建了response对象,并赋予初值,经过controller后,才对其中的值进行了赋值。
Collection<String> headerNames1 = rsp.getHeaderNames();
        headerNames1.forEach(s-> System.out.println(s + " " + rsp.getHeader(s)));
        System.out.println(rsp.getStatus());
        System.out.println(rsp.getCharacterEncoding());

7.3 后端参数接收

7.3.1 url参数接收

  • @PathVariable url中带{}的参数
@GetMapping("/hello5/{id}")
    public String hello5(@PathVariable("id")int id){
        System.out.println(id);
        return "hello";
    }
  • url上的参数:?name=“xx”
  @GetMapping("/hello1")
    public String hello(@RequestParam("name")String name, @RequestParam("head")String head){
        System.out.println(name);
        System.out.println(head);
        return "hello";
    }

7.3.2 请求头参数接收

  • @RequestHeader
@GetMapping("/hello2")
    public String hello1(@RequestHeader("head")String head){
        System.out.println(head);
        return "hello";
    }

7.3.3 请求体参数接收

7.3.3.1 正常类型数据接收
  • @RequestParam
    也是上面那个注解
7.3.3.2 文件类型数据接收

SpringBoot项目中使用MultipartFile来上传文件(包含多文件)

  • @RequestParam
 @GetMapping("/hello")
    public String hello(@RequestParam("file") MultipartFile[] file,@RequestParam String body){
        for (int i = 0; i < file.length; i++) {
            MultipartFile multipartFile = file[i];
            System.out.println(multipartFile.getOriginalFilename());
            System.out.println(multipartFile.getName());
        }
        System.out.println(body);
        return "hello";
    }

7.3.4 获取cookie

  • @CookieValue:获取cookie参数
@GetMapping("/hello1")
    public String hello1(@CookieValue("name") String name ){
        System.out.println(name);
        return "hello";
    }

7.3.5 自定义传参使用

  • @RequestAttribute

 @PostMapping("/hello6")
    public String hello4(@RequestAttribute("num") String num){
        System.out.println(num);
        return "hello";
    }
  • request设置值
 req.setAttribute("num",12);

7.4 设置

7.4.1 大文件上传设置

  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

7.5 seesion和cookie

session和cookie的作用原理和区别
【JavaWeb】Cookie和Session

  • session技术就是一种基于后端有别于数据库的临时存储技术
  • session是基于cookie实现的,服务器存储每个会话的session信息,如何找到该session的信息呢?session通过cookie保存session的id信息来实现寻找同一个session对象。

7.5.1 区别

  • cookie存在浏览器端的浏览器缓存中,session存在服务端,服务端的内存中。
  • tomcat默认session的生命周期是20min,一旦session重新活跃,刷新生命周期。关闭浏览器,sessionID就会失效,但是服务器依然会存储session信息,session一般存放在服务器内存,如果服务器重启session则会清空。cookie也可以设置过期时间,但是关闭浏览器,cookie就失效。
  • cookie不是很安全,容易被窃取,session存储到服务端,不容易被窃取。

7.5.2 测试

  • response添加cookie
 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss");
            String format = formatter.format(LocalDateTime.now());
            String s = format.replaceAll(" ", "");
            System.out.println("设置cookie的值为" + " " + s);
            Cookie cookie = new Cookie("sessionID", s);
            //20s
            //cookie.setMaxAge(20*60);
            //cookie.setPath("/*");
            //response添加cookie
            rsp.addCookie(cookie);
  • 关闭浏览器,cookie失效
  • 设置session
 HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse rsp = (HttpServletResponse) response;
        HttpSession session = req.getSession();
        session.setAttribute("name","jack");
        System.out.println(session.getId());

7.5.3 重要概念

  • 会话:用户打开一个浏览器访问服务器,如果浏览器和服务器有一个关闭则会话结束,否则之间的所有操作都算一次会话。多个浏览器是多个会话。

八、spring mvc

8.1 spring mvc流程

SpringMVC工作流程(超级详细版)

8.1.1 过程

  • 用户发送请求,(调度服务器)dispatchServlet接收请求,转发请求到handermapping(处理器映射器),处理器映射器查找对应的handler,返回handler
  • dispatchServlet请求执行handler,处理器适配器执行handler,返回Modelandview对象
  • dispatchServlet请求解析Modelandview,viewresolver解析视图,返回view对象到dispatchServlet
  • dispatchServlet返回view对象

九、总结

9.1 总结

  • 过滤器对所有请求做增强,拦截器只对spring mvc的访问做增强
  • 过滤器依赖servlet容器,拦截器依赖spring mvc
  • 拦截器更强调对controller具体方法的前后做增强

十、valid参数校验

10.1 校验失败

  • 校验失败返回BindException
package com.example.seckilldemo.exception;

import com.example.seckilldemo.vo.RespBean;
import com.example.seckilldemo.vo.RespBeanEnum;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/**
 * 全局异常处理类
 *
 * @author: LC
 * @date 2022/3/2 5:33 下午
 * @ClassName: GlobalExceptionHandler
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public RespBean ExceptionHandler(Exception e) {
        if (e instanceof GlobalException) {
            GlobalException exception = (GlobalException) e;
            return RespBean.error(exception.getRespBeanEnum());
        } else if (e instanceof BindException) {
            BindException bindException = (BindException) e;
            RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
            respBean.setMessage("参数校验异常:" + bindException.getBindingResult().getAllErrors().get(0).getDefaultMessage());
            return respBean;
        }
        System.out.println("异常信息" + e);
        return RespBean.error(RespBeanEnum.ERROR);
    }
}

10.2 自定义注解

package com.example.seckilldemo.validator;

import com.example.seckilldemo.utils.ValidatorUtil;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.Validator;
import javax.validation.constraints.NotNull;
import java.lang.annotation.*;

/**
 * 验证手机号
 *
 * @author: LC
 * @date 2022/3/2 3:05 下午
 * @ClassName: isMobile
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = {
            IsMobileValidator.class
        }
)
public @interface IsMobile {
    //该字段必填

    boolean required() default true;

    String message() default "手机号码格式错误";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

10.3 实现ConstraintValidator接口

  • ConstraintValidator<IsMobile, String> :第一个参数是注解类型,第二个参数是校验数据的类型
package com.example.seckilldemo.validator;

import com.example.seckilldemo.utils.ValidatorUtil;
import org.thymeleaf.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 手机号码校验规则
 *
 * @author: LC
 * @date 2022/3/2 3:08 下午
 * @ClassName: IsMobileValidator
 */
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {

    private boolean required = false;

    @Override
    public void initialize(IsMobile constraintAnnotation) {
//        ConstraintValidator.super.initialize(constraintAnnotation);
        required = constraintAnnotation.required();
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (required) {
            return ValidatorUtil.isMobile(s);
        } else {
            if (StringUtils.isEmpty(s)) {
                return true;
            } else {
                return ValidatorUtil.isMobile(s);
            }
        }
    }
}

10.4 pom

  <dependency>
           <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

10.5 @Validated

  • 使用参数校验的参数需要添加@Validated注解才会生效
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 之 jwt,过滤器,拦截器,aop,监听器,参数校验 的相关文章

随机推荐

  • Spark中json字符串和DataFrame相互转换

    本文介绍基于Spark 2 0 的Json字符串和DataFrame相互转换 json字符串转DataFrame spark提供了将json字符串解析为DF的接口 如果不指定生成的DF的schema 默认spark会先扫码一遍给的json字
  • 【计算机视觉】CVPR 23 新论文

    文章目录 一 导读 二 背景 2 1 主要贡献 2 2 网络介绍 DeSTSeg 三 方法 3 1 Synthetic Anomaly Generation 合成异常生成 3 2 Denoising Student Teacher Netw
  • 子序列的判定算法c语言,求最大子序列的四种算法,数据结构与算法分析(C语言版)第二章...

    File Name maxSubSequence c Author Mail com Created Time 2015年07月18日 19 38 14 Description 求最大子序列的四种算法 数据结构与算法分析 C语言版 第二章
  • UE4材质节点

    材质输入引脚 材质中最为关键的是作为最终输出结果的引脚 根据情况的不同有的会使用 有的并不会被使用 基础颜色 Base Color 定义材质的颜色 接受参数为Vector3 RGB 颜色采用float形式 任何超出范围的输入数值都将被cla
  • 数据结构之线索二叉树详细解释

    1 1 线索二叉树的原理 我们现在倡导节约型社会 一切都应该以节约为本 但当我们创建二叉树时我们会发现其中一共有两个指针域 有的指针域指向的结构为空 这也就浪费了很多空间 所以为了不去浪费这些空间我们采取了一个措施 就是利用那些空地址 存放
  • mysql的慢sql优化

    慢sql优化 优化慢sql 最常见的就是添加索引 查询语句中不要使用select 尽量减少子查询 使用关联查询 left join right join inner join 替代 减少使用IN或者NOT IN 使用exists not e
  • TCP和UDP的区别和优缺点

    TCP与UDP区别 1连接方式 TCP 面向连接 UDP UDP是无连接的 即发送数据之前不需要建立连接 2提供服务 TCP 可靠的服务 传送的数据 无差错 不丢失 不重复 且按序到达 通过校验和 重传控制 序号标识 滑动窗口 确认应答实现
  • flask:g对象

    flask中的g对象是一个全局对象存在于应用上下文 但是仅仅存在于一次请求之中 即 请求开始flask收到请求到该请求结束返回结果这一段时间 g对象可以畅游flask全局 demo from flask import Flask from
  • git push错误failed to push some refs to的解决

    问题说明 当我们在github版本库中发现一个问题后 你在github上对它进行了在线的修改 或者你直接在github上的某个库中添加readme文件或者其他什么文件 但是没有对本地库进行同步 这个时候当你再次有commit想要从本地库提交
  • vue3-vant4-vite-pinia-axios-less学习日记

    代码地址 GitHub vue3 vant4 vite pinia axios less 效果如图 1 首页为导航栏 2 绑定英雄页 3 注册页 4 英雄列表页 5 后面不截图了 没啥了 模块 1 vant4 按需引入组件样式文档 2 安装
  • PCB相关知识-元器件+原理图

    文章目录 元器件Component 原理图Schematic 元器件Component 这里说的元器件指的是在原理图中使用的元器件符号 一个电阻 电容 电感 连接器 IC等都是一个元器件 元器件在原理图中只是一个电气符号 形状不定 比如可以
  • Windows下Win32 Disk Imager和Linux中dd命令两种方式烧录树莓派镜像

    下了好久的决心 终于买了一块树莓派 本着已是装机老手的心态 没有买装好系统的sd卡 自己买了一张准备自己装 结果很艰辛 最常见的装树莓派操作系统的方式就是 Win32 Disk Imager 再去领快递的前几个小时 我已经把前戏做的很足 镜
  • Dynamics 365详解

    什么是Dynamics 365 Dynamics 365是微软公司推出的一款企业资源计划 ERP 和客户关系管理 CRM 软件 它是微软旗下的云计算平台Azure上的一项服务 能够在多个设备和平台上运行 Dynamics 365结合了ERP
  • leetcode分类刷题:队列(Queue)(三、优先队列用于归并排序)

    1 当TopK问题出现在多个有序序列中时 就要用到归并排序的思想了 2 将优先队列初始化为添加多个有序序列的首元素的形式 再循环K次优先队列的出队和出队元素对应序列下个元素的入队 就能得到TopK的元素了 3 这些题目好像没有TopK 大用
  • Python--Email

    邮件定义 电子邮件 消息由头域 统称消息头 以及后面可选的消息体组成 根据 RFC 2822 唯一需要的消息标题只有发送日期字段和发送地址字段 即 Date 和 From MAIL FROM RCPT TO DATA 1 电子邮件系统组件和
  • Jbox2D入门学习一物理世界及最简单的物体创建

    这周末无聊 翻到舍友有本游戏开发的书 就浏览了遍 因为对游戏以前其实没接触过 可能就简单知道一边游戏绘图和逻辑以及游戏框架绘制的简单概念 这次主要是看了下游戏开发中最常用和基础的一个物理引擎 Box2D 对于这个引擎 可能说最好表现和表达出
  • Spring Boot彩色日志配置

    在spring boot中使用彩色日志 spring boot是默认支持彩色日志的 但是由于我又添加了自己的logback日志配置文件 然后就没有了彩色日志 经过一番搜索大法找到了一个完美还原spring boot的彩色日志 下面是logb
  • 华为OD机试 - 数字加减游戏(Java)

    题目描述 小明在玩一个数字加减游戏 只使用加法或者减法 将一个数字s变成数字t 每个回合 小明可以用当前的数字加上或减去一个数字 现在有两种数字可以用来加减 分别为a b a b 其中b没有使用次数限制 请问小明最少可以用多少次a 才能将数
  • 技术流

    如果让你向别人推荐十部电影 你会推荐哪十部 这是在知乎上被浏览过 2000 万的问题 一共有 5036 个回答 花 5 分钟读完这篇文章 可以帮你节省99 的找电影时间 全是知乎上最值得推荐的电影 并最后让你获取到几百万人收藏的超级电影名单
  • Spring 之 jwt,过滤器,拦截器,aop,监听器,参数校验

    Spring 之 jwt 过滤器 拦截器 aop 监听器 一 jwt编写 1 1 pom 1 2 JwtUtils 1 3 注意 1 4 用法 1 5 jwt实战 1 5 1 过滤器判断jwt 1 5 2 过滤器注入Bean 1 5 3 t