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
相关产品推荐
相关产品推荐

