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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 00:47:01