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

  • Post author:
  • Post category:其他




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


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



  • 过滤器



    implements Filter

    • 作用:

      • 拦截配置好的客户端请求,然后对Request和Response进行处理(对字符编码、跨域等问题进行过滤)
      • 随着web应用的启动而启动,只初始化一次
    • 方法:


      • init

        容器启动时调用初始化方法,只会初始化一次

      • doFilter

        每次请求都会调用doFilter方法,通过FilterChain 调用后续的方法

      • destroy

        当容器销毁时,执行destory方法,只会被调用一次

  • 拦截器



    implements HandlerInterceptor

    • 作用

      • 对请求进行拦截,比如说验证请求的登录账号和密码,或者验证请求参数签名等
    • 方法:


      • preHandle

        请求方法前置拦截,当注册的所有拦截器的

        preHandle

        执行完毕之后,

        DispatcherServlet

        会根据返回结果来分配具体的

        hanler

        处理请求


      • postHandle


        preHandle

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


      • afterCompletion



        preHandle

        返回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>



版权声明:本文为m0_54355172原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。