java.io.IOException:未找到身份验证质询

时间:2013-06-15 07:22:34

标签: java android authentication httpurlconnection ioexception

我是android的新手,这是我在android上的第一个项目。我在“认证”问题上苦苦挣扎了一天多。我尝试了几个选项,但没有一个可行。

基本上,我想调用REST API并获得响应。我确信API中没有问题,因为我在另一个iOS应用程序中使用相同的。

我传递了授权标头,但仍显示身份验证未找到的消息。我发现与stackoverflow相关的问题很少,但有些问题没有用,有些对我没有意义。

我得到状态代码401。我知道这意味着要么没有传递身份验证,要么传递,那么它们就错了。在这里,我确信我传递的是正确的。

以下是我的代码:

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


查看logcat的屏幕截图:

logcat

任何帮助将不胜感激。谢谢。

5 个答案:

答案 0 :(得分:47)

发生此错误是因为服务器发送401(未授权)但未提供WWW-Authenticate标头,这是对客户端下一步操作的提示。 WWW-Authenticate标头告诉客户端需要哪种身份验证(BasicDigest)。这在无头http客户端可能不是很有用,但这就是HTTP 1.1 RFC is defined的方式。发生此错误是因为lib尝试解析WWW-Authenticate标头但不能。

来自RFC:

  

(...)响应必须包含WWW-Authenticate头字段(部分   14.47)包含适用于所请求资源的挑战。(...)

如果可以更改服务器,可能的解决方案:

  • 添加假的“WWW-Authenticate”标题,如:WWW-Authenticate: Basic realm="fake"。这仅仅是一种解决方法而不是解决方案,但它应该可以工作并且http客户端满意(see here a discussion of what you can put in the header)。但请注意,某些http客户端可能会自动重试请求,从而导致多个请求(例如,过于频繁地增加错误的登录计数)。这是通过iOS http客户端观察到的。
  • 正如this blog中的 loudvchar 所提出的,为了避免像浏览器中的弹出式登录表单一样自动应对挑战,您可以使用非标准的身份验证方法,如下所示: WWW-Authenticate: xBasic realm="fake"。重要的是必须包含realm
  • 使用HTTP状态代码403代替401。它的语义不一样,通常在使用登录401时是正确的响应(see here for a detailed discussion),但在兼容性方面更安全。

如果无法更改服务器,可能的解决方案:

  • 正如@ErikZ在他的post中写道,你可以试试看& 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(); 
    }
    
  • 使用不同的http客户端,例如OkHttp

答案 1 :(得分:1)

您正在测试哪个版本的Android?

在Gingerbread的一些开发工作中我遇到了Android身份验证器的困难(我不知道它在Android的更高版本中是否表现不同)。我使用Fiddler2来检查我的应用程序和服务器之间的HTTP流量,发现身份验证器没有为每个HTTP请求发送身份验证字符串。我需要它。

相反,我采取了这个:

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

这是我的代码中的剪切和粘贴。请注意,urlConnection是一个HttpURLConnection对象。

答案 2 :(得分:1)

我在运行pre-KitKat Android的设备上遇到了同样的问题,但是我使用的是Volley库,所以@ for3st提供的客户端修复对我来说没有用,我不得不为Volley调整它,这里它是,希望它可以帮助有人解决这个问题:

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

这样会返回401错误,您的重试策略可以完成它的工作(例如请求令牌......等等)。虽然IOException可能是由其他问题引起的,但除了401之外,您可以选择解析Authorization关键字的异常消息,并为其他人返回不同的响应代码。

答案 3 :(得分:1)

在某些旧设备(例如华为Y330-U11)上存在同样的问题。解决问题的正确方法是修复服务器端,如最流行的答案所述。

然而,仅在某些设备上发生此问题确实令人失望。我相信它是由于“UrlConnection”的不同实现而发生的。不同的Android版本 - 不同的“UrlConnection”实现。

因此,您可能希望通过在任何地方使用相同的“UrlConnection”来修复它。尝试使用okhttp和okhttp-urlconnection。

以下是将这些库添加到gradle构建中的方法:

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

它在那些过时的设备上解决了我的问题。 (我不得不使用OkClient进行Retrofit RestAdapter)

P.S。在撰写本文时,最新的Androids在内部使用旧版本的OKHTTP库作为“UrlConnection”实现(具有更新的软件包名称),因此它似乎是非常可靠的东西

答案 4 :(得分:0)

您可以使用类似的东西

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