From 2a7be2ca920d84ad7908c7ad021ad1a6df653cdb Mon Sep 17 00:00:00 2001 From: hy001 Date: Mon, 18 May 2026 12:15:24 +0800 Subject: [PATCH] fix: sanitize forwarded feign headers --- .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ .../web/feign/FeignRequestInterceptor.java | 117 ++++++++++++++++++ 8 files changed, 936 insertions(+) create mode 100644 rc-auth/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-scheduling/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-service/rc-service-console/console-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-service/rc-service-external/external-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-service/rc-service-live/live-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-service/rc-service-order/order-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java create mode 100644 rc-service/rc-service-wallet/wallet-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java diff --git a/rc-auth/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-auth/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-auth/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-scheduling/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-scheduling/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-scheduling/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-service/rc-service-console/console-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-service/rc-service-console/console-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-service/rc-service-console/console-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-service/rc-service-external/external-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-service/rc-service-live/live-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-service/rc-service-live/live-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-service/rc-service-live/live-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-service/rc-service-order/order-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-service/rc-service-order/order-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-service/rc-service-order/order-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-service/rc-service-other/other-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +} diff --git a/rc-service/rc-service-wallet/wallet-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java b/rc-service/rc-service-wallet/wallet-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java new file mode 100644 index 00000000..54a55a3b --- /dev/null +++ b/rc-service/rc-service-wallet/wallet-start/src/main/java/com/red/circle/framework/web/feign/FeignRequestInterceptor.java @@ -0,0 +1,117 @@ +package com.red.circle.framework.web.feign; + +import com.red.circle.framework.core.request.RequestHeaderConstant; +import com.red.circle.framework.core.request.ServiceTraceId; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Forward only business headers to Feign requests. + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class FeignRequestInterceptor implements RequestInterceptor { + + private static final String INNER_SERVICE = "FEIGN"; + + private static final List FORWARD_HEADERS = List.of( + RequestHeaderConstant.AUTHORIZATION, + RequestHeaderConstant.REQ_SYS_ORIGIN, + RequestHeaderConstant.REQ_CLIENT, + RequestHeaderConstant.REQ_ZONE, + RequestHeaderConstant.REQ_IMEI, + RequestHeaderConstant.REQ_APP_INTEL, + RequestHeaderConstant.REQ_VERSION, + RequestHeaderConstant.REQ_LANG, + RequestHeaderConstant.REQ_INNER_INTERNAL, + RequestHeaderConstant.REQ_INNER_TRACE_ID + ); + + private static final Set BLOCKED_HEADERS = Set.of( + "host", + "content-length", + "transfer-encoding", + "connection", + "keep-alive", + "upgrade", + "proxy-authorization", + "proxy-authenticate", + "te", + "trailer" + ); + + @Override + public void apply(RequestTemplate requestTemplate) { + removeBlockedHeaders(requestTemplate); + + if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attrs) { + forwardHeaders(requestTemplate, attrs.getRequest()); + } + + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_SERVICE)) { + requestTemplate.header(RequestHeaderConstant.REQ_INNER_SERVICE, INNER_SERVICE); + } + if (!hasHeader(requestTemplate, RequestHeaderConstant.REQ_INNER_TRACE_ID)) { + requestTemplate.header( + RequestHeaderConstant.REQ_INNER_TRACE_ID, + ServiceTraceId.genTraceId(ServiceTraceId.Origin.INNER_REQ)); + } + + removeBlockedHeaders(requestTemplate); + } + + private void forwardHeaders(RequestTemplate requestTemplate, HttpServletRequest request) { + for (String header : FORWARD_HEADERS) { + String value = request.getHeader(header); + if (hasHeader(requestTemplate, header) || !isUsableHeaderValue(value)) { + continue; + } + requestTemplate.header(header, value); + } + } + + private void removeBlockedHeaders(RequestTemplate requestTemplate) { + List names = new ArrayList<>(requestTemplate.headers().keySet()); + for (String name : names) { + if (isBlockedHeader(name)) { + requestTemplate.removeHeader(name); + } + } + } + + private boolean hasHeader(RequestTemplate requestTemplate, String header) { + return requestTemplate.headers().keySet().stream() + .anyMatch(name -> name.equalsIgnoreCase(header)); + } + + private boolean isBlockedHeader(String header) { + if (header == null) { + return true; + } + String normalized = header.toLowerCase(Locale.ROOT); + return BLOCKED_HEADERS.contains(normalized) || normalized.startsWith("proxy-"); + } + + private boolean isUsableHeaderValue(String value) { + if (value == null || value.isBlank()) { + return false; + } + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c < 32 || c == 127) { + return false; + } + } + return true; + } +}