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 implements HttpAuditRuleService { private final HttpAuditProperties props; private final CopyOnWriteArrayList 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 list = list(new LambdaQueryWrapper() .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; } }