• 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 map = new TreeMap();
            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 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 map = new TreeMap();
            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 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 biwBillPullFilterConfig() {
            FilterRegistrationBean 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()));
        }
    }
    

  • 相关阅读:
    从 Hadoop 到云原生, 大数据平台如何做存算分离
    MySQL视图
    SpringBoot 自定义异常@ContollerAdvice ExceptionHandler不起作用
    linux之基础shell脚本编程4 字符串操作,变量赋值,配置用户环境
    《DevOps实践指南》- 读书笔记(四)
    BUUCTF题解之[极客大挑战 2019]Havefun 1
    python接口自动化封装导出excel方法和读写excel数据
    Java自动装箱与自动拆箱
    FRR+BFD+OSPF与BGP联动
    web系统字典统一中文翻译问题
  • 原文地址:https://blog.csdn.net/java_2017_csdn/article/details/132691537