java.io.IOException: no se encontraron desafíos de autenticación

Soy un novato en Android y este es mi primer proyecto en Android. Estoy luchando con el problema de “autenticación” por más de un día. Probé varias opciones pero ninguna de ellas funcionó.

Básicamente, quiero llamar a una API REST y obtener respuesta. Estoy seguro de que no hay ningún problema en la API ya que la utilizo en otra aplicación de iOS.

Paso el encabezado de autorización, pero aún se muestra el mensaje de autenticación no encontrada. Encontré pocas preguntas sobre stackoverflow relacionadas con esto, pero algunas no funcionaron y otras no tienen sentido para mí.

Obtengo el código de estado 401 . Sé que esto significa que o bien no se aprobó la autenticación o si se aprobó, entonces están equivocados. Aquí, estoy seguro de que mis aprobados son correctos.

A continuación está mi código:

 try { url = new URL(baseUrl); } catch (MalformedURLException me) { Log.e(TAG, "URL could not be parsed. URL : " + baseUrl + ". Line : " + getLineNumber(), me); me.printStackTrace(); } try { urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod(method); urlConnection.setConnectTimeout(TIMEOUT * 1000); urlConnection.setChunkedStreamingMode(0); // Set HTTP headers String authString = "username:password"; String base64Auth = Base64.encodeToString(authString.getBytes(), Base64.DEFAULT); urlConnection.setRequestProperty("Authorization", "Basic " + base64Auth); urlConnection.setRequestProperty("Accept", "application/json"); urlConnection.setRequestProperty("Content-type", "application/json"); if (method.equals("POST") || method.equals("PUT")) { // Set to true when posting data urlConnection.setDoOutput(true); // Write data to post to connection output stream OutputStream out = urlConnection.getOutputStream(); out.write(postParameters.getBytes("UTF-8")); } try { // Get response in = new BufferedInputStream(urlConnection.getInputStream()); } catch (IOException e) { Log.e(TAG, "Exception in getting connection input stream. in : " + in); e.printStackTrace(); } // Read the input stream that has response statusCode = urlConnection.getResponseCode(); Log.d(TAG, "Status code : " + statusCode); } catch (ProtocolException pe) { pe.printStackTrace(); } catch (IllegalStateException ie) { ie.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { urlConnection.disconnect(); } 

Mire la captura de pantalla de logcat:

logcat

Cualquier ayuda sería apreciada. Gracias.

Este error ocurre porque el servidor envía un 401 (no autorizado) pero no da un encabezado WWW-Authenticate que es una pista para el cliente sobre qué hacer a continuación. El encabezado WWW-Authenticate le dice al cliente qué tipo de autenticación es necesaria (ya sea Basic o Digest ). Esto probablemente no sea muy útil en clientes http sin cabeza, pero así es como se define el HTTP 1.1 RFC . El error se produce porque la lib intenta analizar el encabezado WWW-Authenticate pero no puede.

De la RFC:

(…) La respuesta DEBE incluir un campo de encabezado WWW-Authenticate (sección 14.47) que contenga un desafío aplicable al recurso solicitado. (…)

Posibles soluciones si puedes cambiar el servidor:

  • Agregue un encabezado falso “WWW-Authenticate” como: WWW-Authenticate: Basic realm="fake" . Esta es una mera solución, no una solución, pero debería funcionar y el cliente http está satisfecho ( consulte aquí una explicación de lo que puede colocar en el encabezado ). Pero tenga en cuenta que algunos clientes http pueden volver a intentar automáticamente la solicitud dando como resultado múltiples solicitudes (por ejemplo, incrementa el recuento incorrecto de inicio de sesión con demasiada frecuencia). Esto se observó con el cliente http de iOS.
  • Según lo propuesto por loudvchar en este blog para evitar reacciones automáticas al desafío como un formulario de inicio de sesión emergente en un navegador, puede utilizar un método de autenticación no estándar como el siguiente: WWW-Authenticate: xBasic realm="fake" . El punto importante es que el realm debe ser incluido.
  • Use el código de estado HTTP 403 lugar de 401 . Su semántica no es la misma y, por lo general, cuando se trabaja con el inicio de sesión 401 es una respuesta correcta ( vea aquí una discusión detallada ) pero la solución más segura en términos de compatibilidad.

Posibles soluciones si no puedes cambiar el servidor:

  • Como @ErikZ escribió en su publicación , podrías usar try & catch

     HttpURLConnection connection = ...; try { // Will throw IOException if server responds with 401. connection.getResponseCode(); } catch (IOException e) { // Will return 401, because now connection has the correct internal state. int responsecode = connection.getResponseCode(); } 
  • Use un cliente http diferente como OkHttp

¿En qué versión de Android estás probando?

Tuve dificultades con el autenticador de Android durante algunos trabajos de desarrollo en Gingerbread (no sé si se comporta de manera diferente en las versiones posteriores de Android). Usé Fiddler2 para examinar el tráfico HTTP entre mi aplicación y el servidor, descubriendo que el autenticador no envió la cadena de autenticación para cada solicitud HTTP. Lo necesitaba.

En cambio, recurrí a esto:

 urlConnection.setRequestProperty("Authorization", "Basic " + Base64.encodeToString("userid:pwd".getBytes(), Base64.NO_WRAP )); 

Es un cortar y pegar de mi código. Tenga en cuenta que urlConnection es un objeto HttpURLConnection.

Tuve el mismo problema en los dispositivos que ejecutan Android pre-KitKat, pero estaba usando la biblioteca Volley, por lo que la corrección del cliente proporcionada por @ for3st no funcionó para mí, tuve que ajustarla para Volley, aquí está, espero que ayuda a alguien a luchar con este problema:

 HurlStack hurlStack = new HurlStack() { @Override public HttpResponse performRequest(final Request request, final Map additionalHeaders) throws IOException, AuthFailureError { try { return super.performRequest(request, additionalHeaders); } catch (IOException e) { return new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 401, e.getMessage()); } } }; Volley.newRequestQueue(context.getApplicationContext(), hurlStack); 

De esta manera, se devuelve un error 401 y su política de rebash puede hacer su trabajo (por ejemplo, solicitud de token … etc …). Aunque IOException podría ser causada por algún otro problema, aparte de un 401, por lo que podría optar por analizar el mensaje de excepción para la palabra clave Authorization, y devolver un código de respuesta diferente para los demás.

Tuve el mismo problema en algunos dispositivos antiguos (Huawei Y330-U11, por ejemplo). La forma correcta de corregirlo es arreglarlo desde el lado del servidor como se menciona en la respuesta más popular.

Sin embargo, es realmente decepcionante que el problema ocurra solo en algunos dispositivos. Y creo que sucede debido a las diferentes implementaciones de “UrlConnection”. Diferentes versiones de Android: diferentes implementaciones de “UrlConnection”.

Por lo tanto, es posible que desee solucionarlo utilizando el mismo “UrlConnection” en todas partes. Intenta usar okhttp y okhttp-urlconnection.

Aquí está la manera de agregar esas libs a tu comstackción de gradle:

 compile 'com.squareup.okhttp:okhttp:2.5.0' compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0' 

Me solucionó el problema en esos dispositivos desactualizados. (Tuve que usar OkClient para Retrofit RestAdapter)

Los últimos PS Androids al momento de escribir usan la versión anterior de la biblioteca OKHTTP internamente como implemetación “UrlConnection” (con nombres de paquetes actualizados), por lo que parece ser algo bastante sólido