24

我是 android 新手,这是我在 android 上的第一个项目。我在“身份验证”问题上苦苦挣扎了一天多。我尝试了几种选择,但都没有奏效。

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

我通过了授权标头,但仍然没有显示身份验证消息。我在 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 的屏幕截图:

日志猫

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

4

5 回答 5

51

发生此错误是因为服务器发送了 401(未经授权),但没有给出WWW-Authenticate提示客户端下一步要做什么的标头。标WWW-Authenticate头告诉客户端,需要哪种身份验证(BasicDigest)。这在无头 http 客户端中可能不是很有用,但这就是HTTP 1.1 RFC 的定义方式。发生错误是因为 lib 尝试解析WWW-Authenticate标头但不能。

来自 RFC:

(...)响应必须包含一个 WWW-Authenticate 头字段(第 14.47 节),其中包含适用于所请求资源的质询。(...)

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

  • 添加一个假的“WWW-Authenticate”标头,例如:WWW-Authenticate: Basic realm="fake". 这只是一种解决方法而不是解决方案,但它应该可以工作并且 http 客户端很满意(请参阅此处讨论您可以在标头中放入的内容)。但请注意,某些 http 客户端可能会自动重试请求,从而导致多个请求(例如,过于频繁地增加错误的登录计数)。这是在 iOS http 客户端中观察到的。
  • 正如loudvchar本博客中提出的那样,为了避免对挑战的自动反应,例如浏览器中的弹出登录表单,您可以使用非标准身份验证方法,如下所示:WWW-Authenticate: xBasic realm="fake". 重要的一点是realm必须包括在内。
  • 使用 HTTP 状态代码403而不是401. 它的语义是不一样的,通常在使用登录 401 时是正确的响应(详细讨论请参见此处),但在兼容性方面是更安全的解决方案。

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

  • 正如@ErikZ 在他的帖子中所写,您可以使用 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(); 
    }
    
  • 使用不同的 http 客户端,如OkHttp

于 2014-02-03T17:54:55.033 回答
1

您在哪个版本的 Android 上进行测试?

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

相反,我求助于这个:

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

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

于 2013-06-15T10:47:09.130 回答
1

我在运行 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 关键字解析异常消息,并为其他问题返回不同的响应代码。

于 2015-09-23T14:55:58.087 回答
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 来改造 RestAdapter)

PS 在撰写本文时,最新的 Android 在内部使用旧版本的 OKHTTP 库作为“UrlConnection”实现(带有更新的包名称),所以它似乎是相当可靠的东西

于 2015-11-24T07:34:35.483 回答
0

你可以使用这样的东西

catch (IOException ex) {
        if(ex.getMessage().toString().toLowerCase().equals(("No authentication challenges found").toLowerCase()))
// re-generate the authentication Token
             }
于 2018-12-18T18:28:51.587 回答