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

调用需认证的Jenkins API遇SSO拦截,如何提取构建结果?

解决Jenkins API调用遇到SSO表单跳转的问题

看起来你碰到的是Jenkins启用了**SAML单点登录(SSO)**导致的问题——你的Basic Auth请求被拦截,重定向到了SSO的自动提交表单页面,而不是返回预期的API数据。下面给你几个可行的解决思路,按推荐程度排序:

1. 配置Jenkins让API请求绕过SSO(最推荐)

这是最简单且可靠的方案,只要你有权限修改Jenkins的全局安全配置:

  • 登录Jenkins后台,进入 Manage Jenkins > Configure Global Security
  • 找到SAML 2.0插件的配置区域(不同插件可能位置略有不同,比如有些在"Security Realm"下的SAML配置里)
  • 找到**"Excluded paths"**或类似的设置项,添加API相关的路径规则,比如:
    • /api/*:所有API请求都绕过SSO
    • /job/*/api/*:仅允许任务相关的API请求绕过
  • 保存配置后,再用你原来的Java代码调用API,应该就能直接返回JSON数据了,不需要修改代码。

2. 模拟SAML认证流程(适合无法修改Jenkins配置的情况)

如果不能修改Jenkins配置,你需要让HttpClient完整模拟浏览器的SAML认证流程,步骤大概是这样:

  • 第一步:发送初始GET请求,接收返回的SSO表单,解析出RelayState参数和表单提交的URL(也就是action="/SSO.saml2")
  • 第二步:构造POST请求提交这个表单,带上RelayState参数,处理重定向到身份提供商(IDP)的登录页面
  • 第三步:解析IDP的登录表单,构造POST请求提交你的Jenkins用户名和API密钥(注意:这里需要匹配IDP的表单字段名,比如username/password或者其他自定义字段)
  • 第四步:接收IDP返回的SAML断言,再提交给Jenkins的SSO端点,获取认证后的Session Cookie
  • 第五步:带着这个Session Cookie去请求目标API URL

下面是一个简化的代码示例(仅展示核心流程,需要根据你的IDP实际情况调整):

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

public class JenkinsScraperWithSSO {
    public String scrapeWithSso(String urlString, String username, String password) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpClientContext context = HttpClientContext.create();

        // 1. 获取初始SSO表单
        HttpGet initialGet = new HttpGet(urlString);
        HttpResponse initialResponse = httpClient.execute(initialGet, context);
        String formContent = EntityUtils.toString(initialResponse.getEntity());
        
        // 解析RelayState和表单action(实际建议用Jsoup等HTML解析库,这里用简单字符串匹配示例)
        String relayState = extractRelayState(formContent);
        String ssoAction = "/SSO.saml2";
        URI ssoUri = URI.create(urlString).resolve(ssoAction);

        // 2. 提交SSO表单
        HttpPost ssoPost = new HttpPost(ssoUri);
        List<NameValuePair> ssoParams = new ArrayList<>();
        ssoParams.add(new BasicNameValuePair("RelayState", relayState));
        ssoPost.setEntity(new UrlEncodedFormEntity(ssoParams));
        HttpResponse ssoResponse = httpClient.execute(ssoPost, context);
        EntityUtils.consume(ssoResponse.getEntity()); // 自动处理重定向

        // 3. 提交IDP登录表单(替换成你的IDP登录URL和字段名)
        HttpPost idpLoginPost = new HttpPost("https://your-idp-login-url");
        List<NameValuePair> loginParams = new ArrayList<>();
        loginParams.add(new BasicNameValuePair("username", username));
        loginParams.add(new BasicNameValuePair("password", password));
        idpLoginPost.setEntity(new UrlEncodedFormEntity(loginParams));
        HttpResponse idpResponse = httpClient.execute(idpLoginPost, context);
        EntityUtils.consume(idpResponse.getEntity()); // 处理重定向回Jenkins

        // 4. 再次请求目标API,此时已携带认证Cookie
        HttpGet apiGet = new HttpGet(urlString);
        HttpResponse apiResponse = httpClient.execute(apiGet, context);
        return EntityUtils.toString(apiResponse.getEntity());
    }

    // 简单提取RelayState的方法,实际建议用Jsoup解析HTML
    private String extractRelayState(String html) {
        int start = html.indexOf("name=\"RelayState\" value=\"") + 25;
        int end = html.indexOf("\"", start);
        return html.substring(start, end);
    }
}

注意:这个方案需要依赖你的IDP具体实现,字段名、跳转逻辑可能都不一样,调试起来比较麻烦,优先推荐第一种方案。

3. 尝试使用Jenkins Crumb + Session认证

有些SSO配置允许Session认证绕过,你可以先通过Basic Auth获取Crumb和Session Cookie,再用Cookie请求API:

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URI;

public class JenkinsScraperWithCrumb {
    public String scrapeWithCrumb(String urlString, String username, String password) throws IOException {
        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
        
        CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultCredentialsProvider(credsProvider)
                .build();
        HttpClientContext context = HttpClientContext.create();

        // 获取Crumb
        HttpGet crumbGet = new HttpGet(URI.create(urlString).resolve("/crumbIssuer/api/json"));
        HttpResponse crumbResponse = httpClient.execute(crumbGet, context);
        String crumbJson = EntityUtils.toString(crumbResponse.getEntity());
        // 解析Crumb(可以用Jackson等JSON库,这里简化处理)
        String crumb = crumbJson.split("\"crumb\":\"")[1].split("\"")[0];
        String crumbRequestField = crumbJson.split("\"crumbRequestField\":\"")[1].split("\"")[0];

        // 请求API,带上Crumb和Session Cookie
        HttpGet apiGet = new HttpGet(urlString);
        apiGet.addHeader(crumbRequestField, crumb);
        HttpResponse apiResponse = httpClient.execute(apiGet, context);
        return EntityUtils.toString(apiResponse.getEntity());
    }
}

不过这个方案是否生效取决于你的SSO配置,有些情况下还是会被重定向到SSO页面。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:11:09