我需要可靠地检测设备是否具有完全的互联网访问权限,即用户不仅限于强制网络门户(也称为围墙花园),即有限的子网,强制用户在表单上提交凭据以获得完全访问权限。
我的应用程序正在自动执行身份验证过程,因此在开始登录活动之前知道完全互联网访问不可用非常重要。
问题是不关于如何检查网络接口是否已启动且处于连接状态。它是关于确保设备具有不受限制的互联网访问,而不是沙盒内部网段。
到目前为止我尝试过的所有方法都失败了,因为连接到任何知名主机都不会抛出异常但会返回有效的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”来检查实际的响应体。我使用的主机,但它看起来不是一个合适的解决方案。
答案 0 :(得分:43)
供参考,以下是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();
}
}
}
此方法依赖于特定网址,mWalledGardenUrl = "http://clients3.google.com/generate_204"
始终返回204
响应代码。即使DNS受到干扰,这也会起作用,因为在这种情况下将返回200
代码而不是预期的204
。我看到一些强制门户网站欺骗了对此特定网址的请求,以防止Android设备上的 Internet无法访问消息。
Google有此主题的变体:提取http://www.google.com/blank.html
将返回带有零长度响应正文的200
代码。因此,如果你得到一个非空的身体,这将是另一种方法,可以发现你是在一个有围墙的花园后面。
Apple有自己的URL用于检测强制网络门户:当网络启动时,IOS和MacOS设备会连接到http://www.apple.com/library/test/success.html,http://attwifi.apple.com/library/test/success.html或http://captive.apple.com/hotspot-detect.html等必须返回HTTP的网址状态代码200
和包含Success
的正文。
注: 这种方法不适用于互联网访问受限的地区,例如中国,其中整个国家是围墙花园,大多数Google / Apple服务被阻止或过滤。其中一些可能不会被阻止:
http://www.google.cn/generate_204
,http://g.cn/generate_204
,http://gstatic.com/generate_204
或http://connectivitycheck.gstatic.com/generate_204
- 但这些都属于Google,因此无法保证可以正常使用。
答案 1 :(得分:5)
另一种可能的解决方案可能是通过HTTPS连接并检查目标证书。不确定有围墙的花园是否真的通过HTTPS服务登录页面或只是删除连接。在任何一种情况下,您都应该能够看到您的目的地不是您期望的目的地。
当然,您还有TLS和证书检查的开销。不幸的是,这是经过身份验证的连接的价格。
答案 2 :(得分: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;
如果这不起作用,那么我认为唯一的方法就是检查响应的正文。
答案 3 :(得分: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();
}
}
}
答案 4 :(得分: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
}
}
);
请注意,您的互联网检查http客户端必须与主http客户端分开。
答案 5 :(得分:0)
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中更高级的操作