cl
8 小时以前 c4bba32b20f0869b45ed14be04543869dd91ee6c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.vincent.rsf.httpaudit.web;
 
import com.vincent.rsf.httpaudit.entity.HttpAuditLog;
import com.vincent.rsf.httpaudit.model.HttpAuditDecision;
import com.vincent.rsf.httpaudit.props.HttpAuditProperties;
import com.vincent.rsf.httpaudit.service.HttpAuditAsyncRecorder;
import com.vincent.rsf.httpaudit.service.HttpAuditRuleService;
import com.vincent.rsf.httpaudit.support.HttpAuditSupport;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Date;
 
/**
 * 缓存请求/响应体并异步写审计表
 */
@Slf4j
@RequiredArgsConstructor
public class HttpAuditFilter extends OncePerRequestFilter {
 
    private final HttpAuditAsyncRecorder recorder;
    private final HttpAuditProperties props;
    private final Environment environment;
    private final HttpAuditRuleService httpAuditRuleService;
 
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return !props.isEnabled() || HttpAuditSupport.shouldExclude(request, props);
    }
 
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper(request, props.getMaxRequestCacheBytes());
        ContentCachingResponseWrapper resWrapper = new ContentCachingResponseWrapper(response);
        long t0 = System.currentTimeMillis();
        Exception chainError = null;
        try {
            filterChain.doFilter(reqWrapper, resWrapper);
        } catch (IOException | ServletException e) {
            chainError = e;
            throw e;
        } catch (RuntimeException e) {
            chainError = e;
            throw e;
        } finally {
            try {
                record(reqWrapper, resWrapper, t0, chainError);
            } catch (Throwable ignore) {
                log.warn("http-audit record 异常已吞掉:{}", ignore.getMessage());
            }
            try {
                resWrapper.copyBodyToResponse();
            } catch (IOException io) {
                log.debug("copyBodyToResponse: {}", io.getMessage());
            }
        }
    }
 
    private void record(ContentCachingRequestWrapper req, ContentCachingResponseWrapper res, long t0, Exception chainError) {
        Charset charset = HttpAuditSupport.resolveCharset(req);
        String ctReq = req.getContentType();
        String reqBody;
        if (ctReq != null && ctReq.toLowerCase().startsWith("multipart/")) {
            reqBody = "[multipart omitted]";
        } else {
            reqBody = HttpAuditSupport.bytesToString(req.getContentAsByteArray(), charset);
        }
 
        HttpAuditDecision dec = httpAuditRuleService.decideInbound(req, reqBody);
        if (!dec.isAudit()) {
            return;
        }
 
        int reqMax = dec.getRequestMaxChars() != null ? dec.getRequestMaxChars() : props.getDefaultRequestStoreChars();
        String reqStored = HttpAuditSupport.storeWithCharLimit(reqBody, reqMax);
 
        String respCt = res.getContentType();
        String resBodyRaw = HttpAuditSupport.bytesToString(res.getContentAsByteArray(), charset);
        int resMax;
        if (dec.getResponseMaxChars() != null) {
            resMax = dec.getResponseMaxChars();
        } else if (HttpAuditSupport.isQueryLike(req)) {
            resMax = props.getQueryResponseMaxChars();
        } else {
            resMax = props.getMaxResponseStoreChars();
        }
 
        String resBodyToStore;
        int truncated = 0;
        if (respCt != null && (respCt.contains("octet-stream") || respCt.contains("application/pdf"))) {
            resBodyToStore = "[binary response omitted]";
            truncated = 1;
        } else {
            resBodyToStore = HttpAuditSupport.storeWithCharLimit(resBodyRaw, resMax);
            if (HttpAuditSupport.overCharLimit(resBodyRaw, resMax)) {
                truncated = 1;
            }
        }
 
        int status = res.getStatus();
        int ok = (chainError == null && status >= 200 && status < 400) ? 1 : 0;
        String errMsg = null;
        if (chainError != null) {
            String s = chainError.toString();
            errMsg = s.length() > 4000 ? s.substring(0, 4000) + "..." : s;
        }
 
        String appName = environment.getProperty("spring.application.name", "unknown");
        String path = HttpAuditSupport.safePath(req);
 
        HttpAuditLog logEntity = new HttpAuditLog()
                .setServiceName(appName)
                .setScopeType(HttpAuditSupport.resolveScope(req, props))
                .setUri(path)
                .setIoDirection("IN")
                .setMethod(req.getMethod())
                .setFunctionDesc(HttpAuditSupport.resolveFunctionDesc(req, props))
                .setQueryString(req.getQueryString())
                .setRequestBody(reqStored)
                .setResponseBody(resBodyToStore)
                .setResponseTruncated(truncated)
                .setHttpStatus(status)
                .setOkFlag(ok)
                .setSpendMs((int) (System.currentTimeMillis() - t0))
                .setClientIp(HttpAuditSupport.clientIp(req))
                .setErrorMessage(errMsg)
                .setCreateTime(new Date())
                .setDeleted(0);
 
        recorder.save(logEntity);
    }
}