0
点赞
收藏
分享

微信扫一扫

springboot使用拦截器拦截验证签名sign

雷亚荣 2022-04-25 阅读 84
java

1生成签名

2使用拦截器验证签名

2.1重写request,以读取存储二级制流

2.2配置过滤器,将默认的request替换为重写的

2.3配置过滤器

2.4写拦截器

2.5配置拦截器

1生成签名

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.renren.common.utils.SM3Util;
import cn.hutool.core.util.HexUtil;

import java.util.HashMap;
import java.util.Map;

public class SignUtils {
    public static void main(String[] args) {
    //appid对应一个accessKey,存储在数据库中
        String accessKey = "71a8e08ca1122c61faff1abffcbc8226b9a2e940";
        Long timestamp = System.currentTimeMillis();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("id","333");
        map.put("id2","陈大壮");
        map.put("passwd","XFCY8R7dhRRnyXDMAlzTNvDKnf9zT2RO1bVc8LXe0K6Qj6vifnWawy5JJw6vhl8xuokTQPqTtoK8gpKfslS08emERiejbZrhrYfmyeof7EPpv+VCwLQ/vbbi4hwwUtK+9s8M6MuOXVAisd06WXq5BdT4RDMDvd48pptB+tXJsd8=");

        ObjectMapper objectMapper = new ObjectMapper();
        String hexStr = "";
        try {
            String valueAsString = objectMapper.writeValueAsString(map);
            System.out.println(valueAsString);
            String verifySing = accessKey + "&" + timestamp + "&" + valueAsString;
            System.out.println(verifySing);
            byte[] hmac = SM3Util.hash(verifySing.getBytes());
            hexStr = HexUtil.encodeHexStr(hmac);
            System.out.println(hexStr);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }
}

生成json串类似下图。

{
   "timeStamp":"1650606559147",
   "sign":"3cb7c8b5715e510de106b76c40e1397a5d5c73c70d53994f3eca3b72c84a8264",
   "appId":"fxIzd7xG",
   "data":{
       "id2":"陈大壮",
       "id":"333",
       "passwd":"XFCY8R7dhRRnyXDMAlzTNvDKnf9zT2RO1bVc8LXe0K6Qj6vifnWawy5JJw6vhl8xuokTQPqTtoK8gpKfslS08emERiejbZrhrYfmyeof7EPpv+VCwLQ/vbbi4hwwUtK+9s8M6MuOXVAisd06WXq5BdT4RDMDvd48pptB+tXJsd8="
   }
}

2 使用拦截器验证签名

2.1重写request,以读取存储二级制流


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;


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;

/**
 * 解决request流只读取一次的问题
 */
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {

    /**
     * 存储body数据的容器
     */
    private final byte[] body;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);

        // 将body数据存储起来
        body = getBodyString(request).getBytes(Charset.defaultCharset());
    }

    /**
     * 获取请求Body
     *
     * @param request request
     * @return String
     */
    public String getBodyString(final ServletRequest request) {
        try {
            return inputStream2String(request.getInputStream());
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取请求Body
     *
     * @return String
     */
    public String getBodyString() {
        final InputStream inputStream = new ByteArrayInputStream(body);

        return inputStream2String(inputStream);
    }

    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream 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 BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

}

2.2配置过滤器,将默认的request替换为重写的

import io.renren.modules.sys.request.RequestWrapper;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ReplaceStreamFilter implements Filter {

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

2.3配置过滤器


import io.renren.common.xss.XssFilter;
import io.renren.modules.sys.filter.ReplaceStreamFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

/**
 * Filter配置
 *
 * @author Mark sunlightcs@gmail.com
 */
@Configuration
public class FilterConfig {




    /**
     * 注册过滤器
     *
     * @return FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new ReplaceStreamFilter());
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        registration.addUrlPatterns("/api/*");
        registration.setName("streamFilter");
        registration.setOrder(12);
        return registration;
    }


}

2.4写拦截器


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.renren.common.utils.R;

import io.renren.common.utils.SM3Util;

import io.renren.common.utils.TimeUtil;
import io.renren.modules.sys.autoconfigure.SystemProperties;
import io.renren.modules.sys.entity.AccessEntity;
import io.renren.modules.sys.request.RequestWrapper;
import io.renren.modules.sys.service.AccessService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.servlet.HandlerInterceptor;



import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.PrintWriter;


/**
 * 验证签名
 */
public class SignInterceptor implements  HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private SystemProperties systemProperties;
    @Autowired
    private AccessService accessService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");

        RequestWrapper requestWrapper = new RequestWrapper(request);
        String jsonParam = requestWrapper.getBodyString();

        JSONObject json = (JSONObject) JSONObject.parseObject(jsonParam);

        logger.info("[ACCESS LOG] body args:" + (json == null ? "" : json.toJSONString()));
        String appId = json.getString("appId");
        String sign = json.getString("sign");
        String timeStamp = json.getString("timeStamp");
        String data = json.getString("data");
        logger.info("开始验证签名"+sign);
        if(StringUtils.isEmpty(sign)||StringUtils.isEmpty(appId)){
            PrintWriter out = response.getWriter();
            out.print(JSON.toJSONString((R.error("sign或者appId为空"))));
            return false;
        }
        //验证时间戳是否在规定时间返回之内
        if(!TimeUtil.validTime(timeStamp,systemProperties.getIntervalTime())){
            PrintWriter out = response.getWriter();
            out.print(JSON.toJSONString((R.error("时间戳失效"))));
            return false;
        }
        //根据参数重新生成sign并验证,从数据库中appId获取数据accessKey
        AccessEntity accessEntity = accessService.queryAccessEntityByAppId(appId);
        String verifySing = accessEntity.getAppSecret() + "&" + timeStamp + "&" + data;
        //根据accessKey、时间错、数据验证签名是否有效
        boolean signFlag = SM3Util.verify(verifySing, sign);
        if(!signFlag){
            PrintWriter out = response.getWriter();
            out.print(JSON.toJSONString((R.error("签名错误"))));
            return false;
        }
        //todo判断是否有权限、是否白名单、是否禁用。
        logger.info("验证签名完成");
        return true;
    }



}

返回R类


import java.util.HashMap;
import java.util.Map;

/**
 * 返回数据
 *
 * @author Mark sunlightcs@gmail.com
 */
public class R extends HashMap<String, Object> {
	private static final long serialVersionUID = 1L;
	
	public R() {
		put("code", 0);
		put("msg", "success");
	}
	
	public static R error() {
		return error(500, "未知异常,请联系管理员");
	}
	
	public static R error(String msg) {
		return error(500, msg);
	}
	
	public static R error(int code, String msg) {
		R r = new R();
		r.put("code", code);
		r.put("msg", msg);
		return r;
	}

	public static R ok(String msg) {
		R r = new R();
		r.put("msg", msg);
		return r;
	}
	
	public static R ok(Map<String, Object> map) {
		R r = new R();
		r.putAll(map);
		return r;
	}
	
	public static R ok() {
		return new R();
	}

	@Override
	public R put(String key, Object value) {
		super.put(key, value);
		return this;
	}
}

系统属性配置类


/**
 * 项目配置文件
 */
@Component
@ConfigurationProperties(prefix = "systemproperties")
@Data
public class SystemProperties {
    /**
     * 有效时间
     */
    private int intervalTime;
    /**
     * 公钥
     */
    private String publicKey;
    /**
     * 私钥
     */
    private String secretKey;
    /**
     * 应用发布服务器
     */
    private String hostIp;



}

application.yml
时间戳的有效时间

systemproperties:
  intervalTime: 30

时间工具类


public class TimeUtil {
    /**
     * 根据当前时间,判断timestamp是否在有效时间范围内
     * @param timeStamp 时间戳
     * @param intervalTime 分钟
     * @return
     */
    public static boolean validTime(String timeStamp,int intervalTime){
       long timeStampL = Long.parseLong(timeStamp);
        return validTime(timeStampL,intervalTime);
    }
    /**
     * 根据当前时间,判断timestamp是否在有效时间范围内
     * @param timeStamp 时间戳
     * @param intervalTime 分钟
     * @return
     */
    public static boolean validTime(long timeStamp,int intervalTime){
        long current = System.currentTimeMillis();
        //取绝对值,当前时间之内波动几分钟
        long interval = Math.abs(current-timeStamp);
        //转换为分钟
        if(interval/(1000*60)>intervalTime){
            return false;
        }
        return true;
    }
}

这行代码,大家自行发挥替换
AccessEntity accessEntity = accessService.queryAccessEntityByAppId(appId);

验签工具类


import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class SM3Util {
    public static byte[] hash(byte[] srcData) {
        SM3Digest digest = new SM3Digest();
        digest.update(srcData, 0, srcData.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return hash;
    }

    public static boolean verify(String srcStr, String sm3HexString) {
        boolean flag = false;
        try {
            //使用指定的字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中
            byte[] srcData = srcStr.getBytes();
            //16进制 --> byte[]
            byte[] sm3Hash = ByteUtils.fromHexString(sm3HexString);
            byte[] newHash = hash(srcData);
            //判断数组是否相等
            if (Arrays.equals(newHash, sm3Hash)) {
                flag = true;
            }
        } catch (Exception e) {

        }
        return flag;
    }

}

2.5配置拦截器


import io.renren.modules.sys.interceptor.SignInterceptor;
import org.springframework.context.annotation.Bean;
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.WebMvcConfigurer;

/**
 * WebMvc配置
 *
 * @author Mark sunlightcs@gmail.com
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

  
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(signInterceptor()).addPathPatterns("/api/**");
    }

    @Bean
    public SignInterceptor signInterceptor(){
        return new SignInterceptor();
    }

}

2.6ApiController


/**
 *
 *
 * @author Mark sunlightcs@gmail.com
 */
@RestController
@RequestMapping("/api")
public class ApiController  {
	@Autowired
	private SystemProperties systemProperties;
	
	/**
	 * 列表
	 */
	@RequestMapping("/list")
	public R list(@RequestBody ApiRequest apiRequest){
		System.out.println("data = " + apiRequest);
		System.out.println("data = " + apiRequest.getSign());
		System.out.println("data = " + apiRequest.getTimeStamp());
		System.out.println("data = " + apiRequest);
		return R.ok("恭喜成功了");
	}


	
	
}

2.7请求方式

在这里插入图片描述

举报

相关推荐

0 条评论