View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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.Immutable;
47  import org.apache.http.auth.AuthOption;
48  import org.apache.http.auth.AuthScheme;
49  import org.apache.http.auth.AuthSchemeProvider;
50  import org.apache.http.auth.AuthScope;
51  import org.apache.http.auth.Credentials;
52  import org.apache.http.auth.MalformedChallengeException;
53  import org.apache.http.client.AuthCache;
54  import org.apache.http.client.AuthenticationStrategy;
55  import org.apache.http.client.CredentialsProvider;
56  import org.apache.http.client.config.AuthSchemes;
57  import org.apache.http.client.config.RequestConfig;
58  import org.apache.http.client.protocol.HttpClientContext;
59  import org.apache.http.config.Lookup;
60  import org.apache.http.protocol.HTTP;
61  import org.apache.http.protocol.HttpContext;
62  import org.apache.http.util.Args;
63  import org.apache.http.util.CharArrayBuffer;
64  
65  @Immutable
66  abstract class AuthenticationStrategyImpl implements AuthenticationStrategy {
67  
68      private final Log log = LogFactory.getLog(getClass());
69  
70      private static final List<String> DEFAULT_SCHEME_PRIORITY =
71          Collections.unmodifiableList(Arrays.asList(
72                  AuthSchemes.SPNEGO,
73                  AuthSchemes.KERBEROS,
74                  AuthSchemes.NTLM,
75                  AuthSchemes.DIGEST,
76                  AuthSchemes.BASIC));
77  
78      private final int challengeCode;
79      private final String headerName;
80  
81      /**
82       * @param challengeCode for example SC_PROXY_AUTHENTICATION_REQUIRED or SC_UNAUTHORIZED
83       * @param headerName for example "Proxy-Authenticate" or "WWW-Authenticate"
84       */
85      AuthenticationStrategyImpl(final int challengeCode, final String headerName) {
86          super();
87          this.challengeCode = challengeCode;
88          this.headerName = headerName;
89      }
90  
91      @Override
92      public boolean isAuthenticationRequested(
93              final HttpHost authhost,
94              final HttpResponse response,
95              final HttpContext context) {
96          Args.notNull(response, "HTTP response");
97          final int status = response.getStatusLine().getStatusCode();
98          return status == this.challengeCode;
99      }
100 
101     /**
102      * Generates a map of challenge auth-scheme =&gt; Header entries.
103      *
104      * @return map: key=lower-cased auth-scheme name, value=Header that contains the challenge
105      */
106     @Override
107     public Map<String, Header> getChallenges(
108             final HttpHost authhost,
109             final HttpResponse response,
110             final HttpContext context) throws MalformedChallengeException {
111         Args.notNull(response, "HTTP response");
112         final Header[] headers = response.getHeaders(this.headerName);
113         final Map<String, Header> map = new HashMap<String, Header>(headers.length);
114         for (final Header header : headers) {
115             final CharArrayBuffer buffer;
116             int pos;
117             if (header instanceof FormattedHeader) {
118                 buffer = ((FormattedHeader) header).getBuffer();
119                 pos = ((FormattedHeader) header).getValuePos();
120             } else {
121                 final String s = header.getValue();
122                 if (s == null) {
123                     throw new MalformedChallengeException("Header value is null");
124                 }
125                 buffer = new CharArrayBuffer(s.length());
126                 buffer.append(s);
127                 pos = 0;
128             }
129             while (pos < buffer.length() && HTTP.isWhitespace(buffer.charAt(pos))) {
130                 pos++;
131             }
132             final int beginIndex = pos;
133             while (pos < buffer.length() && !HTTP.isWhitespace(buffer.charAt(pos))) {
134                 pos++;
135             }
136             final int endIndex = pos;
137             final String s = buffer.substring(beginIndex, endIndex);
138             map.put(s.toLowerCase(Locale.ROOT), header);
139         }
140         return map;
141     }
142 
143     abstract Collection<String> getPreferredAuthSchemes(RequestConfig config);
144 
145     @Override
146     public Queue<AuthOption> select(
147             final Map<String, Header> challenges,
148             final HttpHost authhost,
149             final HttpResponse response,
150             final HttpContext context) throws MalformedChallengeException {
151         Args.notNull(challenges, "Map of auth challenges");
152         Args.notNull(authhost, "Host");
153         Args.notNull(response, "HTTP response");
154         Args.notNull(context, "HTTP context");
155         final HttpClientContext clientContext = HttpClientContext.adapt(context);
156 
157         final Queue<AuthOption> options = new LinkedList<AuthOption>();
158         final Lookup<AuthSchemeProvider> registry = clientContext.getAuthSchemeRegistry();
159         if (registry == null) {
160             this.log.debug("Auth scheme registry not set in the context");
161             return options;
162         }
163         final CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
164         if (credsProvider == null) {
165             this.log.debug("Credentials provider not set in the context");
166             return options;
167         }
168         final RequestConfig config = clientContext.getRequestConfig();
169         Collection<String> authPrefs = getPreferredAuthSchemes(config);
170         if (authPrefs == null) {
171             authPrefs = DEFAULT_SCHEME_PRIORITY;
172         }
173         if (this.log.isDebugEnabled()) {
174             this.log.debug("Authentication schemes in the order of preference: " + authPrefs);
175         }
176 
177         for (final String id: authPrefs) {
178             final Header challenge = challenges.get(id.toLowerCase(Locale.ROOT));
179             if (challenge != null) {
180                 final AuthSchemeProvider authSchemeProvider = registry.lookup(id);
181                 if (authSchemeProvider == null) {
182                     if (this.log.isWarnEnabled()) {
183                         this.log.warn("Authentication scheme " + id + " not supported");
184                         // Try again
185                     }
186                     continue;
187                 }
188                 final AuthScheme authScheme = authSchemeProvider.create(context);
189                 authScheme.processChallenge(challenge);
190 
191                 final AuthScope authScope = new AuthScope(
192                         authhost.getHostName(),
193                         authhost.getPort(),
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                     // Try again
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                 schemeName.equalsIgnoreCase(AuthSchemes.DIGEST);
241     }
242 
243     @Override
244     public void authFailed(
245             final HttpHost authhost, final AuthScheme authScheme, final HttpContext context) {
246         Args.notNull(authhost, "Host");
247         Args.notNull(context, "HTTP context");
248 
249         final HttpClientContext clientContext = HttpClientContext.adapt(context);
250 
251         final AuthCache authCache = clientContext.getAuthCache();
252         if (authCache != null) {
253             if (this.log.isDebugEnabled()) {
254                 this.log.debug("Clearing cached auth scheme for " + authhost);
255             }
256             authCache.remove(authhost);
257         }
258     }
259 
260 }