001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.auth; 029 030import java.io.IOException; 031import java.util.Locale; 032import java.util.Map; 033import java.util.Queue; 034 035import org.apache.commons.logging.Log; 036import org.apache.commons.logging.LogFactory; 037import org.apache.http.Header; 038import org.apache.http.HttpException; 039import org.apache.http.HttpHost; 040import org.apache.http.HttpRequest; 041import org.apache.http.HttpResponse; 042import org.apache.http.auth.AuthOption; 043import org.apache.http.auth.AuthProtocolState; 044import org.apache.http.auth.AuthScheme; 045import org.apache.http.auth.AuthState; 046import org.apache.http.auth.AuthenticationException; 047import org.apache.http.auth.ContextAwareAuthScheme; 048import org.apache.http.auth.Credentials; 049import org.apache.http.auth.MalformedChallengeException; 050import org.apache.http.client.AuthenticationStrategy; 051import org.apache.http.protocol.HttpContext; 052import org.apache.http.util.Asserts; 053 054/** 055 * @since 4.3 056 */ 057public class HttpAuthenticator { 058 059 private final Log log; 060 061 public HttpAuthenticator(final Log log) { 062 super(); 063 this.log = log != null ? log : LogFactory.getLog(getClass()); 064 } 065 066 public HttpAuthenticator() { 067 this(null); 068 } 069 070 public boolean isAuthenticationRequested( 071 final HttpHost host, 072 final HttpResponse response, 073 final AuthenticationStrategy authStrategy, 074 final AuthState authState, 075 final HttpContext context) { 076 if (authStrategy.isAuthenticationRequested(host, response, context)) { 077 this.log.debug("Authentication required"); 078 if (authState.getState() == AuthProtocolState.SUCCESS) { 079 authStrategy.authFailed(host, authState.getAuthScheme(), context); 080 } 081 return true; 082 } else { 083 switch (authState.getState()) { 084 case CHALLENGED: 085 case HANDSHAKE: 086 this.log.debug("Authentication succeeded"); 087 authState.setState(AuthProtocolState.SUCCESS); 088 authStrategy.authSucceeded(host, authState.getAuthScheme(), context); 089 break; 090 case SUCCESS: 091 break; 092 default: 093 authState.setState(AuthProtocolState.UNCHALLENGED); 094 } 095 return false; 096 } 097 } 098 099 public boolean handleAuthChallenge( 100 final HttpHost host, 101 final HttpResponse response, 102 final AuthenticationStrategy authStrategy, 103 final AuthState authState, 104 final HttpContext context) { 105 try { 106 if (this.log.isDebugEnabled()) { 107 this.log.debug(host.toHostString() + " requested authentication"); 108 } 109 final Map<String, Header> challenges = authStrategy.getChallenges(host, response, context); 110 if (challenges.isEmpty()) { 111 this.log.debug("Response contains no authentication challenges"); 112 return false; 113 } 114 115 final AuthScheme authScheme = authState.getAuthScheme(); 116 switch (authState.getState()) { 117 case FAILURE: 118 return false; 119 case SUCCESS: 120 authState.reset(); 121 break; 122 case CHALLENGED: 123 case HANDSHAKE: 124 if (authScheme == null) { 125 this.log.debug("Auth scheme is null"); 126 authStrategy.authFailed(host, null, context); 127 authState.reset(); 128 authState.setState(AuthProtocolState.FAILURE); 129 return false; 130 } 131 case UNCHALLENGED: 132 if (authScheme != null) { 133 final String id = authScheme.getSchemeName(); 134 final Header challenge = challenges.get(id.toLowerCase(Locale.ROOT)); 135 if (challenge != null) { 136 this.log.debug("Authorization challenge processed"); 137 authScheme.processChallenge(challenge); 138 if (authScheme.isComplete()) { 139 this.log.debug("Authentication failed"); 140 authStrategy.authFailed(host, authState.getAuthScheme(), context); 141 authState.reset(); 142 authState.setState(AuthProtocolState.FAILURE); 143 return false; 144 } else { 145 authState.setState(AuthProtocolState.HANDSHAKE); 146 return true; 147 } 148 } else { 149 authState.reset(); 150 // Retry authentication with a different scheme 151 } 152 } 153 } 154 final Queue<AuthOption> authOptions = authStrategy.select(challenges, host, response, context); 155 if (authOptions != null && !authOptions.isEmpty()) { 156 if (this.log.isDebugEnabled()) { 157 this.log.debug("Selected authentication options: " + authOptions); 158 } 159 authState.setState(AuthProtocolState.CHALLENGED); 160 authState.update(authOptions); 161 return true; 162 } else { 163 return false; 164 } 165 } catch (final MalformedChallengeException ex) { 166 if (this.log.isWarnEnabled()) { 167 this.log.warn("Malformed challenge: " + ex.getMessage()); 168 } 169 authState.reset(); 170 return false; 171 } 172 } 173 174 public void generateAuthResponse( 175 final HttpRequest request, 176 final AuthState authState, 177 final HttpContext context) throws HttpException, IOException { 178 AuthScheme authScheme = authState.getAuthScheme(); 179 Credentials creds = authState.getCredentials(); 180 switch (authState.getState()) { // TODO add UNCHALLENGED and HANDSHAKE cases 181 case FAILURE: 182 return; 183 case SUCCESS: 184 ensureAuthScheme(authScheme); 185 if (authScheme.isConnectionBased()) { 186 return; 187 } 188 break; 189 case CHALLENGED: 190 final Queue<AuthOption> authOptions = authState.getAuthOptions(); 191 if (authOptions != null) { 192 while (!authOptions.isEmpty()) { 193 final AuthOption authOption = authOptions.remove(); 194 authScheme = authOption.getAuthScheme(); 195 creds = authOption.getCredentials(); 196 authState.update(authScheme, creds); 197 if (this.log.isDebugEnabled()) { 198 this.log.debug("Generating response to an authentication challenge using " 199 + authScheme.getSchemeName() + " scheme"); 200 } 201 try { 202 final Header header = doAuth(authScheme, creds, request, context); 203 request.addHeader(header); 204 break; 205 } catch (final AuthenticationException ex) { 206 if (this.log.isWarnEnabled()) { 207 this.log.warn(authScheme + " authentication error: " + ex.getMessage()); 208 } 209 } 210 } 211 return; 212 } else { 213 ensureAuthScheme(authScheme); 214 } 215 } 216 if (authScheme != null) { 217 try { 218 final Header header = doAuth(authScheme, creds, request, context); 219 request.addHeader(header); 220 } catch (final AuthenticationException ex) { 221 if (this.log.isErrorEnabled()) { 222 this.log.error(authScheme + " authentication error: " + ex.getMessage()); 223 } 224 } 225 } 226 } 227 228 private void ensureAuthScheme(final AuthScheme authScheme) { 229 Asserts.notNull(authScheme, "Auth scheme"); 230 } 231 232 @SuppressWarnings("deprecation") 233 private Header doAuth( 234 final AuthScheme authScheme, 235 final Credentials creds, 236 final HttpRequest request, 237 final HttpContext context) throws AuthenticationException { 238 if (authScheme instanceof ContextAwareAuthScheme) { 239 return ((ContextAwareAuthScheme) authScheme).authenticate(creds, request, context); 240 } else { 241 return authScheme.authenticate(creds, request); 242 } 243 } 244 245}