Spring Boot拦截Http请求设置请求头

ClientHttpRequestInterceptor拦截器

在Spring Boot中,你可以通过拦截RestTemplate请求 并在发送前设置请求头,通常有以下两种方式实现:

1. 使用ClientHttpRequestInterceptor

通过自定义拦截器ClientHttpRequestInterceptor,在intercept方法中修改请求头,然后将拦截器添加到RestTemplate实例中。

步骤:

  1. 创建拦截器类实现ClientHttpRequestInterceptor接口
  2. intercept方法中添加或修改请求头
  3. 将拦截器注册到RestTemplate

示例代码:

java 复制代码
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Collections;

public class HeaderInterceptor implements ClientHttpRequestInterceptor {
    
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                      ClientHttpRequestExecution execution) throws IOException {
        // 添加请求头
        request.getHeaders().add("Authorization", "Bearer your-token");
        request.getHeaders().add("Custom-Header", "custom-value");
        
        return execution.execute(request, body);
    }
}

// 配置RestTemplate
@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Collections.singletonList(new HeaderInterceptor()));
    return restTemplate;
}

2. 使用RestTemplateBuilder(推荐)

通过RestTemplateBuilder可以更方便地添加拦截器,支持链式调用。

示例代码:

java 复制代码
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
                .additionalInterceptors(new HeaderInterceptor())
                .build();
    }
}

3. 动态设置请求头(针对特定请求)

如果某些请求头需要动态设置(如从上下文获取token),可以在拦截器中加入逻辑:

java 复制代码
public class DynamicHeaderInterceptor implements ClientHttpRequestInterceptor {
    
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                      ClientHttpRequestExecution execution) throws IOException {
        // 从安全上下文或其他来源动态获取值
        String token = SecurityContextHolder.getContext().getAuthentication().getCredentials().toString();
        request.getHeaders().add("Authorization", "Bearer " + token);
        
        return execution.execute(request, body);
    }
}

注意事项:

  • 拦截器会应用于所有通过该RestTemplate发起的请求
  • 如果需要为不同请求设置不同请求头,可在拦截器中根据request.getURI()判断路径
  • 考虑使用ThreadLocal存储线程安全的上下文信息(如请求ID)

如果需要更细粒度的控制,可以考虑使用RestTemplateexchange()方法直接传入HttpEntity(包含头部信息),但这种方式需要在每次调用时显式设置。

要统一拦截所有HTTP请求(包括RestTemplate、WebClient、Feign等),可以通过以下几种方式实现:

1. 使用Spring的ClientHttpRequestInterceptor(仅适用于RestTemplate)

适用于所有通过RestTemplate发起的请求,但对其他客户端无效。

2. 使用Servlet Filter(最通用)

Filter可以拦截所有进入容器的HTTP请求,包括:

  • RestTemplate发起的请求
  • WebClient发起的请求
  • Feign客户端发起的请求
  • 任何其他HTTP客户端发起的请求

示例代码:

java 复制代码
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
@Order(1) // 执行顺序
public class GlobalHttpFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 添加全局请求头
        if (httpRequest.getHeader("Authorization") == null) {
            // 这里可以设置默认认证头或其他通用头
        }
        
        // 记录请求日志
        System.out.println("Request URL: " + httpRequest.getRequestURL());
        System.out.println("Method: " + httpRequest.getMethod());
        
        chain.doFilter(request, response); // 继续执行过滤器链
    }
}

3. 使用AOP面向切面编程

通过AOP拦截所有HTTP客户端调用方法:

java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class HttpRequestAspect {

    @Around("execution(* org.springframework.web.client.RestTemplate.*(..)) || " +
            "execution(* org.springframework.web.reactive.function.client.WebClient.*(..))")
    public Object aroundHttpRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        
        // 在方法执行前添加请求头
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof HttpEntity) {
                HttpEntity<?> entity = (HttpEntity<?>) args[i];
                HttpHeaders headers = new HttpHeaders();
                headers.putAll(entity.getHeaders());
                headers.add("Global-Header", "common-value");
                
                // 替换为新的HttpEntity
                args[i] = new HttpEntity<>(entity.getBody(), headers);
            }
        }
        
        return joinPoint.proceed(args);
    }
}

4. 使用OkHttp的Interceptor(如果使用OkHttp作为底层客户端)

java 复制代码
import okhttp3.Interceptor;
import okhttp3.Response;
import java.io.IOException;

public class GlobalOkHttpInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        return chain.proceed(chain.request().newBuilder()
                .addHeader("Authorization", "Bearer token")
                .addHeader("X-Request-ID", UUID.randomUUID().toString())
                .build());
    }
}

5. 推荐方案:组合使用

拦截方式 适用范围 优点 缺点
Servlet Filter 所有HTTP请求 最通用,覆盖全面 无法区分内部/外部请求
AOP 方法级别拦截 精确控制,可区分业务 配置相对复杂
ClientHttpRequestInterceptor 仅RestTemplate 简单易用 局限性大
OkHttp Interceptor 使用OkHttp的客户端 性能好 需要统一HTTP客户端

最佳实践建议:

  1. 使用Servlet Filter处理全局通用的请求头(如追踪ID、基础认证)
  2. 使用AOP处理业务特定的拦截逻辑
  3. 根据项目使用的HTTP客户端选择相应的拦截机制

根据你的需求,Servlet Filter通常是最合适的统一拦截方案。

手动拦截并设置(反射):

java 复制代码
public void setRequestHeader(String key, String value) {
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
            return;
        }
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (null == attributes) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        Class<? extends HttpServletRequest> requestClass = request.getClass();
        try {
            // 反射获取request属性
            Field headerField = requestClass.getDeclaredField("headerMap");
            headerField.setAccessible(true);
            Map<String, String> headerMap = (Map<String, String>) headerField.get(request);
            headerMap.put(key, value);
        } catch (Exception e) {
            log.error("set header error: ", e);
        }
    }

ClientHttpRequestInterceptor 会在 每次 通过配置了该拦截器的 RestTemplate 实例发起 HTTP 调用之前执行。

工作原理

当你调用 RestTemplate 的任何方法(如 getForObject, postForEntity, exchange 等)时,Spring 会按以下顺序处理:

  1. 你调用 restTemplate.getForObject(...)
  2. Spring 开始构建实际的 HTTP 请求
  3. 在执行网络调用之前 ,Spring 会遍历所有已注册的 ClientHttpRequestInterceptor
  4. 按顺序调用每个拦截器的 intercept 方法
  5. 在你的 intercept 方法中,你可以检查和修改 请求(HttpRequest)和请求体(byte[] body
  6. 所有拦截器执行完毕后,最终调用 execution.execute(request, body) 来真正地发送网络请求

关键特性

  • 每次调用都执行 :无论这个 RestTemplate 实例被调用多少次,拦截器都会在每次请求前工作。
  • 适用于所有方法 :它不区分 get, post, put, delete 等方法,对所有通过该 RestTemplate 发起的请求一视同仁。
  • 可设置多个拦截器 :你可以为一个 RestTemplate 添加多个拦截器,它们会按照添加的顺序依次执行。

示例场景验证

假设你有以下配置和代码:

java 复制代码
// 1. 定义一个打印日志的拦截器
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        System.out.println("【拦截器执行】准备发送请求至: " + request.getURI());
        // ... 可以在这里添加请求头
        return execution.execute(request, body);
    }
}

// 2. 将拦截器添加到 RestTemplate
@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor()));
    return restTemplate;
}

// 3. 在服务中多次使用 RestTemplate
@Service
public class MyService {

    @Autowired
    private RestTemplate restTemplate;

    public void fetchData() {
        // 第一次调用
        String result1 = restTemplate.getForObject("https://api.example.com/data/1", String.class);
        
        // 第二次调用
        String result2 = restTemplate.getForObject("https://api.example.com/data/2", String.class);
    }
}

当你调用 myService.fetchData() 时,控制台会输出:

复制代码
【拦截器执行】准备发送请求至: https://api.example.com/data/1
【拦截器执行】准备发送请求至: https://api.example.com/data/2

这清楚地证明了拦截器为每次请求都执行了一次。

总结

你可以放心地依赖 ClientHttpRequestInterceptor 来执行诸如添加认证令牌、设置公共请求头、记录请求日志、重试逻辑等需要在每个请求发出前 完成的统一操作。它的设计初衷就是为此类场景服务的。

Attribute和Header

  • Attribute(属性)服务端内部 的"便签",用于在一次请求的生命周期内 ,在服务器内部的各个组件(如Controller、Service、Interceptor、Filter、View等)之间传递数据。客户端(如浏览器)完全不知道它的存在。
  • Header(头信息)客户端和服务器之间 正式通信协议(HTTP)的一部分,用于传递元数据(如内容类型、认证信息、缓存控制等)。它对客户端是可见的,并且通常会影响请求/响应的处理方式。

下面是一个详细的对比表格,帮助你更清晰地理解:

特性 Attribute(属性) Header(头信息)
作用域 服务器端的一次请求生命周期内(Request Scope)** HTTP协议本身 ,在客户端和服务器之间传输
可见性 仅服务器内部可见(如Filter, Interceptor, Controller, Service, JSP/Thymeleaf等) 客户端和服务器都可见,可通过浏览器开发者工具或抓包工具查看
用途 在服务器处理请求的过程中,在不同组件间传递数据 。例如: - Filter校验用户后,将用户信息存入attribute供Controller使用 - Interceptor计算耗时后,将耗时结果存入attribute 传递HTTP协议的元数据 。例如: - Content-Type : 声明请求体/响应体的格式(如application/json) - Authorization : 传递认证凭证(如Bearer token) - User-Agent : 声明客户端类型 - Custom-Header : 自定义业务头(如X-Request-ID用于全链路追踪)
生命周期 从请求进入服务器开始,到服务器返回响应结束。请求结束即销毁 请求头(Request Header) 随请求从客户端发往服务器。 响应头(Response Header) 随响应从服务器发往客户端。
数据类型 可以是任何Java对象(Object 只能是字符串 (String)或字符串数组(String[])
操作方式 (Java) request.setAttribute("key", value); Object value = request.getAttribute("key"); 获取请求头: String value = request.getHeader("Header-Name"); 设置响应头: response.setHeader("Header-Name", "value"); response.addHeader("Header-Name", "value");

类比帮助理解

你可以把它们想象成寄送一个实体包裹:

  • Header 就像是包裹外面的快递单 。上面写明了收件人地址、寄件人信息、包裹类型(易碎品)、是否需要代收款等。快递员和收发货双方都能看到这个单子,并根据上面的信息来处理包裹。
  • Attribute 就像是包裹里面的一张内部纸条 ,是发货人写给仓库分拣员的,比如"这是VIP客户的加急订单,优先处理"。只有打开包裹的内部人员(服务器)才能看到这张纸条,收货人(客户端)根本不知道它的存在。

代码示例

1. 操作 Header (在Servlet Filter或Controller中)

java 复制代码
// 从客户端发来的请求中获取头信息
String authHeader = httpRequest.getHeader("Authorization");
String userAgent = httpRequest.getHeader("User-Agent");

// 向客户端发送响应时设置头信息
httpResponse.setHeader("Content-Type", "application/json");
httpResponse.addHeader("X-Custom-Header", "MyValue");

2. 操作 Attribute (在Filter, Interceptor, Controller中)

java 复制代码
// 在Filter中设置属性
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    // 认证成功后,将用户信息存入attribute
    User user = authenticateUser(httpRequest);
    httpRequest.setAttribute("currentUser", user); // 存入任何对象
    chain.doFilter(request, response);
}

// 在Controller中获取和使用属性
@GetMapping("/api/profile")
public ResponseEntity getUserProfile(HttpServletRequest request) {
    // 从attribute中取出之前存入的对象
    User currentUser = (User) request.getAttribute("currentUser");
    // ... 使用user对象处理业务
    return ResponseEntity.ok(currentUser.getProfile());
}

总结

记住这个最简单的原则:

  • 需要让客户端知晓或受其影响 的信息,用 Header(如认证、缓存、内容协商)。
  • 只在服务器内部处理过程中 需要临时携带的数据,用 Attribute(如用户身份、计算中间结果、页面渲染数据)。
相关推荐
TechPioneer_lp2 小时前
小红书后端实习一面|1小时高强度技术追问实录
java·后端·面试·个人开发
你这个代码我看不懂2 小时前
SpringBoot单元测试Mock和Spy
spring boot·单元测试·log4j
huahailing10243 小时前
Spring 循环依赖终极解决方案:从原理到实战(附避坑指南)
java·后端·spring
Java程序员威哥4 小时前
SpringBoot2.x与3.x自动配置注册差异深度解析:从原理到迁移实战
java·大数据·开发语言·hive·hadoop·spring boot·后端
shejizuopin4 小时前
基于Spring Boot+小程序的非遗科普平台设计与实现(毕业论文)
spring boot·后端·小程序·毕业设计·论文·毕业论文·非遗科普平台设计与实现
Grassto4 小时前
10 Go 是如何下载第三方包的?GOPROXY 与源码解析
后端·golang·go·go module
MX_93594 小时前
以配置非自定义bean来演示bean的实例化方式
java·开发语言·后端
vx_bisheyuange5 小时前
【源码免费送】计算机毕设精选项目:基于SpringBoot的汽车租赁系统的设计与实现
spring boot·汽车·毕业设计·需求分析
浅水壁虎5 小时前
任务调度——XXLJOB3(执行器)
java·服务器·前端·spring boot