36

我需要可靠地检测设备是否具有完整的互联网访问权限,即用户不限于强制门户(也称为围墙花园),即强制用户在表单上提交其凭据以获得完整的有限子网使用权。

我的应用程序正在自动执行身份验证过程,因此在开始登录活动之前了解无法完全访问 Internet 非常重要。

问题在于如何检查网络接口是否已启动并处于连接状态。这是为了确保设备具有不受限制的互联网访问权限,而不是沙盒内网段。

到目前为止我尝试过的所有方法都失败了,因为连接到任何知名主机都不会引发异常,而是返回有效的HTTP 200响应代码,因为所有请求都被路由到登录页面。

以下是我尝试过的所有方法,但它们都返回true而不是false出于上述原因:

1:

InetAddress.getByName(host).isReachable(TIMEOUT_IN_MILLISECONDS);
isConnected = true; <exception not thrown>

2:

Socket socket = new Socket();
SocketAddress sockaddr = new InetSocketAddress(InetAddress.getByName(host), 80);
socket.connect(sockaddr, pingTimeout);
isConnected = socket.isConnected();

3:

URL url = new URL(hostUrl));
URLConnection urlConn = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection) urlConn;
httpConn.setAllowUserInteraction(false);
httpConn.setRequestMethod("GET");
httpConn.connect();
responseCode = httpConn.getResponseCode();
isConnected = responseCode == HttpURLConnection.HTTP_OK;

那么,如何确保我连接到实际主机而不是登录重定向页面?显然,我可以从我使用的“ping”主机检查实际响应正文,但它看起来不是一个合适的解决方案。

4

6 回答 6

44

作为参考,这里是来自 Android 4.0.1 AOSP 代码库的“官方”方法: WifiWatchdogStateMachine.isWalledGardenConnection()。我将下面的代码包括在内,以防万一将来链接中断。

private static final String mWalledGardenUrl = "http://clients3.google.com/generate_204";
private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;

private boolean isWalledGardenConnection() {
    HttpURLConnection urlConnection = null;
    try {
        URL url = new URL(mWalledGardenUrl); // "http://clients3.google.com/generate_204"
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setInstanceFollowRedirects(false);
        urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
        urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
        urlConnection.setUseCaches(false);
        urlConnection.getInputStream();
        // We got a valid response, but not from the real google
        return urlConnection.getResponseCode() != 204;
    } catch (IOException e) {
        if (DBG) {
            log("Walled garden check - probably not a portal: exception "
                    + e);
        }
        return false;
    } finally {
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }
}

这种方法依赖于一个特定的 URL,mWalledGardenUrl = "http://clients3.google.com/generate_204"总是返回一个204响应代码。即使 DNS 受到干扰,这也会起作用,因为在这种情况下,200将返回一个代码而不是预期的204. 我看到一些强制门户网站欺骗了对这个特定 URL 的请求,以防止在 Android 设备上出现Internet 无法访问的消息。

谷歌有这个主题的变体:获取http://www.google.com/blank.html将返回一个200长度为零的响应正文的代码。所以如果你得到一个非空的身体,这将是另一种判断你在围墙花园后面的方法。

Apple 有自己的 URL 用于检测强制门户:当网络启动时,IOS 和 MacOS 设备将连接到诸如http://www.apple.com/library/test/success.html、http://attwifi.apple之类的URL 。 com/library/test/success.htmlhttp://captive.apple.com/hotspot-detect.html必须返回 HTTP 状态代码200和包含Success.

注意:这种方法不适用于互联网访问受限的地区,例如中国,整个国家都是围墙花园,并且某些 Google/Apple 服务可能会被阻止。其中一些可能不会被阻止:http://www.google.cn/generate_204、、或——但这些都属于谷歌,因此不能保证能正常工作http://g.cn/generate_204http://gstatic.com/generate_204http://connectivitycheck.gstatic.com/generate_204

于 2012-12-25T11:36:58.087 回答
5

另一种可能的解决方案可能是通过 HTTPS 连接并检查目标证书。不确定围墙花园是否真的通过 HTTPS 为登录页面提供服务,或者只是断开连接。无论哪种情况,您都应该能够看到您的目的地不是您所期望的。

当然,您也有 TLS 和证书检查的开销。不幸的是,这就是经过身份验证的连接的代价。

于 2012-12-25T12:00:06.987 回答
1

我相信防止您的连接重定向会起作用。

URL url = new URL(hostUrl));
HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();

/* This line prevents redirects */
httpConn.setInstanceFollowRedirects( false );

httpConn.setAllowUserInteraction( false );
httpConn.setRequestMethod( "GET" );
httpConn.connect();
responseCode = httpConn.getResponseCode();
isConnected = responseCode == HttpURLConnection.HTTP_OK;

如果这不起作用,那么我认为唯一的方法是检查响应的正文。

于 2012-12-19T18:24:35.887 回答
0

这已在 Android 4.2.2+ 版本上实现 - 我发现他们的方法快速而有趣:

CaptivePortalTracker.java 检测围墙花园如下 - 尝试连接到 www.google.com/generate_204 - 检查 HTTP 响应是否为 204

如果检查失败,我们就在一个有围墙的花园里。

private boolean isCaptivePortal(InetAddress server) {
    HttpURLConnection urlConnection = null;
    if (!mIsCaptivePortalCheckEnabled) return false;

    mUrl = "http://" + server.getHostAddress() + "/generate_204";
    if (DBG) log("Checking " + mUrl);
    try {
        URL url = new URL(mUrl);
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setInstanceFollowRedirects(false);
        urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
        urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
        urlConnection.setUseCaches(false);
        urlConnection.getInputStream();
        // we got a valid response, but not from the real google
        return urlConnection.getResponseCode() != 204;
    } catch (IOException e) {
        if (DBG) log("Probably not a portal: exception " + e);
        return false;
    } finally {
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }
}
于 2014-01-28T20:44:33.810 回答
0

如果您已经在使用retrofit,您可以通过retrofit. 只需创建一个 ping.html 页面并使用改造向其发送头部请求,并确保您的 http 客户端配置如下:(followRedirects(false)部分是最重要的部分)

private OkHttpClient getCheckInternetOkHttpClient() {
    return new OkHttpClient.Builder()
            .readTimeout(2L, TimeUnit.SECONDS)
            .connectTimeout(2L, TimeUnit.SECONDS)
            .followRedirects(false)
            .build();
}

然后像下面这样构建你的改造:

private InternetCheckApi getCheckInternetRetrofitApi() {
    return (new Retrofit.Builder())
            .baseUrl("[base url of your ping.html page]")             
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .client(getCheckInternetOkHttpClient())
            .build().create(InternetCheckApi.class);
}

你的 InternetCheckApi.class 就像:

public interface InternetCheckApi {
    @Headers({"Content-Typel: application/json"})
    @HEAD("ping.html")
    Call<Void> checkInternetConnectivity();
}

那么你可以像下面这样使用它:

getCheckInternetOkHttpClient().checkInternetConnectivity().enqueue(new Callback<Void>() {
     public void onResponse(Call<Void> call, Response<Void> response) {
       if(response.code() == 200) {
        //internet is available
       } else {
         //internet is not available
       }
     }

     public void onFailure(Call<Void> call, Throwable t) {
        //internet is not available
     }
  }
);

请注意,您的 Internet 检查 http 客户端必须与您的主要 http 客户端分开。

于 2017-08-03T07:01:52.997 回答
0

这最好在 AOSP 中完成: https ://github.com/aosp-mirror/platform_frameworks_base/blob/6bebb8418ceecf44d2af40033870f3aabacfe36e/core/java/android/net/captiveportal/CaptivePortalProbeResult.java#L61

https://github.com/aosp-mirror/platform_frameworks_base/blob/e3a0f42e8e8678f6d90ddf104d485858fbb2e35b/services/core/java/com/android/server/connectivity/NetworkMonitor.java

private static final String GOOGLE_PING_URL = "http://google.com/generate_204";
private static final int SOCKET_TIMEOUT_MS = 10000;

public boolean isCaptivePortal () {

try {
            URL url = new URL(GOOGLE_PING_URL);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
            urlConnection.setUseCaches(false);
            urlConnection.getInputStream();
            return (urlConnection.getResponseCode() != 204)
                    && (urlConnection.getResponseCode() >= 200)
                    && (urlConnection.getResponseCode() <= 399);
        } catch (Exception e) {
            // for any exception throw an exception saying check was unsuccesful
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
}

请注意,这可能不适用于代理网络,并且需要完成 AOSP url 中更高级的操作

于 2019-01-24T05:12:32.400 回答