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

如何用HttpRequestInterceptor实现Apache HttpClient 5.2预基本认证?

Apache HttpClient 5.2 预基本认证实现方案(基于HttpRequestInterceptor)

问题描述

  • 是否可以参考HttpClient 4的实现思路,用HttpRequestInterceptor实现HttpClient 5.2的预基本认证?
  • 我们既直接使用Apache HttpClient,也将其作为RestTemplate的底层HTTP客户端。HttpClient 4中曾通过HttpRequestInterceptor实现预认证,但因AuthScheme类结构变更,旧代码无法复用。
  • 尝试多种方法(如下方代码)均未达预期:首次请求会收到服务器401响应,客户端携带Basic认证重试后才成功,需要实现首次请求就携带Authorization头的效果。

原尝试代码:

@Component
public class HttpClientPreemptiveAuthInterceptor implements HttpRequestInterceptor {

    @Override
    public void process(HttpRequest httpRequest, EntityDetails entityDetails, HttpContext httpContext) throws IOException, HttpException {

        // Apparently, we have two options:
        // - Set HttpContext AuthCache with Basic auth for the target.
        // - Copy org.apache.hc.client5.http.ContextBuilder#preemptiveBasicAuth()
        // The above method is available for HttpClientContext creation. Not sure why it doesn't set AuthCache though,
        // but instead adds an entry to the AuthExchange map of the given http context.

        HttpClientContext httpClientContext = (HttpClientContext) httpContext;

        // get the target host from the http context
        RouteInfo routeInfo = httpClientContext.getHttpRoute();
        if (routeInfo != null) {
            HttpHost targetHost = routeInfo.getTargetHost();
            AuthExchange authExchange = httpClientContext.getAuthExchange(targetHost);

            if (authExchange.getAuthScheme() == null) {
                CredentialsProvider credentialsProvider = httpClientContext.getCredentialsProvider();
                Credentials credentials = credentialsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), httpClientContext);
                if (credentials == null) {
                    throw new HttpException("No credentials provided for preemptive authentication");
                }
                BasicScheme authScheme = new BasicScheme();
                authScheme.initPreemptive(credentials);
                //authExchange.select(authScheme);
                //httpClientContext.setAuthExchange(targetHost, authExchange);
                DefaultSchemePortResolver schemePortResolver = DefaultSchemePortResolver.INSTANCE;
                httpClientContext.resetAuthExchange(RoutingSupport.normalize(targetHost, schemePortResolver), authScheme);
            }

        }


        /** Second approach
        // get the target host from the http context
        RouteInfo routeInfo = httpClientContext.getHttpRoute();
        if (routeInfo != null) {
            HttpHost targetHost = routeInfo.getTargetHost();
            // add Basic Auth for the target host (credentials for this host will be selected from the CredentialsProvider)
            httpClientContext.resetAuthExchange(targetHost, new BasicScheme());
        }
         **/

        // Third approach
        // Create AuthCache instance
        // final AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local auth cache
        // authCache.put(targetHost, new BasicScheme());
        // httpClientContext.setAuthCache(new BasicAuthCache());
    }
}

解决方案

正确的预认证拦截器实现

直接在拦截器中生成并添加Authorization头,确保首次请求就携带认证信息:

@Component
public class HttpClientPreemptiveAuthInterceptor implements HttpRequestInterceptor {

    @Override
    public void process(HttpRequest request, EntityDetails entityDetails, HttpContext context) throws IOException, HttpException {
        HttpClientContext clientContext = HttpClientContext.adapt(context);
        RouteInfo route = clientContext.getHttpRoute();
        if (route == null) {
            return;
        }
        HttpHost targetHost = route.getTargetHost();
        CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
        if (credsProvider == null) {
            return;
        }
        // 从凭证提供者中获取目标主机对应的认证凭证
        Credentials credentials = credsProvider.getCredentials(
                new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                clientContext
        );
        if (credentials == null) {
            throw new HttpException("无可用的预认证凭证");
        }
        // 初始化BasicScheme并生成认证头
        BasicScheme basicScheme = new BasicScheme();
        basicScheme.initPreemptive(credentials);
        Header authHeader = basicScheme.authenticate(credentials, request, clientContext);
        // 将认证头添加到请求中
        request.addHeader(authHeader);
    }
}

关键说明

  • 直接生成认证头:绕过HttpClient默认的"先401再重试"逻辑,主动构建并添加Authorization头,确保首次请求就携带认证信息。
  • 凭证复用:从CredentialsProvider获取凭证,保持与全局HttpClient配置的一致性,无需硬编码账号密码。

HttpClient配置示例

将拦截器和凭证提供者绑定到HttpClient实例:

@Bean
public CloseableHttpClient httpClient(HttpClientPreemptiveAuthInterceptor authInterceptor) {
    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    // 添加目标主机的认证凭证
    credentialsProvider.setCredentials(
            new AuthScope("目标主机域名", 端口号),
            new UsernamePasswordCredentials("用户名", "密码".toCharArray())
    );

    return HttpClientBuilder.create()
            .setDefaultCredentialsProvider(credentialsProvider)
            .addRequestInterceptorFirst(authInterceptor) // 确保拦截器优先执行
            .build();
}

RestTemplate配置(基于HttpClient)

将配置好的HttpClient作为RestTemplate的底层客户端:

@Bean
public RestTemplate restTemplate(CloseableHttpClient httpClient) {
    HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
    requestFactory.setHttpClient(httpClient);
    return new RestTemplate(requestFactory);
}

原代码失败原因

  • 依赖AuthExchange的resetAuthExchange或select方法:这些操作仅告知HttpClient后续重试时使用认证方案,不会主动在首次请求添加头。
  • AuthCache方式:5.x中需配合AuthCacheInterceptor,但默认逻辑仍需等待401响应后才会触发缓存的认证方案,无法实现预认证。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 09:47:38