1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.impl.auth;
29
30 import java.util.HashMap;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Queue;
36
37 import org.apache.hc.client5.http.AuthenticationStrategy;
38 import org.apache.hc.client5.http.auth.AuthChallenge;
39 import org.apache.hc.client5.http.auth.AuthExchange;
40 import org.apache.hc.client5.http.auth.AuthScheme;
41 import org.apache.hc.client5.http.auth.AuthenticationException;
42 import org.apache.hc.client5.http.auth.ChallengeType;
43 import org.apache.hc.client5.http.auth.CredentialsProvider;
44 import org.apache.hc.client5.http.auth.MalformedChallengeException;
45 import org.apache.hc.client5.http.protocol.HttpClientContext;
46 import org.apache.hc.core5.annotation.Contract;
47 import org.apache.hc.core5.annotation.ThreadingBehavior;
48 import org.apache.hc.core5.http.FormattedHeader;
49 import org.apache.hc.core5.http.Header;
50 import org.apache.hc.core5.http.HttpHeaders;
51 import org.apache.hc.core5.http.HttpHost;
52 import org.apache.hc.core5.http.HttpRequest;
53 import org.apache.hc.core5.http.HttpResponse;
54 import org.apache.hc.core5.http.HttpStatus;
55 import org.apache.hc.core5.http.ParseException;
56 import org.apache.hc.core5.http.message.BasicHeader;
57 import org.apache.hc.core5.http.message.ParserCursor;
58 import org.apache.hc.core5.http.protocol.HttpContext;
59 import org.apache.hc.core5.util.Asserts;
60 import org.apache.hc.core5.util.CharArrayBuffer;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64
65
66
67
68
69
70
71
72 @Contract(threading = ThreadingBehavior.STATELESS)
73 public final class HttpAuthenticator {
74
75 private static final Logger LOG = LoggerFactory.getLogger(HttpAuthenticator.class);
76
77 private final AuthChallengeParser parser;
78
79 public HttpAuthenticator() {
80 this.parser = new AuthChallengeParser();
81 }
82
83
84
85
86
87
88
89
90
91
92
93
94 public boolean isChallenged(
95 final HttpHost host,
96 final ChallengeType challengeType,
97 final HttpResponse response,
98 final AuthExchange authExchange,
99 final HttpContext context) {
100 final int challengeCode;
101 switch (challengeType) {
102 case TARGET:
103 challengeCode = HttpStatus.SC_UNAUTHORIZED;
104 break;
105 case PROXY:
106 challengeCode = HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED;
107 break;
108 default:
109 throw new IllegalStateException("Unexpected challenge type: " + challengeType);
110 }
111
112 final HttpClientContext clientContext = HttpClientContext.adapt(context);
113 final String exchangeId = clientContext.getExchangeId();
114
115 if (response.getCode() == challengeCode) {
116 if (LOG.isDebugEnabled()) {
117 LOG.debug("{} Authentication required", exchangeId);
118 }
119 return true;
120 }
121 switch (authExchange.getState()) {
122 case CHALLENGED:
123 case HANDSHAKE:
124 if (LOG.isDebugEnabled()) {
125 LOG.debug("{} Authentication succeeded", exchangeId);
126 }
127 authExchange.setState(AuthExchange.State.SUCCESS);
128 break;
129 case SUCCESS:
130 break;
131 default:
132 authExchange.setState(AuthExchange.State.UNCHALLENGED);
133 }
134 return false;
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 public boolean updateAuthState(
151 final HttpHost host,
152 final ChallengeType challengeType,
153 final HttpResponse response,
154 final AuthenticationStrategy authStrategy,
155 final AuthExchange authExchange,
156 final HttpContext context) {
157
158 final HttpClientContext clientContext = HttpClientContext.adapt(context);
159 final String exchangeId = clientContext.getExchangeId();
160
161 if (LOG.isDebugEnabled()) {
162 LOG.debug("{} {} requested authentication", exchangeId, host.toHostString());
163 }
164
165 final Header[] headers = response.getHeaders(
166 challengeType == ChallengeType.PROXY ? HttpHeaders.PROXY_AUTHENTICATE : HttpHeaders.WWW_AUTHENTICATE);
167 final Map<String, AuthChallenge> challengeMap = new HashMap<>();
168 for (final Header header: headers) {
169 final CharArrayBuffer buffer;
170 final int pos;
171 if (header instanceof FormattedHeader) {
172 buffer = ((FormattedHeader) header).getBuffer();
173 pos = ((FormattedHeader) header).getValuePos();
174 } else {
175 final String s = header.getValue();
176 if (s == null) {
177 continue;
178 }
179 buffer = new CharArrayBuffer(s.length());
180 buffer.append(s);
181 pos = 0;
182 }
183 final ParserCursor cursor = new ParserCursor(pos, buffer.length());
184 final List<AuthChallenge> authChallenges;
185 try {
186 authChallenges = parser.parse(challengeType, buffer, cursor);
187 } catch (final ParseException ex) {
188 if (LOG.isWarnEnabled()) {
189 LOG.warn("{} Malformed challenge: {}", exchangeId, header.getValue());
190 }
191 continue;
192 }
193 for (final AuthChallenge authChallenge: authChallenges) {
194 final String schemeName = authChallenge.getSchemeName().toLowerCase(Locale.ROOT);
195 if (!challengeMap.containsKey(schemeName)) {
196 challengeMap.put(schemeName, authChallenge);
197 }
198 }
199 }
200 if (challengeMap.isEmpty()) {
201 if (LOG.isDebugEnabled()) {
202 LOG.debug("{} Response contains no valid authentication challenges", exchangeId);
203 }
204 authExchange.reset();
205 return false;
206 }
207
208 switch (authExchange.getState()) {
209 case FAILURE:
210 return false;
211 case SUCCESS:
212 authExchange.reset();
213 break;
214 case CHALLENGED:
215 case HANDSHAKE:
216 Asserts.notNull(authExchange.getAuthScheme(), "AuthScheme");
217 case UNCHALLENGED:
218 final AuthScheme authScheme = authExchange.getAuthScheme();
219 if (authScheme != null) {
220 final String schemeName = authScheme.getName();
221 final AuthChallenge challenge = challengeMap.get(schemeName.toLowerCase(Locale.ROOT));
222 if (challenge != null) {
223 if (LOG.isDebugEnabled()) {
224 LOG.debug("{} Authorization challenge processed", exchangeId);
225 }
226 try {
227 authScheme.processChallenge(challenge, context);
228 } catch (final MalformedChallengeException ex) {
229 if (LOG.isWarnEnabled()) {
230 LOG.warn("{} {}", exchangeId, ex.getMessage());
231 }
232 authExchange.reset();
233 authExchange.setState(AuthExchange.State.FAILURE);
234 return false;
235 }
236 if (authScheme.isChallengeComplete()) {
237 if (LOG.isDebugEnabled()) {
238 LOG.debug("{} Authentication failed", exchangeId);
239 }
240 authExchange.reset();
241 authExchange.setState(AuthExchange.State.FAILURE);
242 return false;
243 }
244 authExchange.setState(AuthExchange.State.HANDSHAKE);
245 return true;
246 }
247 authExchange.reset();
248
249 }
250 }
251
252 final List<AuthScheme> preferredSchemes = authStrategy.select(challengeType, challengeMap, context);
253 final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
254 if (credsProvider == null) {
255 if (LOG.isDebugEnabled()) {
256 LOG.debug("{} Credentials provider not set in the context", exchangeId);
257 }
258 return false;
259 }
260
261 final Queue<AuthScheme> authOptions = new LinkedList<>();
262 if (LOG.isDebugEnabled()) {
263 LOG.debug("{} Selecting authentication options", exchangeId);
264 }
265 for (final AuthScheme authScheme: preferredSchemes) {
266 try {
267 final String schemeName = authScheme.getName();
268 final AuthChallenge challenge = challengeMap.get(schemeName.toLowerCase(Locale.ROOT));
269 authScheme.processChallenge(challenge, context);
270 if (authScheme.isResponseReady(host, credsProvider, context)) {
271 authOptions.add(authScheme);
272 }
273 } catch (final AuthenticationException | MalformedChallengeException ex) {
274 if (LOG.isWarnEnabled()) {
275 LOG.warn(ex.getMessage());
276 }
277 }
278 }
279 if (!authOptions.isEmpty()) {
280 if (LOG.isDebugEnabled()) {
281 LOG.debug("{} Selected authentication options: {}", exchangeId, authOptions);
282 }
283 authExchange.reset();
284 authExchange.setState(AuthExchange.State.CHALLENGED);
285 authExchange.setOptions(authOptions);
286 return true;
287 }
288 return false;
289 }
290
291
292
293
294
295
296
297
298
299
300
301 public void addAuthResponse(
302 final HttpHost host,
303 final ChallengeType challengeType,
304 final HttpRequest request,
305 final AuthExchange authExchange,
306 final HttpContext context) {
307 final HttpClientContext clientContext = HttpClientContext.adapt(context);
308 final String exchangeId = clientContext.getExchangeId();
309 AuthScheme authScheme = authExchange.getAuthScheme();
310 switch (authExchange.getState()) {
311 case FAILURE:
312 return;
313 case SUCCESS:
314 Asserts.notNull(authScheme, "AuthScheme");
315 if (authScheme.isConnectionBased()) {
316 return;
317 }
318 break;
319 case HANDSHAKE:
320 Asserts.notNull(authScheme, "AuthScheme");
321 break;
322 case CHALLENGED:
323 final Queue<AuthScheme> authOptions = authExchange.getAuthOptions();
324 if (authOptions != null) {
325 while (!authOptions.isEmpty()) {
326 authScheme = authOptions.remove();
327 authExchange.select(authScheme);
328 if (LOG.isDebugEnabled()) {
329 LOG.debug("{} Generating response to an authentication challenge using {} scheme",
330 exchangeId, authScheme.getName());
331 }
332 try {
333 final String authResponse = authScheme.generateAuthResponse(host, request, context);
334 final Header header = new BasicHeader(
335 challengeType == ChallengeType.TARGET ? HttpHeaders.AUTHORIZATION : HttpHeaders.PROXY_AUTHORIZATION,
336 authResponse);
337 request.addHeader(header);
338 break;
339 } catch (final AuthenticationException ex) {
340 if (LOG.isWarnEnabled()) {
341 LOG.warn("{} {} authentication error: {}", exchangeId, authScheme, ex.getMessage());
342 }
343 }
344 }
345 return;
346 }
347 Asserts.notNull(authScheme, "AuthScheme");
348 default:
349 }
350 if (authScheme != null) {
351 try {
352 final String authResponse = authScheme.generateAuthResponse(host, request, context);
353 final Header header = new BasicHeader(
354 challengeType == ChallengeType.TARGET ? HttpHeaders.AUTHORIZATION : HttpHeaders.PROXY_AUTHORIZATION,
355 authResponse);
356 request.addHeader(header);
357 } catch (final AuthenticationException ex) {
358 if (LOG.isErrorEnabled()) {
359 LOG.error("{} {} authentication error: {}", exchangeId, authScheme, ex.getMessage());
360 }
361 }
362 }
363 }
364
365 }