如何用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
相关产品推荐
相关产品推荐

