以下に、あなたの要件に対応する以下の2つを紹介します:
✅ ① リクエスト/レスポンス両方のサニタイザを統合した共通ユーティリティクラス
→ HttpHeaderSanitizerUtil
に統合
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.util.*;
public class HttpHeaderSanitizerUtil {
// --- リクエスト用:削除対象ヘッダー ---
private static final Set<String> REQUEST_SENSITIVE_HEADERS = Set.of(
HttpHeaders.HOST,
HttpHeaders.CONTENT_LENGTH,
HttpHeaders.TRANSFER_ENCODING,
HttpHeaders.CONNECTION,
HttpHeaders.EXPECT,
HttpHeaders.TE,
HttpHeaders.UPGRADE,
HttpHeaders.COOKIE,
HttpHeaders.AUTHORIZATION // 任意許可制
);
// --- レスポンス用:削除対象ヘッダー ---
private static final Set<String> RESPONSE_SENSITIVE_HEADERS = Set.of(
HttpHeaders.TRANSFER_ENCODING,
HttpHeaders.SERVER,
HttpHeaders.DATE,
HttpHeaders.CONNECTION,
HttpHeaders.SET_COOKIE,
HttpHeaders.SET_COOKIE2,
HttpHeaders.WWW_AUTHENTICATE,
HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
HttpHeaders.LOCATION,
HttpHeaders.CACHE_CONTROL,
HttpHeaders.ETAG
);
/** リクエスト用サニタイズ */
public static HttpHeaders sanitizeRequestHeaders(HttpHeaders original, Set<String> allowedAdditional) {
HttpHeaders sanitized = new HttpHeaders();
for (Map.Entry<String, List<String>> entry : original.entrySet()) {
String headerName = entry.getKey();
if (!REQUESTSENSITIVE_HEADERS.contains(headerName)
|| (allowedAdditional != null && allowedAdditional.contains(headerName))) {
sanitized.put(headerName, entry.getValue());
}
}
return sanitized;
}
/** レスポンス用サニタイズ */
public static HttpHeaders sanitizeResponseHeaders(HttpHeaders original, long contentLength, MediaType contentType) {
HttpHeaders sanitized = new HttpHeaders();
for (String key : original.keySet()) {
if (!RESPONSE_SENSITIVE_HEADERS.contains(key)) {
sanitized.put(key, original.get(key));
}
}
sanitized.setContentLength(contentLength);
if (contentType != null) {
sanitized.setContentType(contentType);
}
return sanitized;
}
}
✅ ② Spring Filter または HandlerInterceptor による自動適用
✅ OncePerRequestFilter
による実装(レスポンスヘッダーの最終制御)
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class ResponseHeaderSanitizingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// ラップして書き込みをフック
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
if (!shouldSuppressHeader(name)) {
super.setHeader(name, value);
}
}
@Override
public void addHeader(String name, String value) {
if (!shouldSuppressHeader(name)) {
super.addHeader(name, value);
}
}
private boolean shouldSuppressHeader(String name) {
return HttpHeaderSanitizerUtil.RESPONSE_SENSITIVE_HEADERS
.stream()
.anyMatch(h -> h.equalsIgnoreCase(name));
}
};
filterChain.doFilter(request, responseWrapper);
}
}
※このフィルターはすべてのレスポンスヘッダーに適用されます。特定のControllerに限定したい場合は、条件分岐で判定を追加してください。
✅ コントローラ内での利用例(両方のユーティリティを適用)
@GetMapping("/proxy")
public Mono<ResponseEntity<byte[]>> proxy(@RequestHeader HttpHeaders incomingHeaders) {
// リクエストヘッダーのクリーンアップ
HttpHeaders sanitizedRequestHeaders = HttpHeaderSanitizerUtil.sanitizeRequestHeaders(
incomingHeaders, Set.of(HttpHeaders.AUTHORIZATION)
);
return webClient.get()
.uri("https://example.com/api")
.headers(h -> h.putAll(sanitizedRequestHeaders))
.exchangeToMono(res -> res.bodyToMono(byte[].class).map(body -> {
HttpHeaders sanitizedResponseHeaders = HttpHeaderSanitizerUtil.sanitizeResponseHeaders(
res.headers().asHttpHeaders(),
body.length,
res.headers().contentType().orElse(null)
);
return new ResponseEntity<>(body, sanitizedResponseHeaders, res.statusCode());
}));
}
✅ 補足
方法 | 主な用途 | 特徴 |
---|---|---|
ユーティリティクラス | 中継系処理(WebClientなど) | 明示的に使う設計がしやすい |
Filter | 全てのレスポンスに対して | グローバル適用。誤爆の危険もあるので慎重に |
HandlerInterceptor | 認証・トレース向き | リクエストだけに処理を入れたい場合はこちらが安全 |