java 过滤器 接口(API)验证入参,验签(sign) Demo

java 过滤器 接口(API)验证入参,验签(sign) Demo

一、思路
1、配置yml文件;
2、创建加载配置文件类;
3、继承 OncePerRequestFilter 重写方法 doFilterInternal;
4、注册自定义过滤器;


二、步骤

1、配置yml文件;
###系统签名验证配置
biw:
  ###过滤器开关是否打开
  enable: true
  ###过滤器-验签秘钥(自定义一个字符串)
  securityKey: ccf12f15155c9c564daf1783a6f65f69a4a0
  ###过滤器-URL
  urlPathPatterns: /test/test001/*,/com/baidu006/*

2、创建加载配置文件类;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @Author 
 * @create 2023/07/18
 */
@Data
@Component
@ConfigurationProperties(prefix = "biw")
public class BiwConfig {

    /**
     * 系统通讯密钥
     */
    private String securityKey;

    /**
     * 签名验证URL路径
     */
    private String urlPathPatterns;

    /**
     * 是否开启签名验证
     */
    private Boolean enable;

}

3、继承 OncePerRequestFilter 重写方法 doFilterInternal;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.test.baidu.ResultCodeEnum;
import com.test.baidu.config.BiwConfig;
import com.test.baidu.common.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

/**
 * BIW系统接口鉴权过滤器
 *
 * @Author 
 * @Date 2023/07/18
 */
@Slf4j
@Component
public class BiwSignFilter extends OncePerRequestFilter {

    @Autowired
    private BiwConfig biwConfig;

    private final String SIGN_FIELD_NAME = "sign";
    private final String KEY_FIELD_NAME = "key";


    /**
     * doFilterInternal
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        try {
            ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);

            // 判断签名验证开关是否开启
            if (!biwConfig.getEnable()) {
                filterChain.doFilter(requestWrapper, response);
                return;
            }

            String bodyText = this.readHttpBody(requestWrapper);
            log.info("[系统接口鉴权]body内容: {}", bodyText);
            JSONObject jsonBody = JSONObject.parseObject(bodyText);
            Object signRequest = jsonBody.get(SIGN_FIELD_NAME);
            if (biwConfig.getEnable() && null == signRequest) {
                log.error("签名信息不存在");
                this.doReturn(response, Result.createError(ResultCodeEnum.SIGN_NOT_EXISTS.getCode(), ResultCodeEnum.SIGN_NOT_EXISTS.getMessage()));
                return;
            }
            String sign = this.signMD5(jsonBody);
            // 验证签名
            if (biwConfig.getEnable() && !sign.equals(signRequest)) {
                log.error("签名验证失败, 签名计算值 {} 签名请求值{} body内容{}", sign, signRequest, bodyText);
                this.doReturn(response, Result.createError(ResultCodeEnum.SIGN_ERROR.getCode(), ResultCodeEnum.SIGN_ERROR.getMessage()));
                return;
            }

            filterChain.doFilter(requestWrapper, response);

        } catch (Exception e) {
            log.error("签名验证异常", e);
            this.doReturn(response, Result.create500Error(e.getMessage()));
            return;
        }
    }


    /**
     * readHttpBody
     *
     * @param requestWrapper
     * @return
     * @throws IOException
     */
    private String readHttpBody(ServletRequest requestWrapper) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(requestWrapper.getInputStream(), Charset.forName("UTF-8")));
        String line = "";
        StringBuilder sb = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    }


    /**
     * doReturn
     *
     * @param response
     * @param result
     * @throws IOException
     */
    private void doReturn(HttpServletResponse response, Result result) throws IOException {
        ServletOutputStream out = response.getOutputStream();
        out.write(JSON.toJSONString(result).getBytes());
        out.flush();
    }


    /**
     * signMD5 : MD5签名加密
     *
     * @param jsonObject
     * @return
     */
    public String signMD5(JSONObject jsonObject) {
        Iterator it = jsonObject.getInnerMap().keySet().iterator();
        Map<String, Object> map = new TreeMap<String, Object>();
        StringBuilder signSb = new StringBuilder();
        while (it.hasNext()) {
            Object key = it.next();
            Object value = jsonObject.get(key);
            if (SIGN_FIELD_NAME.equals(key)) {
                continue;
            }
            map.put(key.toString(), value);
        }
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            signSb.append(entry.getKey());
            signSb.append("=");
            signSb.append(entry.getValue());
            signSb.append("&");
        }
        signSb.append(KEY_FIELD_NAME).append("=").append(biwConfig.getSecurityKey());
        String sign = DigestUtils.md5Hex(signSb.toString()).toUpperCase();
        return sign;
    }

    /**
     * 生成签名
     */
    public static void main(String[] args) {
        String json = "{\"endTime\":\"2023-07-01 08:00:00\",\"startTime\":\"2023-07-01 00:00:00\",\"pageNum\":1,\"pageSize\":50,\"requestId\":\"test001\"}";
        JSONObject jsonObject = JSON.parseObject(json);
        Iterator it = jsonObject.getInnerMap().keySet().iterator();
        Map<String, Object> map = new TreeMap<String, Object>();
        StringBuilder signSb = new StringBuilder();
        while (it.hasNext()) {
            Object key = it.next();
            Object value = jsonObject.get(key);
            if ("sign".equals(key)) {
                continue;
            }
            map.put(key.toString(), value);
        }
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            signSb.append(entry.getKey());
            signSb.append("=");
            signSb.append(entry.getValue());
            signSb.append("&");
        }
        signSb.append("key").append("=").append("ccf12f15155c9c564daf1783a6f65f69a4a0");
        String sign = DigestUtils.md5Hex(signSb.toString()).toUpperCase();
        System.out.println(sign);
    }

}

4、注册自定义过滤器;

import com.test.baidu.config.BiwConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author 
 * @date 2023/07/18
 * @description: 系统过滤配置
 */
@Configuration
public class BiwFilterConfig {

    @Autowired
    private BiwSignFilter biwSignFilter;
    @Autowired
    private BiwConfig biwConfig;


    /**
     * biwBillPullFilterConfig
     * 数据-签名过滤
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<BiwSignFilter> biwBillPullFilterConfig() {
        FilterRegistrationBean<BiwSignFilter> registration = new FilterRegistrationBean<>();
        // 注册自定义过滤器
        registration.setFilter(biwSignFilter);
        // 过滤所有路径
        // registration.addUrlPatterns(biwConfig.getUrlPathPatterns().split(","));
        registration.addUrlPatterns(biwConfig.getUrlPathPatterns());
        // 过滤器名称
        registration.setName("biwParametersFilter");
        // 优先级,越低越优先
        registration.setOrder(1);
        return registration;
    }

}

5、每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中

解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题

import org.apache.commons.io.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;


/**
 * @Author 
 * @create 2023/07/18
 */
public class RequestWrapper extends HttpServletRequestWrapper {

    private byte[] requestBody;

    private HttpServletRequest request;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.request = request;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        /**
         * 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中
         * 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题
         */
        if (null == this.requestBody) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), baos);
            this.requestBody = baos.toByteArray();
        }

        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {

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

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

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() {
                return bais.read();
            }
        };
    }

    public byte[] getRequestBody() {
        return requestBody;
    }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值