fix: sanitize forwarded feign headers

This commit is contained in:
hy001 2026-05-18 12:15:24 +08:00
parent d49341204c
commit 2a7be2ca92
8 changed files with 936 additions and 0 deletions

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}

View File

@ -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<String> 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<String> 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<String> 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;
}
}