完善签名

This commit is contained in:
2023-12-05 14:21:00 +08:00
parent 0d605d002d
commit bce41ce889
4 changed files with 138 additions and 20 deletions

View File

@@ -13,6 +13,7 @@ public class Constants {
public static final String HTTP_HEADER_TOKEN = "X-Token";
public static final String HTTP_HEADER_CLIENT_SIGN = "X-Client-Sign";
public static final String HTTP_HEADER_CLIENT_RANDOM = "X-Client-Random";
public static final String HTTP_HEADER_CLIENT_URL = "X-Client-Url";
public static final String HTTP_HEADER_TS = "X-TS";
public static final String RATE_LIMIT_GLOBAL_SESSION_MIN_PREFIX = "global-session-min-rl-";
public static final String RATE_LIMIT_GLOBAL_ANONYMOUS_MIN_PREFIX = "global-anonymous-min-rl-";

View File

@@ -0,0 +1,79 @@
package cn.lihongjie.coal.filter;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class CacheBodyRequestWrapper extends HttpServletRequestWrapper {
private final CacheBodyServletInputStream inputStream;
/**
* Constructs a request object wrapping the given request.
*
* @param request the {@link HttpServletRequest} to be wrapped.
* @throws IllegalArgumentException if the request is null
*/
@SneakyThrows
public CacheBodyRequestWrapper(HttpServletRequest request) {
super(request);
inputStream = new CacheBodyServletInputStream(super.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
return inputStream;
}
public void reset() {
inputStream.reset();
}
private class CacheBodyServletInputStream extends ServletInputStream {
private final ServletInputStream stream;
private final ByteArrayInputStream cache;
@SneakyThrows
public CacheBodyServletInputStream(ServletInputStream stream) {
this.stream = stream;
cache = new ByteArrayInputStream(IOUtils.toByteArray(stream));
}
public void reset() {
cache.reset();
}
@Override
public boolean isFinished() {
return cache.available() <= 0;
}
@Override
public boolean isReady() {
return true;
}
@SneakyThrows
@Override
public void setReadListener(ReadListener readListener) {
// readListener.onAllDataRead();
}
@Override
public int read() throws IOException {
return cache.read();
}
}
}

View File

@@ -12,8 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import java.io.IOException;
@@ -29,12 +27,10 @@ public class CacheFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
long start = System.currentTimeMillis();
var req = new ContentCachingRequestWrapper(request);
var res = new ContentCachingResponseWrapper(response);
filterChain.doFilter(req, res);
var req = new CacheBodyRequestWrapper(request);
filterChain.doFilter(req, response);
long end = System.currentTimeMillis();
res.addHeader(Constants.HTTP_HEADER_RESPONSE_TIME, String.valueOf(end - start));
res.copyBodyToResponse();
response.addHeader(Constants.HTTP_HEADER_RESPONSE_TIME, String.valueOf(end - start));
}
}

View File

@@ -26,7 +26,9 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@@ -50,8 +52,7 @@ public class SignFilter extends OncePerRequestFilter {
@Autowired IpQueryService ipQueryService;
@Autowired LoginUserService loginUserService;
@Autowired
RedisTemplate<String, String> redisTemplate;
@Autowired RedisTemplate<String, String> redisTemplate;
@Override
protected void doFilterInternal(
@@ -59,6 +60,9 @@ public class SignFilter extends OncePerRequestFilter {
throws ServletException, IOException {
if (sysConfigService.isEnable(Constants.SYSCONFIG_ENABLE_REQUEST_SIGN)) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("基本请求头");
StringBuilder sb = new StringBuilder();
@@ -68,7 +72,11 @@ public class SignFilter extends OncePerRequestFilter {
sb.append(StringUtils.defaultString("\n"));
// 请求地址
sb.append(StringUtils.defaultString(request.getRequestURI()));
sb.append(
StringUtils.defaultString(
ObjectUtils.defaultIfNull(
request.getHeader(Constants.HTTP_HEADER_CLIENT_URL),
request.getRequestURI())));
sb.append(StringUtils.defaultString("\n"));
// 请求参数
@@ -128,17 +136,40 @@ public class SignFilter extends OncePerRequestFilter {
sb.append(StringUtils.defaultString("\n"));
String sha256Hex = DigestUtils.sha256Hex(request.getInputStream()).toUpperCase();
stopWatch.stop();
stopWatch.start("sha256Hex");
String sha256Hex;
MediaType mediaType =
MediaType.parseMediaType(
ObjectUtils.defaultIfNull(
request.getContentType(),
MediaType.APPLICATION_OCTET_STREAM_VALUE));
// 文件上传不对body进行签名
if (mediaType.isCompatibleWith(MediaType.MULTIPART_FORM_DATA)) {
sha256Hex = DigestUtils.sha256Hex("").toUpperCase();
} else {
sha256Hex = DigestUtils.sha256Hex(request.getInputStream()).toUpperCase();
}
sb.append(StringUtils.defaultString(sha256Hex));
sb.append(StringUtils.defaultString("\n"));
stopWatch.stop();
stopWatch.start("签名");
Mac mac =
HmacUtils.getInitializedMac(
HmacAlgorithms.HMAC_SHA_256, token.getBytes(StandardCharsets.UTF_8));
mac.update(sb.toString().getBytes(StandardCharsets.UTF_8));
String sign = Hex.encodeHexString(mac.doFinal()).toUpperCase();
stopWatch.stop();
String clientSign = request.getHeader(Constants.HTTP_HEADER_CLIENT_SIGN);
if (!sign.equals(clientSign)) {
@@ -150,22 +181,33 @@ public class SignFilter extends OncePerRequestFilter {
return;
}
String cr = redisTemplate.opsForValue().get(Constants.CACHE_CLIENT_RANDOM_PREFIX + random);
stopWatch.start("重复请求");
String cr =
redisTemplate.opsForValue().get(Constants.CACHE_CLIENT_RANDOM_PREFIX + random);
if (StringUtils.equals(cr, "1")){
if (StringUtils.equals(cr, "1")) {
sysLogService.saveSysLog(request, "签名模块", "重复请求", "");
RequestUtils.writeResponse(
new BizException("请求已过期, 请刷新页面重试"), response);
RequestUtils.writeResponse(new BizException("请求已过期, 请刷新页面重试"), response);
return;
}else {
redisTemplate.opsForValue().set(Constants.CACHE_CLIENT_RANDOM_PREFIX + random, "1", TIME_DIFF_MS, TimeUnit.MILLISECONDS);
} else {
redisTemplate
.opsForValue()
.set(
Constants.CACHE_CLIENT_RANDOM_PREFIX + random,
"1",
TIME_DIFF_MS,
TimeUnit.MILLISECONDS);
}
stopWatch.stop();
if (request instanceof CacheBodyRequestWrapper cacheBodyRequestWrapper) {
cacheBodyRequestWrapper.reset();
}
log.info("签名验证通过: {}", stopWatch.prettyPrint());
}
doFilter(request, response, filterChain);
}
}