package com.vincent.rsf.httpaudit.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.vincent.rsf.httpaudit.entity.HttpAuditRule;
|
import com.vincent.rsf.httpaudit.mapper.HttpAuditRuleMapper;
|
import com.vincent.rsf.httpaudit.model.HttpAuditDecision;
|
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
|
import com.vincent.rsf.httpaudit.support.HttpAuditSupport;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.scheduling.annotation.Scheduled;
|
|
import javax.annotation.PostConstruct;
|
import javax.servlet.http.HttpServletRequest;
|
import java.net.URI;
|
import java.util.List;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.regex.Pattern;
|
|
/**
|
* 规则缓存;入站/出站或关系;record_all 时白名单下也全记
|
*/
|
@Slf4j
|
public class HttpAuditRuleServiceImpl extends ServiceImpl<HttpAuditRuleMapper, HttpAuditRule> implements HttpAuditRuleService {
|
|
private final HttpAuditProperties props;
|
private final CopyOnWriteArrayList<HttpAuditRule> cache = new CopyOnWriteArrayList<>();
|
|
public HttpAuditRuleServiceImpl(HttpAuditRuleMapper mapper, HttpAuditProperties props) {
|
this.baseMapper = mapper;
|
this.props = props;
|
}
|
|
@PostConstruct
|
public void init() {
|
refreshCache();
|
}
|
|
@Override
|
@Scheduled(fixedDelayString = "${http-audit.rule-cache-refresh-ms:60000}")
|
public void refreshCache() {
|
try {
|
List<HttpAuditRule> list = list(new LambdaQueryWrapper<HttpAuditRule>()
|
.eq(HttpAuditRule::getDeleted, 0)
|
.eq(HttpAuditRule::getEnabled, 1)
|
.orderByAsc(HttpAuditRule::getSortOrder)
|
.orderByAsc(HttpAuditRule::getId));
|
cache.clear();
|
cache.addAll(list);
|
log.debug("http-audit 规则缓存已刷新,条数={}", cache.size());
|
} catch (Exception e) {
|
log.warn("http-audit 规则缓存刷新失败(白名单将不生效,入站不落库)", e);
|
}
|
}
|
|
@Override
|
public boolean shouldAudit(HttpServletRequest request, String requestBody) {
|
return decideInbound(request, requestBody).isAudit();
|
}
|
|
@Override
|
public HttpAuditDecision decideInbound(HttpServletRequest request, String requestBody) {
|
if (!props.isWhitelistOnly()) {
|
return HttpAuditDecision.yes(reqLimitFromRecordAllRow(), resLimitFromRecordAllRow());
|
}
|
if (cache.isEmpty()) {
|
return HttpAuditDecision.SKIP;
|
}
|
HttpAuditRule allRow = firstRecordAllRule();
|
if (allRow != null) {
|
return HttpAuditDecision.yes(allRow.getRequestMaxChars(), allRow.getResponseMaxChars());
|
}
|
String path = HttpAuditSupport.safePath(request);
|
String ip = HttpAuditSupport.clientIp(request);
|
String body = requestBody == null ? "" : requestBody;
|
for (HttpAuditRule r : cache) {
|
if (isRecordAll(r)) {
|
continue;
|
}
|
if (!appliesInbound(r)) {
|
continue;
|
}
|
try {
|
if (matchInbound(r, path, ip, body)) {
|
return HttpAuditDecision.yes(r.getRequestMaxChars(), r.getResponseMaxChars());
|
}
|
} catch (Exception e) {
|
log.debug("http-audit 规则 id={} 匹配异常:{}", r.getId(), e.getMessage());
|
}
|
}
|
return HttpAuditDecision.SKIP;
|
}
|
|
@Override
|
public HttpAuditDecision decideOutbound(String fullUrl, String method, String requestBody) {
|
if (!props.isWhitelistOnly()) {
|
return HttpAuditDecision.yes(reqLimitFromRecordAllRow(), resLimitFromRecordAllRow());
|
}
|
if (cache.isEmpty()) {
|
return HttpAuditDecision.SKIP;
|
}
|
HttpAuditRule allRow = firstRecordAllRule();
|
if (allRow != null) {
|
return HttpAuditDecision.yes(allRow.getRequestMaxChars(), allRow.getResponseMaxChars());
|
}
|
String body = requestBody == null ? "" : requestBody;
|
for (HttpAuditRule r : cache) {
|
if (isRecordAll(r)) {
|
continue;
|
}
|
if (!appliesOutbound(r)) {
|
continue;
|
}
|
try {
|
if (matchOutbound(r, fullUrl, body)) {
|
return HttpAuditDecision.yes(r.getRequestMaxChars(), r.getResponseMaxChars());
|
}
|
} catch (Exception e) {
|
log.debug("http-audit 出站规则 id={} 匹配异常:{}", r.getId(), e.getMessage());
|
}
|
}
|
return HttpAuditDecision.SKIP;
|
}
|
|
private Integer reqLimitFromRecordAllRow() {
|
HttpAuditRule row = firstRecordAllRule();
|
return row == null ? null : row.getRequestMaxChars();
|
}
|
|
private Integer resLimitFromRecordAllRow() {
|
HttpAuditRule row = firstRecordAllRule();
|
return row == null ? null : row.getResponseMaxChars();
|
}
|
|
private HttpAuditRule firstRecordAllRule() {
|
for (HttpAuditRule r : cache) {
|
if (isRecordAll(r)) {
|
return r;
|
}
|
}
|
return null;
|
}
|
|
private static boolean isRecordAll(HttpAuditRule r) {
|
return r.getRecordAll() != null && r.getRecordAll() == 1;
|
}
|
|
private static String dir(HttpAuditRule r) {
|
String d = r.getDirection();
|
if (d == null || d.isEmpty()) {
|
return HttpAuditRule.DIR_IN;
|
}
|
return d;
|
}
|
|
private static boolean appliesInbound(HttpAuditRule r) {
|
if (isRecordAll(r)) {
|
return false;
|
}
|
String d = dir(r);
|
return HttpAuditRule.DIR_IN.equals(d) || HttpAuditRule.DIR_BOTH.equals(d);
|
}
|
|
private static boolean appliesOutbound(HttpAuditRule r) {
|
if (isRecordAll(r)) {
|
return false;
|
}
|
String d = dir(r);
|
return HttpAuditRule.DIR_OUT.equals(d) || HttpAuditRule.DIR_BOTH.equals(d);
|
}
|
|
private static boolean matchInbound(HttpAuditRule r, String path, String ip, String body) {
|
return matchByRuleType(r, path, ip, body);
|
}
|
|
private boolean matchOutbound(HttpAuditRule r, String fullUrl, String body) {
|
String t = r.getRuleType();
|
String mode = r.getMatchMode();
|
String p = r.getPattern();
|
if (p == null) {
|
return false;
|
}
|
if (HttpAuditRule.TYPE_URI.equals(t)) {
|
if (matchString(fullUrl, mode, p)) {
|
return true;
|
}
|
return matchString(extractPath(fullUrl), mode, p);
|
}
|
if (HttpAuditRule.TYPE_IP.equals(t)) {
|
String host = extractHost(fullUrl);
|
return matchString(host, mode, p);
|
}
|
if (HttpAuditRule.TYPE_REQUEST_BODY.equals(t)) {
|
return matchString(body, mode, p);
|
}
|
return false;
|
}
|
|
private static String extractPath(String url) {
|
try {
|
URI u = URI.create(url);
|
String path = u.getPath();
|
return path == null ? "" : path;
|
} catch (Exception e) {
|
return url == null ? "" : url;
|
}
|
}
|
|
private static String extractHost(String url) {
|
try {
|
URI u = URI.create(url);
|
String h = u.getHost();
|
return h == null ? "" : h;
|
} catch (Exception e) {
|
return "";
|
}
|
}
|
|
private static boolean matchByRuleType(HttpAuditRule r, String path, String ip, String body) {
|
String t = r.getRuleType();
|
String mode = r.getMatchMode();
|
String p = r.getPattern();
|
if (p == null) {
|
return false;
|
}
|
if (HttpAuditRule.TYPE_URI.equals(t)) {
|
return matchString(path, mode, p);
|
}
|
if (HttpAuditRule.TYPE_IP.equals(t)) {
|
return matchString(ip, mode, p);
|
}
|
if (HttpAuditRule.TYPE_REQUEST_BODY.equals(t)) {
|
return matchString(body, mode, p);
|
}
|
return false;
|
}
|
|
private static boolean matchString(String value, String mode, String pattern) {
|
if (value == null) {
|
value = "";
|
}
|
if (HttpAuditRule.MODE_EQUAL.equals(mode)) {
|
return value.equals(pattern);
|
}
|
if (HttpAuditRule.MODE_PREFIX.equals(mode)) {
|
return value.startsWith(pattern);
|
}
|
if (HttpAuditRule.MODE_CONTAINS.equals(mode)) {
|
return value.contains(pattern);
|
}
|
if (HttpAuditRule.MODE_REGEX.equals(mode)) {
|
return Pattern.compile(pattern).matcher(value).find();
|
}
|
return false;
|
}
|
}
|