web-dev-qa-db-fra.com

java.io.IOException: aucun défi d'authentification trouvé

Je suis novice sur Android et ceci est mon premier projet sur Android. Je suis aux prises avec le problème de "l'authentification" pendant plus d'une journée. J'ai essayé plusieurs options mais aucune d'entre elles n'a fonctionné. 

En gros, je souhaite appeler une API REST et obtenir une réponse. Je suis sûr qu'il n'y a pas de problème dans les API car j'utilise le même logiciel dans une autre application iOS.

Je passe l'en-tête d'autorisation mais l'authentification ne montre toujours aucun message trouvé. J'ai trouvé peu de questions sur stackoverflow à ce sujet, mais certaines ne fonctionnaient pas et d'autres n'avaient aucun sens pour moi.

Je reçois le code de statut 401. Je sais que cela signifie soit qu'aucune authentification n'a été transmise, ou si elle est transmise, elles sont erronées. Ici, je suis sûr que mes réponses sont correctes. 

Ci-dessous mon code:

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();
}


Regardez capture d'écran de logcat:

logcat

Toute aide serait appréciée. Je vous remercie.

21
Geek

Cette erreur se produit car le serveur envoie un message 401 (non autorisé) mais ne donne pas d'en-tête WWW-Authenticate qui indique au client ce qu'il doit faire ensuite. L'en-tête WWW-Authenticate indique au client le type d'authentification requis ( Basic _ ou Digest ). Ce n'est probablement pas très utile dans les clients http sans tête, mais c'est ainsi que le HTTP 1.1 RFC est défini }. L'erreur se produit parce que la bibliothèque tente d'analyser l'en-tête WWW-Authenticate mais ne le peut pas. 

De la RFC:

(...) La réponse DOIT inclure un champ d'en-tête WWW-Authenticate (section 14.47) contenant un défi applicable à la ressource demandée. (...)

Solutions possibles si vous pouvez changer le serveur:

  • Ajoutez un faux en-tête "WWW-Authenticate" tel que: WWW-Authenticate: Basic realm="fake". Il s’agit d’une solution de contournement et non d’une solution, mais elle devrait fonctionner et le client http est satisfait ( voir ici une discussion de ce que vous pouvez mettre dans l’en-tête } _). Cependant, sachez que certains clients http peuvent réessayer automatiquement la demande, ce qui entraîne plusieurs demandes (par exemple, incrémente trop souvent le mauvais nombre de connexions). Cela a été observé avec le client HTTP iOS.
  • Comme proposé par loudvchar dans ce blog afin d'éviter les réactions automatiques au défi, telle qu'un formulaire de connexion contextuel dans un navigateur, vous pouvez utiliser une méthode d'authentification non standard telle que: WWW-Authenticate: xBasic realm="fake". Le point important est que la realm doit être incluse.
  • Utilisez le code d'état HTTP 403 au lieu de 401. La sémantique n'est pas la même et lorsque vous utilisez login 401, vous obtenez généralement une réponse correcte ( voir la discussion détaillée ci-dessous _), mais la solution la plus sûre en termes de compatibilité.

Solutions possibles si vous ne pouvez pas changer le serveur:

  • Comme @ErikZ l'a écrit dans son post , vous pouvez utiliser un test

    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(); 
    }
    
  • Utilisez un client http différent comme OkHttp

47
patrickf

J'avais le même problème sur les appareils fonctionnant sous Android pré-KitKat, mais j'utilisais la bibliothèque Volley, de sorte que le correctif côté client fourni par @ for3st ne fonctionnait pas pour moi, je devais l'adapter à Volley. aide une personne aux prises avec ce problème:

HurlStack hurlStack = new HurlStack() {
            @Override
            public HttpResponse performRequest(final Request<?> request, final Map<String, String> 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 cette façon, une erreur 401 est renvoyée et votre stratégie de nouvelle tentative peut s’acquitter de sa tâche (par exemple, jeton de demande, etc., etc.). Bien que l’exception IOException puisse être provoquée par un autre problème, autre qu’un code 401, vous pouvez choisir d’analyser le message d’exception pour le mot-clé Authorization et renvoyer un code de réponse différent pour les autres.

1
Leo K

Avait le même problème sur certains anciens appareils (Huawei Y330-U11 par exemple). La bonne façon de résoudre ce problème consiste à le réparer côté serveur, comme indiqué dans la réponse la plus courante.

Cependant, il est vraiment décevant que le problème ne se produise que sur certains périphériques. Et je crois que cela se produit en raison de différentes implémentations de "UrlConnection". Différentes versions d'Android - différentes implémentations de "UrlConnection".

Donc, vous voudrez peut-être résoudre le problème en utilisant la même "UrlConnection" partout. Essayez d’utiliser okhttp et okhttp-urlconnection.

Voici la façon d'ajouter ces bibliothèques à votre construction de gradle:

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

Cela a résolu le problème pour moi sur ces appareils obsolètes. (J'ai dû utiliser OkClient pour Retrofit RestAdapter)

P.S. Les derniers androïdes au moment de l'écriture utilisent l'ancienne version de la bibliothèque OKHTTP en interne comme implémentation "UrlConnection" (avec les noms de paquet mis à jour), donc cela semble être une chose assez solide

1
GregoryK

Quelle version d'Android testez-vous? 

J'ai eu des difficultés avec l'authentificateur Android lors de travaux de développement sur Gingerbread (je ne sais pas s'il se comporte différemment sur les versions ultérieures d'Android). J'ai utilisé Fiddler2 pour examiner le trafic HTTP entre mon application et le serveur, en découvrant que l'authentificateur n'avait pas envoyé la chaîne d'authentification pour chaque requête HTTP. J'en avais besoin.

Au lieu de cela, j'ai eu recours à ceci:

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

C'est un copier-coller de mon code. Notez que urlConnection est un objet HttpURLConnection.

1
UpLate

vous pouvez utiliser quelque chose comme ça

catch (IOException ex) {
        if(ex.getMessage().toString().toLowerCase().equals(("No authentication challenges found").toLowerCase()))
// re-generate the authentication Token
             }
0
Wowo Ot