java.io.IOException : No authentication challenges found

大兔子大兔子 提交于 2019-11-26 20:26:32
patrickf

This error happens because the server sends a 401 (Unauthorized) but does not give a WWW-Authenticate header which is a hint to the client what to do next. The WWW-Authenticate header tells the client, which kind of authentication is needed (either Basic or Digest). This is probably not very useful in headless http clients, but that's how the HTTP 1.1 RFC is defined. The error occurs because the lib tries to parse the WWW-Authenticate header but can't.

From the RFC:

(...)The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource.(...)

Possible solutions if you can change the server:

  • Add a fake "WWW-Authenticate" header like: WWW-Authenticate: Basic realm="fake". This is a mere workaround not a solution, but it should work and the http client is satisfied (see here a discussion of what you can put in the header). But beware that some http clients may automatically retry the request resulting in multiple requests (e.g. increments the wrong login count too often). This was observed with the iOS http client.
  • As proposed by loudvchar in this blog to avoid automatic reactions to the challenge like a pop-up login form in a browser, you can use a non-standard authentication method like so: WWW-Authenticate: xBasic realm="fake". The important point is that the realm has to be included.
  • Use HTTP status code 403 instead of 401. It's semantic is not the same and usually when working with login 401 is a correct response (see here for a detailed discussion) but the safer solution in terms of compatibility.

Possible solutions if you can't change the server:

  • As @ErikZ wrote in his post you could use a 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 different http client like OkHttp

What version of Android are you testing on?

I had difficulties with the Android authenticator during some development work on Gingerbread (I don't know if it behaves differently on later versions of Android). I used Fiddler2 to examine the HTTP traffic between my app and the server, discovering that the authenticator did not send out the authentication string for every HTTP request. I needed it to.

Instead, I resorted to this:

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

It's a cut-and-paste from my code. Note that urlConnection is an HttpURLConnection object.

I had the same issue on devices running pre-KitKat Android, but I was using the Volley library so the client side fix provided by @for3st didn't work for me, I had to adjust it for Volley, here it is, hope it helps someone struggling with this problem:

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

This way a 401 error is returned and your retry policy can do it's job (e.g. request token... etc...). Although IOException could be caused by some other problem, other then a 401, so you could opt for parsing the exception message for Authorization keyword, and return a different response code for others.

Had the same issue on some old devices (Huawei Y330-U11 for instance). Correct way to fix it is to fix it server side as mentioned in the most popular answer.

However, it's really disappointing that the issue occurs only on some devices. And I believe it happens due to different implementations of "UrlConnection". Different Android versions - different "UrlConnection" implementations.

So, you might want to fix it by using the same "UrlConnection" everywhere. Try to use okhttp and okhttp-urlconnection.

Here is the way to add those libs to your gradle build:

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

It solved the problem for me on those outdated devices. (I had to use OkClient for Retrofit RestAdapter)

P.S. Latest Androids at time of writing use old version of OKHTTP library internally as "UrlConnection" implemetation (with updated package names), so it seems to be quite solid thing

you can use something like this

catch (IOException ex) {
        if(ex.getMessage().toString().toLowerCase().equals(("No authentication challenges found").toLowerCase()))
// re-generate the authentication Token
             }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!