过滤器 + 签名拦截器 + 签名工具类

1. 拦截器和过滤器的概述

过滤器与拦截器的执行流程:

  • 过滤器implements Filter
    • 作用:
      • 拦截配置好的客户端请求,然后对Request和Response进行处理(对字符编码、跨域等问题进行过滤)
      • 随着web应用的启动而启动,只初始化一次
    • 方法:
      • init 容器启动时调用初始化方法,只会初始化一次
      • doFilter 每次请求都会调用doFilter方法,通过FilterChain 调用后续的方法
      • destroy 当容器销毁时,执行destory方法,只会被调用一次
  • 拦截器implements HandlerInterceptor
    • 作用
      • 对请求进行拦截,比如说验证请求的登录账号和密码,或者验证请求参数签名等
    • 方法:
      • preHandle 请求方法前置拦截,当注册的所有拦截器的 preHandle 执行完毕之后, DispatcherServlet 会根据返回结果来分配具体的 hanler 处理请求

      • postHandle preHandle 返回结果为true时,在Controller方法执行之后,视图渲染之前被调用

      • afterCompletionpreHandle 返回ture,并且整个请求结束之后,执行该方法

    • 注册
      • 编写一个类 implements WebMvcConfigurer
      • addInterceptors 中注册
        • addPathPatterns 需要拦截的请求
        • excludePathPatterns 不需要拦截的请求

2. 存储HttpServletRequest的输入流

因为 HttpServletRequest的输入流只能读取一次 , 而工程中会有多个地方需要获取 HttpServletRequest 的信息,所以需要先存储 HttpServletRequest的输入流

1.http请求包装类

HttpServletRequestWrapper 一个http请求包装类。

  • 使用方法:
    • extends HttpServletRequestWrapper
    • 实例化一个容器来存储流数据(数组或集合)
    • 重写getInputStream() 读取请求正文
    • 重写getReader() 读取请求正文

代码:

package com.chenjy.transfer.common.conf;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;

/**
 * @Author: chenJY
 * @Description:
 * @Date: 2022-10-24 10:37
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    // 存储流的容器
    private byte[] requestBody =null;

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 将流复制到字节数组 requestBody 中
        requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    /**
     * @Description 获取请求体
     * @author chenJY
     * @date 2022/10/24 14:21
     * @param request
     * @return String
    */
    public String getBodyString(final ServletRequest request) {
        try {
            return inputStream2String(request.getInputStream());
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * @Description 获取请求体
     * @author chenJY
     * @date 2022/10/24 14:23
     * @return String
    */
    public String getBodyString() {
        final InputStream inputStream = new ByteArrayInputStream(requestBody);

        return inputStream2String(inputStream);
    }

    /**
     * @Description 读取inputStream数据,并转换为String
     * @author chenJY
     * @date 2022/10/24 14:24
     * @param inputStream
     * @return String
    */
    private String inputStream2String(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("异常:", e);
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }

        return sb.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                log.info("保存输入流......");
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

}

2.过滤器

除了要写一个包装器外,我们还需要在过滤器里将原生的 HttpServletRequest 对象替换成我们自定义的RequestWrapper对象。

package com.chenjy.transfer.common.filter;

import com.chenjy.transfer.common.conf.RequestWrapper;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @Author: chenJY
 * @Description:
 * @Date: 2022-10-24 9:41
 */
@Slf4j
@WebFilter(urlPatterns = "/*")
public class RequestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("过滤器初始化......");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);
        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy() {
        log.info("过滤器销毁......");
    }
}

3. 签名生成

1.常量类

package com.chenjy.transfer.constant;

/**
 * @Author: chenJY
 * @Description: 签名验证相关常量
 * @Date: 2022-10-24 11:18
 */
public interface MyConstant {
    // 签名的key(参数名)
    String SIGN_KEY = "MySignKey";
    // 签名的标志
    String SIGN_FLAG = "MySFlag";
    // 签名后缀内容
    String SIGN_STR = "zuilitiaodengkanjian_XQJ";
}

2.签名工具类

package com.chenjy.transfer.common.util;

import com.chenjy.transfer.constant.MyConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Map;

/**
 * @Author: chenJY
 * @Description: 签名工具类(生成签名、签名加密)
 * @Date: 2022-10-24 11:21
 */
@Slf4j
public class SignUtil {

    /**
     * @Description 签名生成逻辑 (请求发送方法):
     *                  - 获取参数key,排序key(防止因参数顺序问题而导致签名不一致)
     *                  - 拼接key和value,并在末尾拼接上后缀内容 SIGN_STR
     *                  - 对拼接好的字符串进行 md5 加密
     * @author chenJY
     * @date 2022/10/24 11:24
     * @param params
     * @return String
    */
    public static String createSign(Map<String, Object> params) throws Exception {
        log.info("开始拼接签名......");
        StringBuilder ketStr = new StringBuilder();
        Object[] keys = params.keySet().toArray();
        Arrays.sort(keys);
        for (Object key : keys) {
            String valueStr = "";
            Object value = params.get(key);
            if (value != "") {
                valueStr = String.valueOf(value);
            }
            ketStr.append(key)
                    .append("=")
                    .append(valueStr)
                    .append("——");
        }
        ketStr.append(MyConstant.SIGN_STR);
        log.info("待验签名串:" + ketStr);
        return md5(ketStr.toString());
    }

    public static String md5(String str) throws Exception{
        // 指定加密类型
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        // 将字节数组转换为表示每个字节的十六进制值的字符串
        return Hex.encodeHexString(messageDigest.digest(str.getBytes(StandardCharsets.UTF_8)));
    }
}

4.签名拦截器

1.拦截器类

package com.chenjy.transfer.common.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.chenjy.transfer.common.conf.RequestWrapper;
import com.chenjy.transfer.common.util.SignUtil;
import com.chenjy.transfer.constant.MyConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @Author: chenJY
 * @Description:
 * @Date: 2022-10-24 11:13
 */
@Slf4j
@Component
public class SignInterceptor implements HandlerInterceptor {
    /**
     * @Description 拦截器的前置处理器
     * @author chenJY
     * @date 2022/10/24 14:38
     * @param request
     * @param response
     * @param handler
     * @return boolean
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String strParam = new RequestWrapper(request).getBodyString();
        Map<String, Object> paramsMap = str2Map(strParam);
        if (paramsMap.get(MyConstant.SIGN_FLAG) == null) {
            log.error("参数未签名");
            return false;
        }
        if (!verifySign(paramsMap)) {
            log.error("签名错误");
            return false;
        }

        log.info("签名验证通过");
        return true;
    }

    /**
     * @Description 验证签名是否一致
     * @author chenJY
     * @date 2022/10/24 14:43
     * @param paramsMap
     * @return boolean
    */
    public boolean verifySign(Map<String, Object> paramsMap) throws Exception {
        Set pNames = paramsMap.keySet();
        Iterator it = pNames.iterator();
        Map<String, Object> newParams = new HashMap<>();
        String originSign = paramsMap.get(MyConstant.SIGN_KEY).toString();
        while (it.hasNext()) {
            String pName = (String) it.next();
            if (MyConstant.SIGN_KEY.equals(pName)) {
                continue;
            }
            Object pValue = paramsMap.get(pName);
            newParams.put(pName, pValue);
        }
        String sign = SignUtil.createSign(newParams);
        log.info("sign-result:" + sign);
        return sign.equalsIgnoreCase(originSign);
    }

    /**
     * @Description json字符串转Map
     * @author chenJY
     * @date 2022/10/24 14:32
     * @param str
     * @return Map<Object>
    */
    public Map<String, Object> str2Map(String str) {
        /*先转换为JSON对象*/
        JSONObject jsonParam = JSONObject.parseObject(str);
        Map<String, Object> resMap = new HashMap<>();
        Iterator it = jsonParam.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
            resMap.put(entry.getKey(), entry.getValue());
        }
        return resMap;
    }
}

2.注册拦截器

实现WebMvcConfigurer接口中的addInterceptors方法把自定义的拦截器类添加进来

package com.chenjy.transfer.common.conf;

import com.chenjy.transfer.common.interceptor.SignInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author: chenJY
 * @Description:
 * @Date: 2022-10-24 14:48
 */
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Bean
    public HandlerInterceptor getSignInterceptor() {
        return new SignInterceptor();
    }

    /**
     * @Description 注册拦截器,声明要拦截的url
     * @author chenJY
     * @date 2022/10/24 14:50
     * @param registry
    */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 签名拦截
        InterceptorRegistration signInterceptor = registry.addInterceptor(getSignInterceptor());
        signInterceptor.addPathPatterns("/verify/**");
    }
}

5.项目依赖

        <!--基础依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--提供Slf4j日志-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--fastjson:json与java object数据转换-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <!--十六进制转换(加密使用)-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

364.99°

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值