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.http.impl.client;
29
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Queue;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42 import org.apache.http.FormattedHeader;
43 import org.apache.http.Header;
44 import org.apache.http.HttpHost;
45 import org.apache.http.HttpResponse;
46 import org.apache.http.annotation.Contract;
47 import org.apache.http.annotation.ThreadingBehavior;
48 import org.apache.http.auth.AuthOption;
49 import org.apache.http.auth.AuthScheme;
50 import org.apache.http.auth.AuthSchemeProvider;
51 import org.apache.http.auth.AuthScope;
52 import org.apache.http.auth.Credentials;
53 import org.apache.http.auth.MalformedChallengeException;
54 import org.apache.http.client.AuthCache;
55 import org.apache.http.client.AuthenticationStrategy;
56 import org.apache.http.client.CredentialsProvider;
57 import org.apache.http.client.config.AuthSchemes;
58 import org.apache.http.client.config.RequestConfig;
59 import org.apache.http.client.protocol.HttpClientContext;
60 import org.apache.http.config.Lookup;
61 import org.apache.http.protocol.HTTP;
62 import org.apache.http.protocol.HttpContext;
63 import org.apache.http.util.Args;
64 import org.apache.http.util.CharArrayBuffer;
65
66 @Contract(threading = ThreadingBehavior.IMMUTABLE)
67 abstract class AuthenticationStrategyImpl implements AuthenticationStrategy {
68
69 private final Log log = LogFactory.getLog(getClass());
70
71 private static final List<String> DEFAULT_SCHEME_PRIORITY =
72 Collections.unmodifiableList(Arrays.asList(
73 AuthSchemes.SPNEGO,
74 AuthSchemes.KERBEROS,
75 AuthSchemes.NTLM,
76 AuthSchemes.CREDSSP,
77 AuthSchemes.DIGEST,
78 AuthSchemes.BASIC));
79 private final int challengeCode;
80 private final String headerName;
81
82
83
84
85
86 AuthenticationStrategyImpl(final int challengeCode, final String headerName) {
87 super();
88 this.challengeCode = challengeCode;
89 this.headerName = headerName;
90 }
91
92 @Override
93 public boolean isAuthenticationRequested(
94 final HttpHost authhost,
95 final HttpResponse response,
96 final HttpContext context) {
97 Args.notNull(response, "HTTP response");
98 final int status = response.getStatusLine().getStatusCode();
99 return status == this.challengeCode;
100 }
101
102
103
104
105
106
107 @Override
108 public Map<String, Header> getChallenges(
109 final HttpHost authhost,
110 final HttpResponse response,
111 final HttpContext context) throws MalformedChallengeException {
112 Args.notNull(response, "HTTP response");
113 final Header[] headers = response.getHeaders(this.headerName);
114 final Map<String, Header> map = new HashMap<String, Header>(headers.length);
115 for (final Header header : headers) {
116 final CharArrayBuffer buffer;
117 int pos;
118 if (header instanceof FormattedHeader) {
119 buffer = ((FormattedHeader) header).getBuffer();
120 pos = ((FormattedHeader) header).getValuePos();
121 } else {
122 final String s = header.getValue();
123 if (s == null) {
124 throw new MalformedChallengeException("Header value is null");
125 }
126 buffer = new CharArrayBuffer(s.length());
127 buffer.append(s);
128 pos = 0;
129 }
130 while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
131 pos++;
132 }
133 final int beginIndex = pos;
134 while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) {
135 pos++;
136 }
137 final int endIndex = pos;
138 final String s = buffer.substring(beginIndex, endIndex);
139 map.put(s.toLowerCase(Locale.ROOT), header);
140 }
141 return map;
142 }
143
144 abstract Collection<String> getPreferredAuthSchemes(RequestConfig config);
145
146 @Override
147 public Queue<AuthOption> select(
148 final Map<String, Header> challenges,
149 final HttpHost authhost,
150 final HttpResponse response,
151 final HttpContext context) throws MalformedChallengeException {
152 Args.notNull(challenges, "Map of auth challenges");
153 Args.notNull(authhost, "Host");
154 Args.notNull(response, "HTTP response");
155 Args.notNull(context, "HTTP context");
156 final HttpClientContext clientContext = HttpClientContext.adapt(context);
157
158 final Queue<AuthOption> options = new LinkedList<AuthOption>();
159 final Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry();
160 if (registry == null) {
161 this.log.debug("Auth scheme registry not set in the context");
162 return options;
163 }
164 final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
165 if (credsProvider == null) {
166 this.log.debug("Credentials provider not set in the context");
167 return options;
168 }
169 final RequestConfig config = clientContext.getRequestConfig();
170 Collection<String> authPrefs = getPreferredAuthSchemes(config);
171 if (authPrefs == null) {
172 authPrefs = DEFAULT_SCHEME_PRIORITY;
173 }
174 if (this.log.isDebugEnabled()) {
175 this.log.debug("Authentication schemes in the order of preference: " + authPrefs);
176 }
177
178 for (final String id: authPrefs) {
179 final Header challenge = challenges.get(id.toLowerCase(Locale.ROOT));
180 if (challenge != null) {
181 final AuthSchemeProvider authSchemeProvider = registry.lookup(id);
182 if (authSchemeProvider == null) {
183 if (this.log.isWarnEnabled()) {
184 this.log.warn("Authentication scheme " + id + " not supported");
185
186 }
187 continue;
188 }
189 final AuthScheme authScheme = authSchemeProvider.create(context);
190 authScheme.processChallenge(challenge);
191
192 final AuthScopehtml#AuthScope">AuthScope authScope = new AuthScope(
193 authhost,
194 authScheme.getRealm(),
195 authScheme.getSchemeName());
196
197 final Credentials credentials = credsProvider.getCredentials(authScope);
198 if (credentials != null) {
199 options.add(new AuthOption(authScheme, credentials));
200 }
201 } else {
202 if (this.log.isDebugEnabled()) {
203 this.log.debug("Challenge for " + id + " authentication scheme not available");
204
205 }
206 }
207 }
208 return options;
209 }
210
211 @Override
212 public void authSucceeded(
213 final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
214 Args.notNull(authhost, "Host");
215 Args.notNull(authScheme, "Auth scheme");
216 Args.notNull(context, "HTTP context");
217
218 final HttpClientContext clientContext = HttpClientContext.adapt(context);
219
220 if (isCachable(authScheme)) {
221 AuthCache authCache = clientContext.getAuthCache();
222 if (authCache == null) {
223 authCache = new BasicAuthCache();
224 clientContext.setAuthCache(authCache);
225 }
226 if (this.log.isDebugEnabled()) {
227 this.log.debug("Caching '" + authScheme.getSchemeName() +
228 "' auth scheme for " + authhost);
229 }
230 authCache.put(authhost, authScheme);
231 }
232 }
233
234 protected boolean isCachable(final AuthScheme authScheme) {
235 if (authScheme == null || !authScheme.isComplete()) {
236 return false;
237 }
238 final String schemeName = authScheme.getSchemeName();
239 return schemeName.equalsIgnoreCase(AuthSchemes.BASIC);
240 }
241
242 @Override
243 public void authFailed(
244 final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
245 Args.notNull(authhost, "Host");
246 Args.notNull(context, "HTTP context");
247
248 final HttpClientContext clientContext = HttpClientContext.adapt(context);
249
250 final AuthCache authCache = clientContext.getAuthCache();
251 if (authCache != null) {
252 if (this.log.isDebugEnabled()) {
253 this.log.debug("Clearing cached auth scheme for " + authhost);
254 }
255 authCache.remove(authhost);
256 }
257 }
258
259 }