调用需认证的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
相关产品推荐
相关产品推荐

