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("恭喜成功了");
}
}