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

将带证书的cURL请求转为Delphi Indy HTTP.Post时遇400错误求助

解决Delphi 11 Indy实现MTLS POST请求返回400 Bad Request的问题

原HTTP请求

POST /oauth/token HTTPS/1.1 
Host: psd2.mtls.sandbox.apis.op.fi
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=accounts&client_id=<APP_CLIENT_ID>&client_secret=<APP_CLIENT_SECRET>

对应cURL命令

curl -vk --key key.pem --cert client.crt https://psd2.mtls.sandbox.apis.op.fi/oauth/token -d 'grant_type=client_credentials&scope=accounts&client_id=<client_id>&client_secret=<client_secret>'

问题情况

已将key.pem和client.crt文件放在程序目录下,SSL协商显示成功,但始终收到HTTP/1.0 400 Bad Request响应。尝试过直接拼接POST数据、用TStringList传递等多种方式均无效,现有Delphi代码如下:

现有Delphi代码

var
  url,data : string;
  tiedot : TStringList;
  PostData: TStringStream;
  Result,sWhichFail, sVer: String;
begin
  tiedot := TStringList.Create();
  try
    url := 'https://psd2.mtls.sandbox.apis.op.fi/oauth/token';

    {not working way either:
    tiedot.Add('grant_type=client_credentials');
    tiedot.Add('scope=accounts');
    tiedot.Add('client_id=6G..03');
    tiedot.Add('client_secret=7P..W7');
    }
    data := 'grant_type=client_credentials&scope=accounts'+ 
      '&client_id=6G..03&client_secret=7P..W7';
    try
      PostData := TStringStream.Create(data, TEncoding.UTF8);
      ClientSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
      ClientSSLIOHandler.SSLOptions.Mode := sslmClient;
      ClientSSLIOHandler.SSLOptions.VerifyMode := [];
      ClientSSLIOHandler.SSLOptions.VerifyDepth := 0;
      ClientSSLIOHandler.SSLOptions.CertFile := 'client.cer';
      ClientSSLIOHandler.SSLOptions.KeyFile := 'key.pem';
      http.HandleRedirects  := True;
      http.IOHandler := ClientSSLIOHandler;
      http.AllowCookies := True;

      //none of these are working better
      http.HTTPOptions := [hoForceEncodeParams];
      //http.HTTPOptions := [];
      //http.HTTPOptions := http.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];

      http.Request.BasicAuthentication := false;
      http.Request.ContentType := 'application/x-www-form-urlencoded';
      http.Request.CharSet := 'UTF-8';

      try
        result := http.post(url,PostData); //tiedot -not working either
        mLoki.Lines.Add(result);
      except
        on E: EIdOSSLCouldNotLoadSSLLibrary do begin
          sVer := OpenSSLVersion();
          sWhichFail := WhichFailedToLoad();//uses IdSSLOpenSSLHeaders
          mLoki.Lines.Add(sVer);
          mLoki.Lines.Add(sWhichFail);
          ShowMessage('Could not load OpenSSL');
        end;
        on E: EIdHTTPProtocolException do begin
          mLoki.Lines.Add('HTTP Failure');
          mLoki.Lines.Add(Format('- Response Code: %d', [http.ResponseCode]));
          mLoki.Lines.Add(Format('- Response Text: %s', [http.ResponseText]));
          mLoki.Lines.Add('- '+E.ErrorMessage);
          ShowMessage('HTTP Failure');
        end;
        on E: Exception do begin
          mLoki.Lines.Add(E.ClassName);
          mLoki.Lines.Add(E.Message);
          ShowMessage('Unknown Error');
        end;
      end;
    finally
      PostData.Free;
    end;
  finally
    tiedot.Free;
  end;

错误日志

tilatieto: Resolving hostname psd2.mtls.sandbox.apis.op.fi.
tilatieto: Connecting to 18.159.93.42.
tilainfo: SSL status: "before/connect initialization"
tilainfo: SSL status: "before/connect initialization"
tilainfo: SSL status: "SSLv2/v3 write client hello A"
tilainfo: SSL status: "SSLv3 read server hello A"
tilainfo: SSL status: "SSLv3 read server certificate A"
tilainfo: SSL status: "SSLv3 read server key exchange A"
tilainfo: SSL status: "SSLv3 read server certificate request A"
tilainfo: SSL status: "SSLv3 read server done A"
tilainfo: SSL status: "SSLv3 write client certificate A"
tilainfo: SSL status: "SSLv3 write client key exchange A"
tilainfo: SSL status: "SSLv3 write certificate verify A"
tilainfo: SSL status: "SSLv3 write change cipher spec A"
tilainfo: SSL status: "SSLv3 write finished A"
tilainfo: SSL status: "SSLv3 flush data"
tilainfo: SSL status: "SSLv3 read finished A"
tilainfo: SSL status: "SSL negotiation finished successfully"
tilainfo: SSL status: "SSL negotiation finished successfully"
tilainfo: Cipher: name = ECDHE-RSA-AES128-GCM-SHA256; description = ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD
; bits = 128; version = TLSv1/SSLv3; 
tilainfo: SSL status: "SSL negotiation finished successfully"
tilainfo: SSL status: "SSL negotiation finished successfully"
tilatieto: Disconnected.
HTTP Failure
- Response Code: 400
- Response Text: HTTP/1.0 400 Bad Request
- {"message":"Bad Request"}

解决方案及修正代码

问题排查点

  1. 证书文件名不匹配:代码中指定的证书文件是client.cer,但实际文件是client.crt,后缀不一致会导致证书加载异常,虽然SSL协商日志显示成功,但服务端可能未正确识别客户端证书。
  2. SSL版本兼容性:旧版本TLS(v1.0、v1.1)可能不被服务端支持,限制为TLSv1.2即可。
  3. POST数据传递方式:使用TStringList传递参数时,Indy会自动处理URL编码和Content-Type设置,比手动拼接字符串更可靠。
  4. 请求头检查:显式指定Host头,避免Indy自动提取时出现异常。

修正后的代码

var
  url: string;
  tiedot: TStringList;
  Result, sWhichFail, sVer: String;
begin
  tiedot := TStringList.Create();
  try
    url := 'https://psd2.mtls.sandbox.apis.op.fi/oauth/token';

    // 使用TStringList传递参数,Indy自动处理编码
    tiedot.Add('grant_type=client_credentials');
    tiedot.Add('scope=accounts');
    tiedot.Add('client_id=6G..03'); // 替换为实际client_id
    tiedot.Add('client_secret=7P..W7'); // 替换为实际client_secret

    // 配置SSL参数
    ClientSSLIOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; // 仅启用TLSv1.2
    ClientSSLIOHandler.SSLOptions.Mode := sslmClient;
    ClientSSLIOHandler.SSLOptions.VerifyMode := [];
    ClientSSLIOHandler.SSLOptions.VerifyDepth := 0;
    ClientSSLIOHandler.SSLOptions.CertFile := 'client.crt'; // 修正证书文件名
    ClientSSLIOHandler.SSLOptions.KeyFile := 'key.pem';
    // 如果私钥有密码,添加以下行:
    // ClientSSLIOHandler.SSLOptions.Password := 'your_key_password';

    // 配置HTTP客户端
    http.HandleRedirects := True;
    http.IOHandler := ClientSSLIOHandler;
    http.AllowCookies := True;
    http.HTTPOptions := []; // 禁用hoForceEncodeParams,TStringList已自动处理编码
    http.Request.BasicAuthentication := False;
    http.Request.ContentType := 'application/x-www-form-urlencoded';
    http.Request.CharSet := 'UTF-8';
    http.Request.Host := 'psd2.mtls.sandbox.apis.op.fi'; // 显式指定Host头

    try
      Result := http.Post(url, tiedot);
      mLoki.Lines.Add(Result);
    except
      on E: EIdOSSLCouldNotLoadSSLLibrary do begin
        sVer := OpenSSLVersion();
        sWhichFail := WhichFailedToLoad();
        mLoki.Lines.Add(sVer);
        mLoki.Lines.Add(sWhichFail);
        ShowMessage('Could not load OpenSSL');
      end;
      on E: EIdHTTPProtocolException do begin
        mLoki.Lines.Add('HTTP Failure');
        mLoki.Lines.Add(Format('- Response Code: %d', [http.ResponseCode]));
        mLoki.Lines.Add(Format('- Response Text: %s', [http.ResponseText]));
        mLoki.Lines.Add('- ' + E.ErrorMessage);
        ShowMessage('HTTP Failure');
      end;
      on E: Exception do begin
        mLoki.Lines.Add(E.ClassName);
        mLoki.Lines.Add(E.Message);
        ShowMessage('Unknown Error');
      end;
    end;
  finally
    tiedot.Free;
  end;
end;

额外建议

  • 启用Indy调试日志,查看完整请求头和发送的POST数据,确认与cURL请求的一致性。
  • 检查私钥文件格式:确保key.pem是未加密的PEM格式,若为PKCS#12格式,需改用SSLOptions.PKCS12File指定证书文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:14:52