Commit e6f5f8b2 authored by liaozan's avatar liaozan 🏀

AbstractSignatureValidationInterceptor

parent c98d8823
package com.schbrain.common.web.servlet;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.util.ArrayUtil;
import com.schbrain.common.web.utils.ContentCachingServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import static com.schbrain.common.web.utils.ContentCachingServletUtils.wrapRequestIfRequired;
......@@ -58,7 +55,7 @@ public class RequestLoggingFilter extends OncePerRequestFilter implements Ordere
String method = request.getMethod();
String requestUri = request.getRequestURI();
String queryString = request.getQueryString();
String body = getRequestBody(request);
String body = ContentCachingServletUtils.getRequestBody(request, false);
StringBuilder builder = new StringBuilder();
builder.append("requestUri: ").append(method).append(CharPool.SPACE).append(requestUri);
if (StringUtils.isNotBlank(queryString)) {
......@@ -73,22 +70,4 @@ public class RequestLoggingFilter extends OncePerRequestFilter implements Ordere
return builder.toString();
}
protected String getRequestBody(HttpServletRequest request) {
ContentCachingRequestWrapper nativeRequest = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
byte[] content = nativeRequest.getContentAsByteArray();
if (ArrayUtil.isEmpty(content)) {
return null;
}
String charset = nativeRequest.getCharacterEncoding();
try {
return new String(content, charset);
} catch (UnsupportedEncodingException e) {
log.warn("unsupported charset {} detect during convert body to String", charset, e);
return null;
}
}
}
package com.schbrain.common.web.support.signature;
import cn.hutool.crypto.digest.DigestUtil;
import com.schbrain.common.web.support.BaseHandlerInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static cn.hutool.core.text.StrPool.UNDERLINE;
import static com.schbrain.common.web.utils.ContentCachingServletUtils.getRequestBody;
import static com.schbrain.common.web.utils.ContentCachingServletUtils.wrapRequestIfRequired;
public abstract class AbstractSignatureValidationInterceptor<T extends SignatureContext> extends BaseHandlerInterceptor {
private static final String SCH_APP_KEY = "Sch-App-Key";
private static final String SCH_TIMESTAMP = "Sch-Timestamp";
private static final String SCH_SIGNATURE = "Sch-Signature";
private static final String SCH_EXPIRE_TIME = "Sch-Expire-Time";
@Override
protected boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) {
String appKey = request.getHeader(SCH_APP_KEY);
String timestamp = request.getHeader(SCH_TIMESTAMP);
String signature = request.getHeader(SCH_SIGNATURE);
String expireTime = request.getHeader(SCH_EXPIRE_TIME);
// 空校验
if (StringUtils.isAnyBlank(appKey, timestamp, signature)) {
throw new SignatureValidationException("签名参数为空!");
}
// 过期校验
if (StringUtils.isNotBlank(expireTime) && System.currentTimeMillis() > Long.parseLong(expireTime)) {
throw new SignatureValidationException("请求信息已过期!");
}
// 获取appSecret
SignatureContext context = getSignatureContext(appKey);
if (null == context || StringUtils.isBlank(context.getAppSecret())) {
throw new SignatureValidationException();
}
request = wrapRequestIfRequired(request);
// 校验签名
String requestUri = request.getRequestURI();
String queryString = request.getQueryString();
String body = getRequestBody(request, true);
String compareSignature = signParams(requestUri, queryString, body, timestamp, appKey, context.getAppSecret());
if (!signature.equals(compareSignature)) {
throw new SignatureValidationException();
}
SignatureContextUtil.set(context);
return true;
}
@Override
protected void afterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception ex) {
SignatureContextUtil.clear();
}
protected abstract T getSignatureContext(String appKey);
protected String signParams(String requestUri, String queryString, String bodyString, String timestamp, String appKey, String appSecret) {
StringBuilder toSign = new StringBuilder(requestUri);
if (StringUtils.isNotBlank(queryString)) {
toSign.append(UNDERLINE).append(queryString);
}
if (StringUtils.isNotBlank(bodyString)) {
toSign.append(UNDERLINE).append(bodyString);
}
toSign.append(UNDERLINE).append(timestamp).append(UNDERLINE).append(appKey).append(UNDERLINE).append(appSecret);
return DigestUtil.sha256Hex(toSign.toString());
}
}
package com.schbrain.common.web.support.signature;
import lombok.Data;
@Data
public class SignatureContext {
private String appKey;
private String appSecret;
}
package com.schbrain.common.web.support.signature;
import java.util.Optional;
import java.util.function.Supplier;
public class SignatureContextUtil {
private static final ThreadLocal<SignatureContext> LOCAL = new InheritableThreadLocal<>();
private static final Supplier<SignatureValidationException> EXCEPTION_SUPPLIER = SignatureValidationException::new;
/**
* 取值
*/
public static <T extends SignatureContext> T get(Class<T> type) {
return type.cast(Optional.ofNullable(LOCAL.get()).orElseThrow(EXCEPTION_SUPPLIER));
}
/**
* 赋值
*/
public static <T extends SignatureContext> void set(T signatureContext) {
LOCAL.set(signatureContext);
}
/**
* 移除
*/
public static void clear() {
LOCAL.remove();
}
}
package com.schbrain.common.web.support.signature;
import com.schbrain.common.exception.BaseException;
import static com.schbrain.common.constants.ResponseActionConstants.ALERT;
import static com.schbrain.common.constants.ResponseCodeConstants.PARAM_INVALID;
public class SignatureValidationException extends BaseException {
private static final long serialVersionUID = 7564001466173362458L;
private static final String DEFAULT_ERR_MSG = "签名验证异常";
public SignatureValidationException() {
this(DEFAULT_ERR_MSG);
}
public SignatureValidationException(String message) {
super(message, PARAM_INVALID, ALERT);
}
}
package com.schbrain.common.web.utils;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
/**
* @author liaozan
* @since 2023-05-08
*/
@Slf4j
public class ContentCachingServletUtils {
/**
......@@ -37,4 +44,25 @@ public class ContentCachingServletUtils {
}
}
public static String getRequestBody(HttpServletRequest request, boolean readFromInputStream) {
ContentCachingRequestWrapper nativeRequest = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Charset charset = Charset.forName(nativeRequest.getCharacterEncoding());
if (readFromInputStream) {
try {
return StreamUtils.copyToString(request.getInputStream(), charset);
} catch (IOException e) {
log.warn("Failed to read body content from request inputStream");
return null;
}
}
byte[] content = nativeRequest.getContentAsByteArray();
if (ArrayUtil.isEmpty(content)) {
return null;
}
return new String(content, charset);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment