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

THTTPRIO未触发OnNeedClientCertificate事件,SOAP证书调用弹窗问题

解决Delphi THTTPRIO调用SOAP服务时自动选择指定证书的问题

问题分析

你绑定了OnNeedClientCertificate事件但未触发,通常有两个核心原因:

  1. 事件绑定时机错误:THTTPRIO的HTTPWebNode.HTTP对象可能在调用GetrnoSoap后才完成初始化,提前绑定会导致事件关联失效。
  2. 服务器证书要求类型:部分服务器要求客户端在握手阶段预先提供证书,而非等待服务器触发证书请求,此时OnNeedClientCertificate事件不会被调用。

解决方案

方案1:修正事件绑定时机

将事件绑定移到GetrnoSoap调用之后,确保HTTP对象已创建完成:

var RIO := THTTPRIO.Create(nil);
try
  var svc := wsETurizemPorocanje.GetrnoSoap(false, '', RIO);
  // 此时HTTPWebNode的HTTP对象已初始化,再绑定事件
  RIO.HTTPWebNode.HTTP.OnNeedClientCertificate := DoOnClientCertificateNeeded;
  RIO.HTTPWebNode.GetHTTPReqResp.PreemptiveAuthentication := true;

  data := TXmlData.Create;
  try
    data.LoadFromXml(xml.XML);
    var errors := svc.oddajPorocilo('UserName', 'Password', data , 1);
  finally
    data.Free;
  end;
finally
  RIO.Free;
end;

方案2:手动指定证书(更可靠)

直接从Windows证书存储中查找目标证书,添加到HTTP客户端的证书列表并设置自动选择,跳过事件触发逻辑:

1. 实现证书查找函数

添加Winapi.WinCrypt单元,编写根据序列号查找证书的函数(注意证书序列号在Windows存储中是字节倒序存储的):

uses Winapi.WinCrypt, System.SysUtils;

function ReverseBytes(var Bytes: TBytes): TBytes;
var
  I, J: Integer;
  Temp: Byte;
begin
  Result := Bytes;
  J := Length(Result) - 1;
  for I := 0 to (Length(Result) div 2) - 1 do
  begin
    Temp := Result[I];
    Result[I] := Result[J];
    Result[J] := Temp;
    Dec(J);
  end;
end;

function FindCertificateBySerial(const SerialNumber: string): TCertificate;
var
  CertStore: HCERTSTORE;
  Cert: PCCERT_CONTEXT;
  CleanSerial: string;
  SerialBytes: TBytes;
  I: Integer;
begin
  Result := nil;
  CleanSerial := StringReplace(SerialNumber, ' ', '', [rfReplaceAll]);
  if Length(CleanSerial) mod 2 <> 0 then Exit;

  // 将十六进制序列号转为字节数组
  SetLength(SerialBytes, Length(CleanSerial) div 2);
  for I := 0 to Length(CleanSerial) div 2 - 1 do
    SerialBytes[I] := StrToInt('$' + Copy(CleanSerial, I*2+1, 2));
  // 反转字节(适配Windows存储的序列号格式)
  SerialBytes := ReverseBytes(SerialBytes);

  // 打开当前用户的"个人"证书存储;若证书在本地计算机存储,改用CERT_SYSTEM_STORE_LOCAL_MACHINE
  CertStore := CertOpenSystemStore(0, PChar('MY'));
  if CertStore = nil then Exit;
  try
    // 遍历存储中的所有证书
    Cert := CertFindCertificateInStore(CertStore, X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
      0, CERT_FIND_ANY, nil, nil);
    while Cert <> nil do
    begin
      // 匹配序列号
      if (Cert^.pCertInfo^.SerialNumber.cbData = Length(SerialBytes)) and
         CompareMem(Cert^.pCertInfo^.SerialNumber.pbData, @SerialBytes[0], Length(SerialBytes)) then
      begin
        // 检查证书有效期
        if (Cert^.pCertInfo^.NotBefore.dwLowDateTime <= GetTickCount64) and
           (Cert^.pCertInfo^.NotAfter.dwLowDateTime >= GetTickCount64) then
        begin
          Result := TCertificate.Create(Cert);
          Break;
        end
        else
          raise Exception.Create('目标证书已过期');
      end;
      Cert := CertFindCertificateInStore(CertStore, X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
        0, CERT_FIND_ANY, nil, Cert);
    end;
  finally
    CertCloseStore(CertStore, 0);
  end;
end;

2. 在SOAP调用中使用证书

var
  RIO: THTTPRIO;
  svc: rnoSoap;
  TargetCert: TCertificate;
  data: TXmlData;
  errors: { 替换为实际返回类型 };
begin
  RIO := THTTPRIO.Create(nil);
  TargetCert := nil;
  data := nil;
  try
    // 查找指定序列号的证书
    TargetCert := FindCertificateBySerial(AppSettings.Server.Hostel.ETurizemSettings.CertSerial);
    if not Assigned(TargetCert) then
      raise Exception.Create('未找到指定序列号的证书');

    // 将证书添加到HTTP客户端并设置自动选择
    RIO.HTTPWebNode.HTTP.ClientCertificates.Add(TargetCert);
    RIO.HTTPWebNode.HTTP.ClientCertificateIndex := 0;

    RIO.HTTPWebNode.GetHTTPReqResp.PreemptiveAuthentication := true;
    svc := wsETurizemPorocanje.GetrnoSoap(false, '', RIO);

    data := TXmlData.Create;
    data.LoadFromXml(xml.XML);
    errors := svc.oddajPorocilo('UserName', 'Password', data , 1);
  finally
    data.Free;
    TargetCert.Free;
    RIO.Free;
  end;
end;

额外注意事项

  • 证书存储权限:若证书在「本地计算机」存储,程序需以管理员权限运行才能访问。
  • 序列号格式:确保传入的序列号是纯十六进制字符串,无多余空格或分隔符。
  • 有效期验证:查找函数中已包含有效期检查,若证书过期会直接抛出异常。

内容的提问来源于stack exchange,提问作者Marko Milutinović

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 16:40:58