0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

(調査中)「レスポンス用のサニタイザと統合した統一ユーティリティ」や「Spring Filter / HandlerInterceptor で自動適用」

Posted at

以下に、あなたの要件に対応する以下の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 認証・トレース向き リクエストだけに処理を入れたい場合はこちらが安全

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?