mirror of
https://codeup.aliyun.com/64f7d6b8ce01efaafef1e678/coal/coal.git
synced 2026-01-25 07:46:40 +08:00
增加接口签名校验
This commit is contained in:
@@ -11,9 +11,12 @@ public class Constants {
|
||||
public static final String CACHE_RESOURCE_API_TREE = "resourceApiTree";
|
||||
public static final String CACHE_RESOURCE_MENU_TREE = "resourceMenuTree";
|
||||
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_TS = "X-TS";
|
||||
public static final String RATE_LIMIT_GLOBAL_SESSION_PREFIX = "global-session-rl-";
|
||||
public static final String RATE_LIMIT_GLOBAL_USER_PREFIX = "global-user-rl-";
|
||||
public static String SYSCONFIG_ENABLE_CAPTCHA = "enable_captcha";
|
||||
public static String SYSCONFIG_ENABLE_REQUEST_SIGN = "enable_request_sign";
|
||||
public static String SYSCONFIG_SESSION_TIMEOUT = "session_timeout";
|
||||
public static String SYSCONFIG_ACCOUNT_MAX_ONLINE = "account_max_online";
|
||||
public static String SYSCONFIG_RESETPWD_ENABLE = "resetpwd_enable";
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
package cn.lihongjie.coal.common;
|
||||
|
||||
import cn.lihongjie.coal.base.dto.R;
|
||||
import cn.lihongjie.coal.exception.BizException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import eu.bitwalker.useragentutils.Browser;
|
||||
import eu.bitwalker.useragentutils.OperatingSystem;
|
||||
import eu.bitwalker.useragentutils.UserAgent;
|
||||
import eu.bitwalker.useragentutils.Version;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.entity.ContentType;
|
||||
|
||||
@UtilityClass
|
||||
public class RequestUtils {
|
||||
@@ -67,4 +75,14 @@ public class RequestUtils {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void writeResponse(BizException ex, HttpServletResponse response) {
|
||||
|
||||
response.setStatus(200);
|
||||
response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
|
||||
R<Object> fail = R.fail(ex.getCode(), ex.getMessage());
|
||||
response.getOutputStream().write(Ctx.getContext().getBean(ObjectMapper.class).writeValueAsBytes(fail));
|
||||
response.getOutputStream().flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package cn.lihongjie.coal.filter;
|
||||
|
||||
import cn.lihongjie.coal.base.dto.R;
|
||||
import cn.lihongjie.coal.common.Constants;
|
||||
import cn.lihongjie.coal.common.Ctx;
|
||||
import cn.lihongjie.coal.common.RequestUtils;
|
||||
import cn.lihongjie.coal.exception.BizException;
|
||||
import cn.lihongjie.coal.permission.dto.PermissionDto;
|
||||
import cn.lihongjie.coal.permission.service.PermissionService;
|
||||
@@ -23,12 +23,9 @@ import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -77,7 +74,7 @@ public class AuthFilter extends OncePerRequestFilter {
|
||||
} catch (Exception e) {
|
||||
|
||||
logger.info("系统异常", e);
|
||||
writeResponse(new BizException("系统异常"), response);
|
||||
RequestUtils.writeResponse(new BizException("系统异常"), response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +106,7 @@ public class AuthFilter extends OncePerRequestFilter {
|
||||
resourceService.findUrlFromCache(getRequestURI(request));
|
||||
|
||||
if (resource.isEmpty()) {
|
||||
writeResponse(new BizException("invalidUrl", "资源未找到"), response);
|
||||
RequestUtils.writeResponse(new BizException("invalidUrl", "资源未找到"), response);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,7 +137,7 @@ public class AuthFilter extends OncePerRequestFilter {
|
||||
|
||||
} else {
|
||||
|
||||
writeResponse(new BizException("loginRequired", "请先登录"), response);
|
||||
RequestUtils.writeResponse(new BizException("loginRequired", "请先登录"), response);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -153,7 +150,7 @@ public class AuthFilter extends OncePerRequestFilter {
|
||||
|
||||
} catch (BizException ex) {
|
||||
|
||||
writeResponse(ex, response);
|
||||
RequestUtils.writeResponse(ex, response);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -169,7 +166,7 @@ public class AuthFilter extends OncePerRequestFilter {
|
||||
|
||||
if (userResource.isEmpty() && BooleanUtils.isFalse(user.getSysAdmin())) {
|
||||
|
||||
writeResponse(new BizException("invalidAccess", "当前资源未授权,请联系机构管理员处理。"), response);
|
||||
RequestUtils.writeResponse(new BizException("invalidAccess", "当前资源未授权,请联系机构管理员处理。"), response);
|
||||
} else {
|
||||
|
||||
doFilter(request, response, filterChain);
|
||||
@@ -202,13 +199,4 @@ public class AuthFilter extends OncePerRequestFilter {
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void writeResponse(BizException ex, HttpServletResponse response) {
|
||||
|
||||
response.setStatus(200);
|
||||
response.setContentType(ContentType.APPLICATION_JSON.getMimeType());
|
||||
R<Object> fail = R.fail(ex.getCode(), ex.getMessage());
|
||||
response.getOutputStream().write(objectMapper.writeValueAsBytes(fail));
|
||||
response.getOutputStream().flush();
|
||||
}
|
||||
}
|
||||
|
||||
35
src/main/java/cn/lihongjie/coal/filter/CacheFilter.java
Normal file
35
src/main/java/cn/lihongjie/coal/filter/CacheFilter.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package cn.lihongjie.coal.filter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@Order(0)
|
||||
@Slf4j
|
||||
public class CacheFilter extends OncePerRequestFilter {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
|
||||
var req = new ContentCachingRequestWrapper(request);
|
||||
var res = new ContentCachingResponseWrapper(response);
|
||||
filterChain.doFilter(req, res);
|
||||
res.copyBodyToResponse();
|
||||
|
||||
}
|
||||
}
|
||||
131
src/main/java/cn/lihongjie/coal/filter/SignFilter.java
Normal file
131
src/main/java/cn/lihongjie/coal/filter/SignFilter.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package cn.lihongjie.coal.filter;
|
||||
|
||||
import cn.lihongjie.coal.common.Constants;
|
||||
import cn.lihongjie.coal.common.RequestUtils;
|
||||
import cn.lihongjie.coal.exception.BizException;
|
||||
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.codec.digest.HmacAlgorithms;
|
||||
import org.apache.commons.codec.digest.HmacUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
|
||||
/** 请求签名验证 */
|
||||
@Component
|
||||
@Order(10)
|
||||
@Slf4j
|
||||
public class SignFilter extends OncePerRequestFilter {
|
||||
@Autowired ObjectMapper objectMapper;
|
||||
@Autowired SysConfigService sysConfigService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (sysConfigService.isEnable(Constants.SYSCONFIG_ENABLE_REQUEST_SIGN)) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 请求方法
|
||||
|
||||
sb.append(StringUtils.defaultString(request.getMethod().toUpperCase()));
|
||||
sb.append(StringUtils.defaultString("\n"));
|
||||
|
||||
// 请求地址
|
||||
sb.append(StringUtils.defaultString(request.getRequestURI()));
|
||||
sb.append(StringUtils.defaultString("\n"));
|
||||
|
||||
// 请求参数
|
||||
sb.append(StringUtils.defaultString(request.getQueryString()));
|
||||
sb.append(StringUtils.defaultString("\n"));
|
||||
|
||||
// 请求头
|
||||
|
||||
String token =
|
||||
ObjectUtils.defaultIfNull(
|
||||
request.getHeader(Constants.HTTP_HEADER_TOKEN), "anonymous");
|
||||
String ts = request.getHeader(Constants.HTTP_HEADER_TS);
|
||||
if (StringUtils.isEmpty(ts)) {
|
||||
RequestUtils.writeResponse(
|
||||
new BizException(Constants.HTTP_HEADER_TS + " 请求头缺失"), response);
|
||||
return;
|
||||
}
|
||||
long tsi = 0;
|
||||
|
||||
try {
|
||||
|
||||
tsi = Long.parseLong(ts);
|
||||
} catch (Exception e) {
|
||||
RequestUtils.writeResponse(
|
||||
new BizException(Constants.HTTP_HEADER_TS + " 请求头格式错误"), response);
|
||||
return;
|
||||
}
|
||||
|
||||
long current = System.currentTimeMillis();
|
||||
|
||||
if (Math.abs(tsi - current) > 1000 * 60 * 2) {
|
||||
RequestUtils.writeResponse(
|
||||
new BizException(
|
||||
Constants.HTTP_HEADER_TS
|
||||
+ " 客户端时间误差过大,请校准时间. 服务器时间为: "
|
||||
+ LocalDateTime.now()),
|
||||
response);
|
||||
return;
|
||||
}
|
||||
|
||||
sb.append(StringUtils.defaultString(token));
|
||||
|
||||
sb.append(StringUtils.defaultString("\n"));
|
||||
|
||||
sb.append(StringUtils.defaultString(ts));
|
||||
|
||||
sb.append(StringUtils.defaultString("\n"));
|
||||
|
||||
String sha256Hex = DigestUtils.sha256Hex(request.getInputStream()).toUpperCase();
|
||||
|
||||
sb.append(StringUtils.defaultString(sha256Hex));
|
||||
sb.append(StringUtils.defaultString("\n"));
|
||||
|
||||
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();
|
||||
|
||||
String clientSign = request.getHeader(Constants.HTTP_HEADER_CLIENT_SIGN);
|
||||
|
||||
if (!sign.equals(clientSign)) {
|
||||
log.debug("key: {} \ndata:{}", token, sb);
|
||||
log.warn("签名错误: {} {}", clientSign, sign);
|
||||
RequestUtils.writeResponse(
|
||||
new BizException(Constants.HTTP_HEADER_CLIENT_SIGN + " 请求头签名错误"), response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
doFilter(request, response, filterChain);
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ public class SysConfigService extends BaseService<SysConfigEntity, SysConfigRepo
|
||||
.collect(Collectors.toMap(e -> e.getCode(), e -> e));
|
||||
|
||||
addDictConfig(all, Constants.SYSCONFIG_ENABLE_CAPTCHA, "验证码状态", "1", "status.type");
|
||||
addDictConfig(all, Constants.SYSCONFIG_ENABLE_REQUEST_SIGN, "请求签名验证", "1", "status.type");
|
||||
|
||||
addNumberConfig(
|
||||
all,
|
||||
|
||||
Reference in New Issue
Block a user