Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit e828aad

Browse files
mheathghillert
authored andcommitted
Support providing HTTP credentials from an external process
This enables use of something like `cf oauth-token` to authenticate the Shell to a DF server using Oauth2 tokens. See https://www.pivotaltracker.com/story/show/138282513
1 parent 88a422e commit e828aad

File tree

11 files changed

+334
-91
lines changed

11 files changed

+334
-91
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.cloud.dataflow.rest.util;
17+
18+
import java.net.URI;
19+
20+
import org.apache.http.HttpHost;
21+
import org.apache.http.HttpRequestInterceptor;
22+
import org.apache.http.auth.AuthScope;
23+
import org.apache.http.auth.UsernamePasswordCredentials;
24+
import org.apache.http.conn.ssl.NoopHostnameVerifier;
25+
import org.apache.http.impl.client.BasicCredentialsProvider;
26+
import org.apache.http.impl.client.CloseableHttpClient;
27+
import org.apache.http.impl.client.HttpClientBuilder;
28+
29+
import org.springframework.http.client.ClientHttpRequestFactory;
30+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
31+
32+
/**
33+
* Utility for configuring a {@link CloseableHttpClient}. This class allows for
34+
* chained method invocation. If both basic auth credentials and a target host
35+
* are provided, the HTTP client will aggressively send the credentials
36+
* without having to receive an HTTP 401 response first.
37+
*
38+
* <p>This class can also be used to configure the client used by
39+
* {@link org.springframework.web.client.RestTemplate} using
40+
* {@link #buildClientHttpRequestFactory()}.
41+
*
42+
* @author Mike Heath
43+
*/
44+
public class HttpClientConfigurer {
45+
46+
private final HttpClientBuilder httpClientBuilder;
47+
48+
private boolean useBasicAuth;
49+
private HttpHost targetHost;
50+
51+
public static HttpClientConfigurer create() {
52+
return new HttpClientConfigurer();
53+
}
54+
55+
protected HttpClientConfigurer() {
56+
httpClientBuilder = HttpClientBuilder.create();
57+
}
58+
59+
public HttpClientConfigurer basicAuthCredentials(String username, String password) {
60+
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
61+
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
62+
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
63+
64+
useBasicAuth = true;
65+
66+
return this;
67+
}
68+
69+
/**
70+
* Sets the client's {@link javax.net.ssl.SSLContext} to use
71+
* {@link HttpUtils#buildCertificateIgnoringSslContext()}.
72+
*
73+
* @return a reference to {@code this} to enable chained method invocation
74+
*/
75+
public HttpClientConfigurer skipTlsCertificateVerification() {
76+
httpClientBuilder.setSSLContext(HttpUtils.buildCertificateIgnoringSslContext());
77+
httpClientBuilder.setSSLHostnameVerifier(new NoopHostnameVerifier());
78+
79+
return this;
80+
}
81+
82+
public HttpClientConfigurer skipTlsCertificateVerification(boolean skipTlsCertificateVerification) {
83+
if (skipTlsCertificateVerification) {
84+
skipTlsCertificateVerification();
85+
}
86+
87+
return this;
88+
}
89+
90+
public HttpClientConfigurer targetHost(URI targetHost) {
91+
this.targetHost = new HttpHost(targetHost.getHost(), targetHost.getPort(), targetHost.getScheme());
92+
93+
return this;
94+
}
95+
96+
public HttpClientConfigurer addInterceptor(HttpRequestInterceptor interceptor) {
97+
httpClientBuilder.addInterceptorLast(interceptor);
98+
99+
return this;
100+
}
101+
102+
public CloseableHttpClient buildHttpClient() {
103+
return httpClientBuilder.build();
104+
}
105+
106+
public ClientHttpRequestFactory buildClientHttpRequestFactory() {
107+
if (useBasicAuth && targetHost != null) {
108+
return new PreemptiveBasicAuthHttpComponentsClientHttpRequestFactory(buildHttpClient(), targetHost);
109+
} else {
110+
return new HttpComponentsClientHttpRequestFactory(buildHttpClient());
111+
}
112+
}
113+
114+
}

spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/util/HttpUtils.java

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,14 @@
1515
*/
1616
package org.springframework.cloud.dataflow.rest.util;
1717

18-
import java.net.URI;
1918
import java.security.KeyManagementException;
2019
import java.security.KeyStoreException;
2120
import java.security.NoSuchAlgorithmException;
22-
import java.security.cert.CertificateException;
23-
import java.security.cert.X509Certificate;
2421

2522
import javax.net.ssl.SSLContext;
2623

27-
import org.apache.http.HttpHost;
28-
import org.apache.http.auth.AuthScope;
29-
import org.apache.http.auth.UsernamePasswordCredentials;
3024
import org.apache.http.client.HttpClient;
31-
import org.apache.http.conn.ssl.NoopHostnameVerifier;
32-
import org.apache.http.impl.client.BasicCredentialsProvider;
33-
import org.apache.http.impl.client.CloseableHttpClient;
34-
import org.apache.http.impl.client.HttpClientBuilder;
3525
import org.apache.http.ssl.SSLContexts;
36-
import org.apache.http.ssl.TrustStrategy;
37-
38-
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
39-
import org.springframework.util.Assert;
40-
import org.springframework.util.StringUtils;
41-
import org.springframework.web.client.RestTemplate;
4226

4327
/**
4428
* Provides utilities for the Apache {@link HttpClient}, used to make REST calls
@@ -47,63 +31,16 @@
4731
*/
4832
public class HttpUtils {
4933

50-
/**
51-
* Ensures that the passed-in {@link RestTemplate} is using the Apache HTTP Client. If
52-
* the optional {@code
53-
* username} AND {@code password} are not empty, then a
54-
* {@link BasicCredentialsProvider} will be added to the {@link CloseableHttpClient}.
55-
* <p>
56-
* Furthermore, you can set the underlying {@link SSLContext} of the
57-
* {@link HttpClient} allowing you to accept self-signed certificates.
58-
*
59-
* @param restTemplate the rest template, must not be null
60-
* @param host the target host URI
61-
* @param username the username for authentication, can be null
62-
* @param password the password for authentication, can be null
63-
* @param skipSslValidation whether to skip ssl validation. Use with caution! If true
64-
* certificate warnings will be ignored.
65-
*/
66-
public static void prepareRestTemplate(RestTemplate restTemplate, URI host, String username, String password,
67-
boolean skipSslValidation) {
68-
69-
Assert.notNull(restTemplate, "The provided RestTemplate must not be null.");
70-
71-
final HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
72-
73-
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
74-
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
75-
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
76-
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
77-
}
78-
79-
if (skipSslValidation) {
80-
httpClientBuilder.setSSLContext(HttpUtils.buildCertificateIgnoringSslContext());
81-
httpClientBuilder.setSSLHostnameVerifier(new NoopHostnameVerifier());
82-
}
83-
84-
final CloseableHttpClient httpClient = httpClientBuilder.build();
85-
final HttpHost targetHost = new HttpHost(host.getHost(), host.getPort(), host.getScheme());
86-
87-
final HttpComponentsClientHttpRequestFactory requestFactory = new PreemptiveBasicAuthHttpComponentsClientHttpRequestFactory(
88-
httpClient, targetHost);
89-
restTemplate.setRequestFactory(requestFactory);
90-
}
91-
9234
/**
9335
* Will create a certificate-ignoring {@link SSLContext}. Please use with utmost
9436
* caution as it undermines security, but may be useful in certain testing or
9537
* development scenarios.
9638
*
97-
* @return The SSLContext
39+
* @return an SSLContext that will ignore peer certificates
9840
*/
9941
public static SSLContext buildCertificateIgnoringSslContext() {
10042
try {
101-
return SSLContexts.custom().loadTrustMaterial(new TrustStrategy() {
102-
@Override
103-
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
104-
return true;
105-
}
106-
}).build();
43+
return SSLContexts.custom().loadTrustMaterial((chain, authType) -> true).build();
10744
}
10845
catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
10946
throw new IllegalStateException(

spring-cloud-dataflow-rest-resource/src/main/java/org/springframework/cloud/dataflow/rest/util/PreemptiveBasicAuthHttpComponentsClientHttpRequestFactory.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
3131

3232
/**
33+
* {@link HttpComponentsClientHttpRequestFactory} extension that aggressively
34+
* sends HTTP basic authentication credentials without having to first
35+
* receive an HTTP 401 response from the server.
36+
*
3337
* @author Gunnar Hillert
38+
* @author Mike Heath
3439
*/
3540
public class PreemptiveBasicAuthHttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
3641

@@ -43,10 +48,6 @@ public PreemptiveBasicAuthHttpComponentsClientHttpRequestFactory(HttpClient http
4348

4449
@Override
4550
protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
46-
return createHttpContext();
47-
}
48-
49-
private HttpContext createHttpContext() {
5051
final AuthCache authCache = new BasicAuthCache();
5152

5253
final BasicScheme basicAuth = new BasicScheme();
@@ -56,4 +57,5 @@ private HttpContext createHttpContext() {
5657
localcontext.setAttribute(HttpClientContext.AUTH_CACHE, authCache);
5758
return localcontext;
5859
}
60+
5961
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.cloud.dataflow.rest.util;
17+
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
22+
import org.springframework.core.io.AbstractResource;
23+
24+
/**
25+
* {@link org.springframework.core.io.Resource} implementation to create an
26+
* operating system process process and capture its output.
27+
*
28+
* @author Mike Heath
29+
*/
30+
public class ProcessOutputResource extends AbstractResource {
31+
32+
private final ProcessBuilder processBuilder;
33+
34+
public ProcessOutputResource(String... command) {
35+
processBuilder = new ProcessBuilder(command);
36+
}
37+
38+
@Override
39+
public String getDescription() {
40+
return processBuilder.toString();
41+
}
42+
43+
@Override
44+
public InputStream getInputStream() throws IOException {
45+
return processBuilder.start().getInputStream();
46+
}
47+
48+
@Override
49+
public String toString() {
50+
return getDescription();
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.cloud.dataflow.rest.util;
17+
18+
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
20+
21+
import org.apache.http.HttpException;
22+
import org.apache.http.HttpHeaders;
23+
import org.apache.http.HttpRequest;
24+
import org.apache.http.HttpRequestInterceptor;
25+
import org.apache.http.protocol.HttpContext;
26+
27+
import org.springframework.core.io.Resource;
28+
import org.springframework.util.StreamUtils;
29+
30+
/**
31+
* {@link HttpRequestInterceptor} that adds an {@code Authorization} header
32+
* with a value obtained from a {@link Resource}.
33+
*
34+
* @author Mike Heath
35+
*/
36+
public class ResourceBasedAuthorizationInterceptor implements HttpRequestInterceptor {
37+
38+
private final Resource resource;
39+
40+
public ResourceBasedAuthorizationInterceptor(Resource resource) {
41+
this.resource = resource;
42+
}
43+
44+
@Override
45+
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
46+
final String credentials = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
47+
httpRequest.addHeader(HttpHeaders.AUTHORIZATION, credentials);
48+
}
49+
}

0 commit comments

Comments
 (0)