将带证书的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"}
解决方案及修正代码
问题排查点
- 证书文件名不匹配:代码中指定的证书文件是
client.cer,但实际文件是client.crt,后缀不一致会导致证书加载异常,虽然SSL协商日志显示成功,但服务端可能未正确识别客户端证书。 - SSL版本兼容性:旧版本TLS(v1.0、v1.1)可能不被服务端支持,限制为TLSv1.2即可。
- POST数据传递方式:使用
TStringList传递参数时,Indy会自动处理URL编码和Content-Type设置,比手动拼接字符串更可靠。 - 请求头检查:显式指定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
相关产品推荐
相关产品推荐

