# SpringBoot过滤器和拦截器

SpringBoot过滤器和拦截器(Filter-And-Interceptor)

# 1. 过滤器

待补充

# 2. 拦截器

待补充

# 3. 日志过滤

记录一下日志输入输出

# 3.1. WebMvc

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.AntPathMatcher;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.pcic.util.AddrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 请求响应日志过滤器
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/5/24 9:18
 */
@Component
@WebFilter(filterName = "RequestLogFilter", urlPatterns = "/**")
public class RequestLogFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);

    /**
     * 忽略URL
     */
    private static final List<String> IGNORE_URL = new ArrayList<String>(){{
        add("/favicon.ico");
        add("/doc.html");
        add("/webjars/**");
        add("/v3/api-docs/**");
        add("/druid/**");
    }};

    private static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";

    private static final String MULTIPART_FORM_DATA = "multipart/form-data";

    private static final String LOGIN_USER_ID_KEY = "LOGIN_USER_ID";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        long timeBegin = System.currentTimeMillis();
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 请求属性获取
        String reqStr = "", respStr = "";
        String ip = AddrUtil.getRemoteAddr(request);
        String url = request.getRequestURI(), method = request.getMethod().toUpperCase();
        String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
        // contentType = contentType != null ? contentType.toLowerCase() : APPLICATION_X_WWW_FORM_URLENCODED;
        Object userId = WebUtils.getSessionAttribute(request, LOGIN_USER_ID_KEY);
        String sessionId = WebUtils.getSessionId(request);
        // 忽略URL跳过
        if (UrlUtils.match(url, IGNORE_URL)) {
            // logger.info("【日志统一过滤】入口:{\"ip\":\"{}\", \"url\":\"{}\", \"user\":\"{}\", \"id\":\"{}\"}", ip, url, userId, sessionId);
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            // 获取请求中的流取出来的字符串,再次转换成流,然后把它放入到新对象中
            RequestWrapper requestWrapper = new RequestWrapper(request);
            ResponseWrapper responseWrapper = new ResponseWrapper(response);
            // GET及application/x-www-form-urlencoded,multipart/form-data直接从Parameter获取参数
            Map paramMap = new HashMap(16);
            Enumeration paramNames = request.getParameterNames();
            while (paramNames.hasMoreElements()) {
                String paramName = (String) paramNames.nextElement();
                String[] paramValues = request.getParameterValues(paramName);
                if (paramValues.length == 1) {
                    String paramValue = paramValues[0];
                    if (paramValue.length() != 0) {
                        paramMap.put(paramName, paramValue);
                    }
                }
            }
            // paramMap为空取Body
            if (paramMap.isEmpty()) {
                reqStr = requestWrapper.getBody();
            } else {
                reqStr = JSONUtil.toJsonStr(paramMap);
            }
            logger.info("【日志统一过滤】入口:{\"ip\":\"{}\", \"url\":\"{}\"},请求参数:{}", ip, url, reqStr);
            filterChain.doFilter(requestWrapper, responseWrapper);
            respStr = responseWrapper.getBody();
            // 将返回值重新写入Response
            response.getOutputStream().write(respStr.getBytes());
            long timeEnd = System.currentTimeMillis();
            logger.info("【日志统一过滤】出口:{\"ip\":\"{}\", \"url\":\"{}\", \"time\":\"{}s\"},请求参数:{},返回参数:{}", ip, url, (timeEnd - timeBegin) / 1000.0D, reqStr, respStr);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void destroy() {

    }

    /**
     * RequestWrapper
     *
     * @author wliduo[i@dolyw.com]
     * @date 2022/5/24 9:37
     */
    public static class RequestWrapper extends HttpServletRequestWrapper {

        private final String body;

        public RequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            StringBuilder stringBuilder = new StringBuilder("");
            try (InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream());
                 BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
                // 不能使用byte,byte一个字节无法兼容中文字符会导致汉字乱码,char两个字节支持单个中文字符
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } finally {
                body = stringBuilder.toString();
            }
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
            ServletInputStream servletInputStream = new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {

                }

                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
            return servletInputStream;
        }

        public String getBody() {
            return this.body;
        }

    }

    /**
     * ResponseWrapper
     *
     * @author wliduo[i@dolyw.com]
     * @date 2022/5/24 9:37
     */
    public static class ResponseWrapper extends HttpServletResponseWrapper {

        private PrintWriter printWriter;
        private ByteArrayOutputStream bytes = new ByteArrayOutputStream();

        public ResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            // 将数据写到byte中
            return new CustomServletOutputStream(bytes);
        }

        /**
         * 重写父类的getWriter()方法,将响应数据缓存在PrintWriter中
         */
        @Override
        public PrintWriter getWriter() throws IOException {
            try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(bytes, "utf-8")) {
                printWriter =  new PrintWriter(outputStreamWriter);
            }
            return printWriter;
        }

        /**
         * 获取缓存在PrintWriter中的响应数据
         *
         * @return
         */
        public byte[] getBytes() {
            if (printWriter != null) {
                printWriter.close();
                return bytes.toByteArray();
            }

            if (bytes != null) {
                try {
                    bytes.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return bytes.toByteArray();
        }

        public String getBody() throws IOException {
            return new String(this.getBytes(), "utf-8");
        }

        class CustomServletOutputStream extends ServletOutputStream {
            private ByteArrayOutputStream byteArrayOutputStream;

            public CustomServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
                this.byteArrayOutputStream = byteArrayOutputStream;
            }

            @Override
            public void write(int b) throws IOException {
                // 将数据写到stream中
                byteArrayOutputStream.write(b);
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setWriteListener(WriteListener listener) {

            }
        }
    }
}

# 3.2. WebFlux

package com.pcic.filter;

import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Header;
import com.alibaba.fastjson.JSON;
import com.pcic.util.AddrUtil;
import com.pcic.util.UrlUtils;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * RequestLogFilter - WebFilter过滤
 *
 * WebFlux必须使用@Bean方式才没问题
 * 使用@Component会出现上下文混乱
 *
 * https://blog.csdn.net/qq_33803102/article/details/110927078
 * https://segmentfault.com/a/1190000016229552
 * https://www.modb.pro/db/170295
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/8/4 10:06
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestLogFilter implements WebFilter {

    /**
     * TRACE_ID
     */
    public static final String TRACE_ID = "TraceId";

    /**
     * StartTime
     */
    public static final String START_TIME = "StartTime";

    /**
     * Path
     */
    public static final String PATH = "Path";

    /**
     * URL
     */
    public static final String URL = "Url";

    /**
     * IP
     */
    public static final String IP = "IP";

    /**
     * RequestData
     */
    public static final String REQUEST_DATA = "RequestData";

    /**
     * ResponseData
     */
    public static final String RESPONSE_DATA = "ResponseData";

    /**
     * WebKitFormBoundary
     */
    public static final String WEBKIT_FORM_BOUNDARY = "WebKitFormBoundary";

    /**
     * 忽略URL
     */
    private static final List<String> IGNORE_URL = new ArrayList<String>() {{
        add("/favicon.ico");
        add("/doc.html");
        add("/webjars/**");
        add("/v3/api-docs/**");
        add("/druid/**");
        add("/login/current");
        add("/login/permissions");
        add("/web/adapt/common/download**");
    }};

    @Autowired
    private AuthEncryptFilter authEncryptFilter;

    @Autowired
    private ServerCodecConfigurer serverCodecConfigurer;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        Mono<Void> mono = null;
        // 请求ID
        exchange.getAttributes().put(TRACE_ID, RandomUtil.randomString(16));
        // 开始时间
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        // 请求IP
        exchange.getAttributes().put(IP, AddrUtil.getIpAddress(exchange.getRequest()));
        // 请求URL
        String path = exchange.getRequest().getURI().getRawPath();
        String query = exchange.getRequest().getURI().getRawQuery();
        String url = StrUtil.isNotBlank(query) ? path + "?" + query : path;
        exchange.getAttributes().put(PATH, path);
        exchange.getAttributes().put(URL, url);
        // 忽略URL跳过
        if (UrlUtils.match(url, IGNORE_URL)) {
            // logger.info("【日志统一过滤】入口:{\"ip\":\"{}\", \"url\":\"{}\", \"user\":\"{}\", \"id\":\"{}\"}", ip, url, userId, sessionId);
            return chain.filter(exchange);
        } else {
            // Method
            String method = exchange.getRequest().getMethodValue();
            // ContentType
            MediaType contentType = exchange.getRequest().getHeaders().getContentType();
            contentType = contentType != null ? contentType : MediaType.APPLICATION_JSON;
            // GET请求直接处理
            if (HttpMethod.GET.toString().equals(method)) {
                Map<String, String> queryParams = exchange.getRequest().getQueryParams().toSingleValueMap();
                exchange.getAttributes().put(REQUEST_DATA, JSON.toJSONString(queryParams));
                RequestLogFilter.log(exchange, true);
                mono = chain.filter(exchange.mutate().response(this.readResponse(exchange)).build());
            } else if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)
                    || contentType.toString().startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                mono = this.readData(exchange, chain);
            } else {
                mono = this.readBody(exchange, chain);
            }
            return mono.then(Mono.fromRunnable(() -> RequestLogFilter.log(exchange, false)));
        }
    }

    /**
     * 读取Body数据
     *
     * @param exchange
     * @param chain
     * @return reactor.core.publisher.Mono<java.lang.Void>
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/8/4 15:12
     */
    private Mono<Void> readBody(ServerWebExchange exchange, WebFilterChain chain) {
        // ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
        // 使用默认参数缓冲区只能256K,切换为自定义大小 spring.codec.max-in-memory-size = 16MB
        ServerRequest serverRequest = ServerRequest.create(exchange, serverCodecConfigurer.getReaders());
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            // 重写数据
            exchange.getAttributes().put(REQUEST_DATA, body);
            RequestLogFilter.log(exchange, true);
            // 解密处理
            String content = authEncryptFilter.dataDecrypt(exchange);
            if (StrUtil.isNotBlank(content)) {
                return Mono.just(content);
            }
            return Mono.just(body);
        });
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        Mono<Void> mono = bodyInserter.insert(outputMessage, new BodyInserterContext());
        return mono.then(Mono.defer(() -> {
            ServerHttpRequestDecorator newRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public HttpHeaders getHeaders() {
                    long contentLength = headers.getContentLength();
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }

                @Override
                public Flux<DataBuffer> getBody() {
                    return outputMessage.getBody();
                }
            };
            return chain.filter(exchange.mutate().request(newRequest).response(this.readResponse(exchange)).build());
        }));
    }

    /**
     * 读取Data数据
     *
     * @param exchange
     * @param chain
     * @return reactor.core.publisher.Mono<java.lang.Void>
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/8/4 15:11
     */
    private Mono<Void> readData(ServerWebExchange exchange, WebFilterChain chain) {
        Mono<DataBuffer> dataBufferMono = DataBufferUtils.join(exchange.getRequest().getBody());
        // 请求数据为空不会进入flatMap处理逻辑
        Mono<Void> mono = dataBufferMono.flatMap(dataBuffer -> {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            try {
                String data = new String(bytes, StandardCharsets.UTF_8);
                // 是否包含WebKitFormBoundary数据
                if (data.contains(WEBKIT_FORM_BOUNDARY)) {
                    String contentType = exchange.getRequest().getHeaders().getContentType().toString();
                    String[] contentTypeId = contentType.split(WEBKIT_FORM_BOUNDARY);
                    List<String> paramList = StrSplitter.split(data, WEBKIT_FORM_BOUNDARY + contentTypeId[1], true, true);
                    // 循环内移除元素必须使用迭代器遍历
                    for (Iterator<String> iterator = paramList.iterator(); iterator.hasNext();) {
                        String param = iterator.next();
                        if (!param.contains(Header.CONTENT_DISPOSITION.getValue())) {
                            // 不包含Content-Disposition说明是无效数据
                            iterator.remove();
                        }
                        if (param.contains(Header.CONTENT_DISPOSITION.getValue()) && param.contains(Header.CONTENT_TYPE.getValue())) {
                            // 包含Content-Type说明是文件参数
                            iterator.remove();
                        }
                    }
                    exchange.getAttributes().put(REQUEST_DATA, JSON.toJSONString(paramList));
                } else {
                    exchange.getAttributes().put(REQUEST_DATA, data);
                }
            } catch (Exception e) {
                String traceId = exchange.getAttribute(TRACE_ID);
                log.warn("读取Data数据解析参数异常,请求轨迹:{}", traceId, e);
            }
            DataBufferUtils.release(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                return Mono.just(buffer);
            });
            ServerHttpRequest newRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };
            return chain.filter(exchange.mutate().request(newRequest).response(this.readResponse(exchange)).build());
        });
        RequestLogFilter.log(exchange, true);
        return mono.then(Mono.defer(() -> {
            String requestData = exchange.getAttribute(REQUEST_DATA);
            if (StrUtil.isNotBlank(requestData)) {
                return mono;
            } else {
                // 请求数据为空,必须走chain.filter处理,否则不会进异常处理,会返回空数据
                return chain.filter(exchange.mutate().response(this.readResponse(exchange)).build());
            }
        }));
    }

    /**
     * 读取返回数据,创建新的Response装饰对象,并传入原始Response对象,然后重写writeWith方法
     *
     * @param exchange
     * @return org.springframework.http.server.reactive.ServerHttpResponseDecorator
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/8/4 14:18
     */
    private ServerHttpResponseDecorator readResponse(ServerWebExchange exchange) {
        return new ServerHttpResponseDecorator(exchange.getResponse()) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                Mono<DataBuffer> mono = DataBufferUtils.join(body).map(buffer -> {
                    try {
                        // 重写数据
                        byte[] bytes;
                        String data = buffer.toString(StandardCharsets.UTF_8);
                        exchange.getAttributes().put(RESPONSE_DATA, data);
                        // 加密处理
                        String content = authEncryptFilter.dataEncrypt(exchange);
                        if (StrUtil.isNotBlank(content)) {
                            bytes = content.getBytes(StandardCharsets.UTF_8);
                        } else {
                            bytes = data.getBytes(StandardCharsets.UTF_8);
                        }
                        getHeaders().setContentLength(bytes.length);
                        return this.bufferFactory().wrap(bytes);
                    } finally {
                        DataBufferUtils.release(buffer);
                    }
                });
                return super.writeWith(mono);
            }
        };
    }

    /**
     * 日志输出
     *
     * @param exchange
     * @param start
     * @return void
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/8/4 14:30
     */
    public static void log(ServerWebExchange exchange, boolean start) {
        String traceId = exchange.getAttribute(TRACE_ID);
        String ip = exchange.getAttribute(IP);
        String url = exchange.getAttribute(URL);
        String requestData = exchange.getAttribute(REQUEST_DATA);
        if (start) {
            log.info("【日志统一过滤】入口:{\"ip\":\"{}\", \"url\":\"{}\", \"traceId\":\"{}\"},请求参数:{}", ip, url, traceId, requestData);
        } else {
            long startTime = exchange.getAttribute(START_TIME);
            String responseData = exchange.getAttribute(RESPONSE_DATA);
            log.info("【日志统一过滤】出口:{\"ip\":\"{}\", \"url\":\"{}\", \"traceId\":\"{}\", \"time\":\"{}s\"},请求参数:{},返回参数:{}",
                    ip, url, traceId, (System.currentTimeMillis() - startTime) / 1000.0D, requestData, responseData);
        }
    }
}

# 3.3. 工具类

package com.pcic.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.util.AntPathMatcher;

import java.util.List;

/**
 * UrlUtils
 *
 * @author wliduo[i@dolyw.com]
 * @date 2022/10/17 13:47
 */
public class UrlUtils {

    /**
     * 判断字符是否匹配
     * ?  表示单个字符
     * *  表示一层路径内的任意字符串,不可跨层级
     * ** 表示任意层路径
     *
     * @param urlPath 需要匹配的url
     * @param urls 匹配规则
     * @return boolean
     * @throws
     * @author wliduo[i@dolyw.com]
     * @date 2022/5/31 19:41
     */
    public static boolean match(String urlPath, List<String> urls) {
        if (StrUtil.isEmpty(urlPath) || CollUtil.isEmpty(urls)) {
            return false;
        }
        for (String url : urls) {
            AntPathMatcher matcher = new AntPathMatcher();
            if (matcher.match(url, urlPath)) {
                return true;
            }
        }
        return false;
    }
}
package com.pcic.util;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Optional;

/**
 * 获取IP地址工具
 *
 * @author wliduo[i@dolyw.com]
 * @date 2021/9/9 19:19
 */
@Slf4j
public class AddrUtil {

    /**
     * unknown
     */
    private final static String UNKNOWN_STR = "unknown";

    /**
     * 127.0.0.1
     */
    private final static String LOCALHOST_STR = "127.0.0.1";

    /**
     * 0:0:0:0:0:0:0:1
     */
    private final static String LOCALHOST_IP_STR = "0:0:0:0:0:0:0:1";

    /**
     * IP_LEN
     */
    private static final int IP_LEN = 15;

    /**
     * 获取客户端IP地址
     */
    public static String getRemoteAddr(HttpServletRequest request) {
        /*Enumeration paramNames = request.getHeaderNames();
        while (paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            log.info(paramName + ": " + request.getHeader(paramName));
        }*/
        String ip = request.getHeader("X-Forwarded-For");
        if (isEmptyIp(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            if (isEmptyIp(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
                if (isEmptyIp(ip)) {
                    ip = request.getHeader("HTTP_CLIENT_IP");
                    if (isEmptyIp(ip)) {
                        ip = request.getHeader("X-Real-IP");
                        if (isEmptyIp(ip)) {
                            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
                            if (isEmptyIp(ip)) {
                                ip = request.getRemoteAddr();
                                if (LOCALHOST_STR.equals(ip) || LOCALHOST_IP_STR.equals(ip)) {
                                    // 根据网卡取本机配置的IP
                                    ip = getLocalAddr();
                                }
                            }
                        }
                    }
                }
            }
        } else if (ip.length() > IP_LEN) {
            String[] ips = ip.split(",");
            for (int index = 0; index < ips.length; index++) {
                String strIp = ips[index];
                if (!isEmptyIp(ip)) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }

    /**
     * IP是否为空
     *
     * @param ip
     * @return
     */
    private static boolean isEmptyIp(String ip) {
        if (StrUtil.isEmpty(ip) || UNKNOWN_STR.equalsIgnoreCase(ip)) {
            return true;
        }
        return false;
    }

    /**
     * 获取本机的IP地址
     */
    public static String getLocalAddr() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("InetAddress.getLocalHost() is Error", e);
        }
        return "";
    }

    /**
     * 获取用户真实IP地址,不直接使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
     *
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,究竟哪个才是真正的用户端的真实IP呢?
     * 答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
     *
     * 如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
     * 192.168.1.100
     *
     * 用户真实IP为: 192.168.1.110
     *
     * @param request
     * @return
     */
    public static String getIpAddress(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        // log.info(JSONUtil.toJsonStr(headers));
        String ipAddress = headers.getFirst("X-ClientIP");
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("X-Forwarded-For");
        }
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("Proxy-Client-IP");
        }
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("X-Real-IP");
        }
        if (StrUtil.isEmpty(ipAddress) || UNKNOWN_STR.equalsIgnoreCase(ipAddress)) {
            ipAddress = Optional.ofNullable(request.getRemoteAddress())
                    .map(address -> address.getAddress().getHostAddress())
                    .orElse("");
            if (LOCALHOST_STR.equals(ipAddress)|| LOCALHOST_IP_STR.equals(ipAddress)) {
                // 根据网卡取本机配置的IP
                ipAddress = getLocalAddr();
            }
        }

        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > IP_LEN) {
            int index = ipAddress.indexOf(",");
            if (index > 0) {
                ipAddress = ipAddress.substring(0, index);
            }
        }
        return ipAddress;
    }

    /**
     * 获取客户端端口
     * @param request
     * @return
     */
    public static Integer getPort(ServerHttpRequest request){
        return request.getRemoteAddress().getPort();
    }
}

参考

上次更新时间: 2023-12-15 03:14:55