You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot调用外部Rest API遇403错误(缺失API Key)排查

问题:调用外部API返回403(缺失API Key)

我在Spring Boot应用中调用外部Rest API端点,已提前生成该API可用的API Key,但每次发送GET请求时,都会返回缺失API Key 403禁止访问错误。

报错堆栈

org.springframework.web.client.HttpClientErrorException$Forbidden: 403 Forbidden: "{\"error\":\"Missing API key\"}"
    at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:109)
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:168)
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:122)
    at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:819)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:777)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:711)
    at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:361)
    at com.rakhi.restapidemo.controller.CloudHealthAccessController.getCloudHealthReports(CloudHealthAccessController.java:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:670)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:750)

尝试的错误代码

@RequestMapping(value = {"/reports"}, method = {RequestMethod.GET, RequestMethod.POST}, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> getCloudHealthReports(){
    logger.info("Inside get cloud health reporting function..!!!!");
    try {
        String apiKey = "abc-xyz-example-apikey-e215d82537ba";
        final String uri = "https://chapi.cloudhealthtech.com/olap_reports";
        RestTemplate restTemplate = new RestTemplate();
        
        URL url = new URL(uri);
        HttpURLConnection http = (HttpURLConnection)url.openConnection();
        
        http.setRequestProperty("Accept", "application/json");
        http.setRequestProperty(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", apiKey));
        
        ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
        
        return new ResponseEntity<>(response.getHeaders(), HttpStatus.OK);
        
    } catch (Exception e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return new ResponseEntity<>("Error! Please try again later", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

问题分析与解决方案

你的代码核心问题:创建并配置了HttpURLConnection,但实际发送请求用的是RestTemplate,这两个是完全独立的实例,所以你设置的请求头根本没有被RestTemplate使用,导致API端无法接收到API Key,返回403错误。

正确写法1:通过HttpEntity传递请求头给RestTemplate

直接给RestTemplate的请求设置需要的头信息,示例代码如下:

@RequestMapping(value = {"/reports"}, method = {RequestMethod.GET, RequestMethod.POST}, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> getCloudHealthReports(){
    logger.info("Inside get cloud health reporting function..!!!!");
    try {
        String apiKey = "abc-xyz-example-apikey-e215d82537ba";
        final String uri = "https://chapi.cloudhealthtech.com/olap_reports";
        RestTemplate restTemplate = new RestTemplate();
        
        // 构造请求头
        HttpHeaders headers = new HttpHeaders();
        headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
        headers.set(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", apiKey));
        
        // 包装请求头为HttpEntity
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        // 发送请求时传递HttpEntity
        ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
        
        return new ResponseEntity<>(response.getBody(), HttpStatus.OK);
        
    } catch (Exception e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return new ResponseEntity<>("Error! Please try again later", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

正确写法2:全局配置RestTemplate拦截器(适合多接口复用)

如果你的应用中多个请求都需要携带该API Key,可以配置全局的RestTemplate拦截器,自动添加请求头:

// 配置RestTemplate的Bean
@Configuration
public class RestTemplateConfig {
    @Value("${external.api.key}")
    private String apiKey;

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getInterceptors().add((request, body, execution) -> {
            request.getHeaders().set("Accept", MediaType.APPLICATION_JSON_VALUE);
            request.getHeaders().set(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", apiKey));
            return execution.execute(request, body);
        });
        return restTemplate;
    }
}

然后在控制器中注入使用:

@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = {"/reports"}, method = {RequestMethod.GET, RequestMethod.POST}, produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> getCloudHealthReports(){
    logger.info("Inside get cloud health reporting function..!!!!");
    try {
        final String uri = "https://chapi.cloudhealthtech.com/olap_reports";
        
        ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
        
        return new ResponseEntity<>(response.getBody(), HttpStatus.OK);
        
    } catch (Exception e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return new ResponseEntity<>("Error! Please try again later", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

额外注意点

  • 确认目标API的认证方式是否为Bearer Token:有些API可能要求将API Key放在名为X-API-Key的自定义头中,而非Authorization,需要对照API文档确认。
  • 不要硬编码API Key:建议通过配置文件(如application.properties)读取API Key,避免代码泄露敏感信息。

内容的提问来源于stack exchange,提问作者rsharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 13:10:32