You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Delphi中向Jira传递RSA密钥并解决OAuth登录令牌获取失败问题

我之前帮不少开发者搞定过Delphi对接Jira OAuth的问题,你碰到的RSA私钥编码和签名问题确实是个常见卡点,下面我给你一步步拆解实现方法,保证能跑通:

核心原理先搞懂

Jira OAuth采用的是OAuth 1.0a的RSA-SHA1签名机制,核心逻辑是:

  1. 按照规则构造「签名基串」
  2. 用你的RSA私钥对这个基串进行SHA1签名
  3. 将签名结果Base64编码后,放到HTTP请求的Authorization头中
Delphi具体实现步骤

1. 加载RSA私钥(PEM格式)

Jira要求的私钥一般是PEM格式,我们可以用Delphi自带的Indy库TIdRSA组件来加载,注意要先去掉PEM的头尾标记和换行符:

function LoadRSAPrivateKey(const PrivateKeyPath: string): TIdRSA;
var
  SL: TStringList;
  PEMContent: string;
begin
  Result := TIdRSA.Create;
  SL := TStringList.Create;
  try
    SL.LoadFromFile(PrivateKeyPath);
    // 清理PEM格式的头尾和换行
    PEMContent := StringReplace(SL.Text, '-----BEGIN RSA PRIVATE KEY-----', '', [rfReplaceAll]);
    PEMContent := StringReplace(PEMContent, '-----END RSA PRIVATE KEY-----', '', [rfReplaceAll]);
    PEMContent := StringReplace(PEMContent, sLineBreak, '', [rfReplaceAll]);
    // 解码Base64后加载私钥
    Result.LoadPrivateKeyFromBlob(TIdDecoderMIME.DecodeBytes(PEMContent));
  finally
    SL.Free;
  end;
end;

2. 生成OAuth签名基串

签名基串是OAuth签名的核心,必须严格按照「HTTP方法&编码后的请求URL&编码后的参数串」格式构造,还要对参数按字典序排序:

function GenerateSignatureBaseString(const HTTPMethod, RequestURL: string;
  Params: TStringList): string;
var
  EncodedParams: string;
  I: Integer;
  ParamPairs: TStringList;
begin
  ParamPairs := TStringList.Create;
  try
    // 对每个参数名和值做URL编码,然后排序
    for I := 0 to Params.Count - 1 do
    begin
      ParamPairs.Add(TIdURI.URLEncode(Params.Names[I]) + '=' + TIdURI.URLEncode(Params.ValueFromIndex[I]));
    end;
    ParamPairs.Sort;
    // 用&连接排序后的参数
    EncodedParams := ParamPairs.CommaText.Replace(',', '&');
    // 构造最终的签名基串,每个部分都要URL编码
    Result := TIdURI.URLEncode(HTTPMethod) + '&' +
              TIdURI.URLEncode(RequestURL) + '&' +
              TIdURI.URLEncode(EncodedParams);
  finally
    ParamPairs.Free;
  end;
end;

3. 用RSA私钥签名并编码

用私钥对签名基串做SHA1签名,然后将签名结果Base64编码:

function GenerateRSASignature(const SignatureBaseString: string; RSA: TIdRSA): string;
var
  SignatureBytes: TBytes;
  HashBytes: TBytes;
begin
  // 先对签名基串做SHA1哈希
  HashBytes := THashSHA1.GetHashBytes(SignatureBaseString);
  // 用RSA私钥签名(Jira要求PKCS#1 v1.5填充,TIdRSA默认支持)
  SignatureBytes := RSA.SignBytes(HashBytes, SHA1);
  // 签名结果Base64编码
  Result := TIdEncoderMIME.EncodeBytes(SignatureBytes);
end;

4. 构造Authorization请求头

把所有OAuth参数和签名组合成符合规范的请求头,注意每个参数值都要URL编码:

function BuildAuthorizationHeader(const ConsumerKey, Nonce, Timestamp, Signature: string): string;
begin
  Result := Format('OAuth oauth_consumer_key="%s", oauth_nonce="%s", oauth_signature="%s", oauth_signature_method="RSA-SHA1", oauth_timestamp="%s", oauth_version="1.0"',
    [TIdURI.URLEncode(ConsumerKey), TIdURI.URLEncode(Nonce), TIdURI.URLEncode(Signature), Timestamp]);
end;
完整流程示例(获取请求令牌)

把上面的步骤串起来,实现Jira请求令牌的获取:

procedure GetJiraRequestToken;
var
  RSA: TIdRSA;
  SignatureBase: string;
  Signature: string;
  AuthHeader: string;
  Params: TStringList;
  HTTP: TIdHTTP;
  Response: string;
  Nonce: string;
  Timestamp: string;
begin
  RSA := LoadRSAPrivateKey('C:\your\private\key\path\privatekey.pem');
  try
    // 生成唯一的Nonce和Unix时间戳
    Nonce := THashMD5.GetHashString(IntToStr(Random(MaxInt)) + Now.ToString);
    Timestamp := IntToStr(Trunc(Now - EncodeDate(1970, 1, 1)) * 86400 + Trunc(Time * 86400));

    // 构造OAuth参数列表
    Params := TStringList.Create;
    try
      Params.Add('oauth_consumer_key=YOUR_JIRA_CONSUMER_KEY');
      Params.Add('oauth_nonce=' + Nonce);
      Params.Add('oauth_signature_method=RSA-SHA1');
      Params.Add('oauth_timestamp=' + Timestamp);
      Params.Add('oauth_version=1.0');

      // 生成签名基串和签名
      SignatureBase := GenerateSignatureBaseString('POST', 'https://your-jira-instance.atlassian.net/oauth/request-token', Params);
      Signature := GenerateRSASignature(SignatureBase, RSA);
      // 构造Authorization头
      AuthHeader := BuildAuthorizationHeader('YOUR_JIRA_CONSUMER_KEY', Nonce, Timestamp, Signature);

      // 发送HTTP请求
      HTTP := TIdHTTP.Create;
      try
        HTTP.Request.CustomHeaders.Add('Authorization: ' + AuthHeader);
        // Jira请求令牌接口需要POST空内容
        Response := HTTP.Post('https://your-jira-instance.atlassian.net/oauth/request-token', TStream(nil));
        // 解析响应得到oauth_token和oauth_token_secret
        ShowMessage('Request Token Response: ' + Response);
      finally
        HTTP.Free;
      end;
    finally
      Params.Free;
    end;
  finally
    RSA.Free;
  end;
end;
关键注意事项
  • 确保你在Jira的应用链接设置中,已经上传了对应私钥的公钥
  • 使用较新版本的Indy库,避免旧版本RSA组件的兼容性问题
  • Nonce必须保证每次请求唯一,时间戳是标准Unix时间戳(秒数)
  • 所有URL编码要严格遵循RFC 3986标准,TIdURI.URLEncode符合要求

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

火山引擎 最新活动