Spring RestTemplate传自定义Header报错:不存在OTDSTicket
Spring RestTemplate调用第三方接口携带OTDSTicket返回"no OTDS ticket"问题排查
我需要更新用户集合,调用第三方接口获取用户列表时需在请求头携带OTDSTicket认证票据。Postman测试时能正常获取票据并调用用户接口,但通过Spring的GetUsersService调用时,日志显示请求头已包含OTDSTicket,却收到返回错误“no OTDS ticket”。使用Spring版本2.7.13。
相关代码
Controller代码
package com.cybg.digitilisation.controller; import com.cybg.digitilisation.entity.AuthUser; import com.cybg.digitilisation.entity.Users; import com.cybg.digitilisation.service.GetAuthTicketService; import com.cybg.digitilisation.service.GetUsersService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController @Configuration public class DigitizationController { private final GetAuthTicketService getAuthTicketService; private final GetUsersService getUsersService; @Autowired public DigitizationController(GetAuthTicketService getAuthTicketService, GetUsersService getUsersService) { this.getAuthTicketService = getAuthTicketService; this.getUsersService = getUsersService; } @GetMapping(value = "/getTicket", produces = { "application/json" }) public ResponseEntity<String> geTicket(@RequestBody AuthUser user) { String ticket = getAuthTicketService.getTicket(user.getUsername(), user.getPassword()); return ResponseEntity.ok(ticket); } @GetMapping(value = "/getUsers", produces = { "application/json" }) public ResponseEntity<Users> getUsers(@RequestBody AuthUser user) { return getUsersService.getUsers(user.getUsername(), user.getPassword()); } }
GetAuthTicketService代码
package com.cybg.digitilisation.service; import com.cybg.digitilisation.entity.AuthTicket; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.Objects; @Service public class GetAuthTicketService { private static final Logger LOGGER = LogManager.getLogger(); private final RestTemplate restTemplate; @Value("${endpoint.auth}") private String url; @Autowired public GetAuthTicketService(RestTemplate restTemplate) { super(); this.restTemplate = restTemplate; } public String getTicket(String username, String passwd) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); String request = "{ \"userName\": \"" + username + "\", \"password\": \"" + passwd + "\" }"; LOGGER.info(request); HttpEntity<String> entity = new HttpEntity<>(request, headers); LOGGER.info(entity); AuthTicket ticket = restTemplate.postForObject(url, entity, AuthTicket.class); if (Objects.nonNull(ticket)) { LOGGER.info("Ticket is {}", ticket.getTicket()); return ticket.getTicket(); } return null; } }
GetUsersService代码
package com.cybg.digitilisation.service; import com.cybg.digitilisation.entity.Users; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class GetUsersService { private static final Logger LOGGER = LogManager.getLogger(); @Value("${endpoint.users}") private String url; private final RestTemplate restTemplate; private final GetAuthTicketService authTicketService; @Autowired public GetUsersService(RestTemplate restTemplate, GetAuthTicketService authTicketService) { super(); this.restTemplate = restTemplate; this.authTicketService = authTicketService; } public ResponseEntity<Users> getUsers(String username, String passwd) { String ticket = authTicketService.getTicket(username, passwd); LOGGER.info("Ticket is: {}", ticket); HttpHeaders headers = buildHeaders(ticket); HttpEntity<String> entity = new HttpEntity<>(headers); LOGGER.info(entity); return restTemplate.getForEntity(url, Users.class, entity); } private HttpHeaders buildHeaders(String ticket) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("OTDSTicket", ticket); return headers; } }
错误响应
{ "timestamp": "2023-06-28T07:06:09.076+00:00", "status": 500, "error": "Internal Server Error", "trace": "org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 : \"{\"status\":1009,\"error\":\"no OTDS ticket\",\"errorDetails\":null}\"\n\tat org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:105)\n\tat org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:168)\n\tat org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:122)\n\tat org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)\n\tat org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:825)\n\tat org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:783)\n\tat org.springframework.web.client.RestTemplate.execute(RestTemplate.java:717)\n\tat org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:367)\n\tat com.cybg.digitilisation.service.GetUsersService.getUsers(GetUsersService.java:43)\n\tat com.cybg.digitilisation.controller.DigitizationController.getUsers(DigitizationController.java:35)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:529)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:623)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\n", "message": "401 : \"{\"status\":1009,\"error\":\"no OTDS ticket\",\"errorDetails\":null}\"", "path": "/getUsers" }
日志信息
<[Content-Type:"application/json", OTDSTicket:"long_string_representing_token"]>
问题原因
核心问题在于**RestTemplate.getForEntity方法的参数使用错误**:你传入的HttpEntity被当作了URL占位符的参数,而非请求头载体。
Spring 2.7.x中getForEntity的重载方法里,第三个参数是用于替换URL中占位符的变量(比如/users/{id}里的id),它不会解析HttpEntity中的请求头信息,导致实际发送给第三方接口的请求并没有带上OTDSTicket,所以触发了“no OTDS ticket”错误。
解决方案
将getForEntity替换为exchange方法,该方法支持传入HttpEntity来携带请求头:
修改GetUsersService中的getUsers方法:
public ResponseEntity<Users> getUsers(String username, String passwd) { String ticket = authTicketService.getTicket(username, passwd); LOGGER.info("Ticket is: {}", ticket); HttpHeaders headers = buildHeaders(ticket); HttpEntity<String> entity = new HttpEntity<>(headers); LOGGER.info(entity); // 替换getForEntity为exchange return restTemplate.exchange( url, HttpMethod.GET, entity, Users.class ); }
额外检查点
- 对比日志中打印的ticket与Postman中使用的ticket是否完全一致,排除ticket获取异常的可能
- 确认第三方接口对请求头的大小写是否敏感(比如是否要求
otdsticket而非OTDSTicket),不过从日志看当前头格式是正确的,此可能性较低
内容的提问来源于stack exchange,提问作者AceKokuren
相关产品推荐
相关产品推荐

