• 通过过滤器、拦截器实现公共验证签名以及时间戳功能


    需求背景

    最近我们在做一个开放平台,将公司的能力接口通过此平台暴露出去,给外部公司使用,然后收取费用。那么在对接外部公司的时候,就会涉及到接口参数签名以及验证时间戳。如果每个接口都手动去校验,毫无疑问非常的繁琐,因此优化了一下,通过filter以及interceptor来实现公共校验。

    代码

    1、过滤器

    为啥直接通过拦截器无法实现,因为request的输入流只能读取一次因为流对应的是数据,数据放在内存中,有的是部分放在内存中。read 一次标记一次当前位置(mark position),第二次read就从标记位置继续读(从内存中copy)数据。 所以这就是为什么读了一次第二次是空了。 怎么让它不为空呢?只要inputstream 中的pos 变成0就可以重写读取当前内存中的数据。javaAPI中有一个方法public void reset() 这个方法就是可以重置pos为起始位置,但是不是所有的IO读取流都可以调用该方法!ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream()。

    因此通过过滤器里面进行一层包装,包装拿到了request中的请求数据,并且原来的request继续往后传递,可以理解成做了一个拷贝。

    HttpServletRequestReplacedFilter

    package com.yzf.enterprise.open.platform.bff.security.sign.filter;
    
    import com.yzf.enterprise.open.platform.bff.security.sign.wrapper.RequestWrapper;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    @Component
    public class HttpServletRequestReplacedFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            ServletRequest requestWrapper = null;
            if(request instanceof HttpServletRequest) {
                requestWrapper = new RequestWrapper((HttpServletRequest) request);
            }
            //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
            // 在chain.doFiler方法中传递新的request对象
            if(requestWrapper == null) {
                chain.doFilter(request, response);
            } else {
                chain.doFilter(requestWrapper, response);
            }
        }
    
        @Override
        public void destroy() {
    
        }
    }

    RequestWrapper

    package com.yzf.enterprise.open.platform.bff.security.sign.wrapper;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.*;
    
    /**
     * 由于request数据读取一次就无法读取,因此通过包装类来解决
     */
    public class RequestWrapper extends HttpServletRequestWrapper {
        private final String body;
    
        private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapper.class);
    
        public RequestWrapper(HttpServletRequest request) {
            /**
             * 由于继承了HttpServletRequestWrapper,HttpServletRequestWrapper又继承了ServletRequestWrapper,ServletRequestWrapper
             * 中有一个private ServletRequest request;也就是将原来的request做了一个备份,具体读到的数据放在body中
             */
            super(request);
            StringBuilder stringBuilder = new StringBuilder();
            BufferedReader bufferedReader = null;
            InputStream inputStream = null;
            try {
                inputStream = request.getInputStream();
                if (inputStream != null) {
                    bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    char[] charBuffer = new char[128];
                    int bytesRead = -1;
                    while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                        stringBuilder.append(charBuffer, 0, bytesRead);
                    }
                } else {
                    stringBuilder.append("");
                }
            } catch (Exception ex) {
                LOGGER.error("过滤器request请求包装时出现异常", ex);
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    }
                    catch (IOException e) {
                        LOGGER.error("过滤器request请求包装关闭流出现异常", e);
                    }
                }
                if (bufferedReader != null) {
                    try {
                        bufferedReader.close();
                    }
                    catch (IOException e) {
                        LOGGER.error("过滤器request请求包装关闭流出现异常", e);
                    }
                }
            }
            body = stringBuilder.toString();
            LOGGER.info("过滤器request请求包装结果为:" + body);
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
            ServletInputStream servletInputStream = new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }
                @Override
                public boolean isReady() {
                    return false;
                }
                @Override
                public void setReadListener(ReadListener readListener) {
                }
                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
            return servletInputStream;
    
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(this.getInputStream()));
        }
    
        public String getBody() {
            return this.body;
        }
    }

    FilterRegisterConfig

    然后将filter注册到容器中-----基于springboot项目

    package com.yzf.enterprise.open.platform.bff.security.sign.filter;
    
    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;
    
    @Configuration
    public class FilterRegisterConfig {
    
        @Autowired
        private HttpServletRequestReplacedFilter httpServletRequestReplacedFilter;
    
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter(httpServletRequestReplacedFilter);
            //拦截所有的请求,给每个请求都包装一下,拦截器中再判断是否需要拦截处理
            registrationBean.addUrlPatterns("/*");
            //给自定义的filter设置顺序,值越小,优先级越高,建议可以稍微高一些,防止影响框架的一些filter
            registrationBean.setOrder(10);
            return registrationBean;
        }
    }

    2、拦截器

    SignInterceptor

    package com.yzf.enterprise.open.platform.bff.security.sign.interceptor;
    
    import com.alibaba.fastjson.JSONObject;
    import com.yzf.accounting.common.base.AjaxResult;
    import com.yzf.accounting.common.exception.BizRuntimeException;
    import com.yzf.enterprise.open.platform.bff.security.sign.wrapper.RequestWrapper;
    import com.yzf.enterprise.open.platform.client.api.OpAccessInfoClient;
    import com.yzf.enterprise.open.platform.client.dto.OpAccessInfoDto;
    import com.yzf.enterprise.open.platform.common.exception.BusinessErrorCode;
    import com.yzf.enterprise.open.platform.common.utils.SignUtil;
    import com.yzf.enterprise.open.platform.common.utils.StringUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 统一处理过滤请求:校验签名是否合法、校验时间戳是否超过30s
     */
    @Component
    @Slf4j
    public class SignInterceptor implements HandlerInterceptor {
    
        private static final Logger logger = LoggerFactory.getLogger(SignInterceptor.class);
    
        //目标方法执行之前
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            RequestWrapper requestWrapper;
            /**
             * 统一处理业务接口验签以及验证时间戳操作
             */

          if(request instanceof RequestWrapper) {
            requestWrapper = (RequestWrapper) request;
          }else {
            requestWrapper = new RequestWrapper(request);  
          }

    
            String requestBody = requestWrapper.getBody();
            if(StringUtils.isBlank(requestBody)) {
                return true;
            }
    
            JSONObject jsonObject = null;
            try {
                jsonObject = JSONObject.parseObject(requestBody);
                logger.info("拦截器中请求参数格式化后为:" + jsonObject.toJSONString());
            }catch(Exception ex) {
                logger.error("签名时间戳全局拦截器请求消息转化出现异常", ex);
                throw new BizRuntimeException(BusinessErrorCode.ERROR_PARAM);
            }
            
            Long timestamp = jsonObject.getLong("timestamp");
            String sign = jsonObject.getString("sign");
            if (timestamp == null || StringUtils.isBlank(sign)) {
                throw new BizRuntimeException(BusinessErrorCode.ERROR_PARAM);
            }
    
            /**
             * 校验时间戳
             */
            if (!SignUtil.validateTimestamp(timestamp)) {
                throw new BizRuntimeException(BusinessErrorCode.TIMESTAMP_VALID_ERROR);
            }
    
            /**
             * 校验参数签名
             */
            if (!SignUtil.validateSign(jsonObject, "参与签名的秘钥secret", sign)) {
                throw new BizRuntimeException(BusinessErrorCode.SIGN_VALID_ERROR);
            }
    
            return true;
        }
    }

    CustomMvcConfig

    package com.yzf.enterprise.open.platform.bff.security.sign.interceptor;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    @Configuration
    public class CustomMvcConfig extends WebMvcConfigurationSupport {
    
        @Autowired
        private SignInterceptor signInterceptor;
    
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**").addResourceLocations("classpath:/js/");
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(signInterceptor)
                    .addPathPatterns("/**")
                    .excludePathPatterns("/access/**",
                                         "/getToken",
                                         "/profile/**",
                                         "/swagger-ui.html",
                                         "/swagger-resources/**",
                                         "/webjars/**",
                                         "/*/api-docs",
                                         "/favicon.ico",
                                         "/actuator/**",
                                         "/error");
        }
    }

    SignUtil

    package com.yzf.enterprise.open.platform.common.utils;
    
    import com.alibaba.fastjson.JSON;
    import com.yzf.enterprise.open.platform.common.annotation.SignIgnore;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Date;
    import java.util.Map;
    
    /**
     * 签名工具类
     */
    public class SignUtil {
        /**
         * 日志
         */
        private static final Logger LOGGER = LoggerFactory.getLogger(SignUtil.class);
        /**
         * 编码
         */
        private static final String CHARSET = "utf-8";
    
        /**
         * @param parameters
         * @param accessSecret
         * @param sign
         * @return 注:当接口入参为DTO对象时,使用此方法进行验证签名
         */
        public static boolean validateSign(Object parameters, String accessSecret, String sign) {
            LOGGER.info("validateSign map:{}", JSON.toJSONString(parameters));
            boolean flag = false;
            if (parameters == null) {
                return flag;
            }
            String mySign = getSignByObj(parameters, accessSecret);
            // 验证签名是否一致
            if (mySign.equals(sign)) {
                flag = true;
            }
            return flag;
        }
    
        /**
         * 校验签名timestamp与当前时间是否超过30s
         * 注:true未过期;false过期
         *
         * @param timestamp
         * @return
         */
        public static boolean validateTimestamp(Long timestamp) {
            if (timestamp == null) {
                return false;
            }
    
            Date date = new Date(timestamp);
            return !date.before(new Date(System.currentTimeMillis() - 30000));
        }
    
        /**
         * 签名算法
         *
         * @param o 要参与签名的数据对象
         * @return 签名,使用sha256
         * @throws IllegalAccessException
         */
        public static String getSignByObj(Object o, String key) {
            String result = "";
    
            try {
                ArrayList<String> list = new ArrayList<>();
                Class cls = o.getClass();
                /**
                 * 由于class.getDeclaredFields()无法获取父类的字段,因此通过循环的方式获取其所有父类,并排除掉object类字段
                 */
                while(cls != null && !cls.getName().toLowerCase().equals("java.lang.object")) {
                    Field[] fields = cls.getDeclaredFields();
                    for (Field f : fields) {
                        f.setAccessible(true);
                        if (f.isAnnotationPresent(SignIgnore.class)) {
                            continue;
                        }
    
                        if(ObjectUtil.isSimpleTypeOrString(f.getType())) {
                            if (f.get(o) != null && f.get(o) != "") {
                                list.add(f.getName() + "=" + f.get(o) + "&");
                            }
                        }
                    }
                    cls = cls.getSuperclass();
                }
    
                int size = list.size();
                String[] arrayToSort = list.toArray(new String[size]);
                Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < size; i++) {
                    sb.append(arrayToSort[i]);
                }
    
                result = sb.toString();
                result += key;
                result = SHA256Util.getSHA256StrJava(result, CHARSET);
            } catch (Exception e) {
                LOGGER.error("getSignByObject Exception", e);
            }
    
            return result;
        }
    
        /**
         * 验证签名 服务器端使用
         *
         * @param parametersMap 参数
         * @param secretkey     密钥
         * @param sign          签名
         * @return
         */
        public static boolean validateSign(Map<String, Object> parametersMap, String secretkey, String sign) {
            LOGGER.info("validateSign map:{}", JSON.toJSONString(parametersMap));
            boolean flag = false;
            if (parametersMap.isEmpty()) {
                return flag;
            }
            //去除parametersMap中的sign
            parametersMap.remove("sign");
            String mySign = signRequest(parametersMap, secretkey);
            // 验证签名是否一致
            if (mySign.equals(sign)) {
                flag = true;
            }
            return flag;
        }
    
        /**
         * 签名加密 客户端使用
         *
         * @param parametersMap 参数
         * @param secretkey     密钥
         * @return
         */
        public static String signRequest(Map<String, Object> parametersMap, String secretkey) {
            ArrayList<String> list = new ArrayList<>();
            for (Map.Entry<String, Object> entry : parametersMap.entrySet()) {
                String key = entry.getKey();
                Object val = entry.getValue();
                if(ObjectUtil.isSimpleTypeOrString(val.getClass())) {
                    if (null != val && !"".equals(val)) {
                        list.add(key + "=" + val + "&");
                    }
                }
            }
            int size = list.size();
            String[] arrayToSort = list.toArray(new String[size]);
            Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < size; i++) {
                sb.append(arrayToSort[i]);
            }
            String result = sb.toString();
            result += secretkey;
            result = SHA256Util.getSHA256StrJava(result, CHARSET);
            return result;
        }
    }

     

  • 相关阅读:
    前端学习资源
    CSS样式属性单词之Left
    CSS 解决 a标签去掉下划线 text-decoration: none无效 的解决方案
    CSS二级菜单
    position属性absolute与relative 详解
    CSS行高——line-height
    VS code注释快捷键
    CSS padding margin border属性详解
    block(块级元素)和 inline(内联元素) 的区别
    css(float浮动和clear清除)
  • 原文地址:https://www.cnblogs.com/alimayun/p/12887882.html
Copyright © 2020-2023  润新知