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 map = new HashMap();
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;
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 将body数据存储起来
body = getBodyString(request).getBytes(Charset.defaultCharset());
}
public String getBodyString(final ServletRequest request) {
try {
return inputStream2String(request.getInputStream());
} catch (IOException e) {
log.error("", e);
throw new RuntimeException(e);
}
}
public String getBodyString() {
final InputStream inputStream = new ByteArrayInputStream(body);
return inputStream2String(inputStream);
}
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;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new ReplaceStreamFilter());
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.addUrlPatterns("/api
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; public class R extends HashMap{ 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 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 {
public static boolean validTime(String timeStamp,int intervalTime){
long timeStampL = Long.parseLong(timeStamp);
return validTime(timeStampL,intervalTime);
}
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;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signInterceptor()).addPathPatterns("/api
@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请求方式



