android - 翻新请求在 VPN 后面抛出 UnknownHostException

标签 android ssl retrofit2 vpn unknown-host

所以我遇到了一个看起来直接来自暮光之城的问题。

问题

我必须从后端访问 REST API 端点,问题是为了访问该端点我需要通过 VPN。否则主机不可访问。在桌面上一切正常,我打开 Postman,点击 GET 端点并获得响应。但是,当我尝试通过我的 Android 设备访问相同的端点时,Retrofit 会抛出 UnknownHostException。

上下文

端点 url 类似于 https://api.something.something.net/。我在 Dagger 中使用依赖注入(inject),所以我有一个看起来像这样的 NetworkModule:

...
NetworkModule("https://api.something.something.net/")
...
@Module
class NetworkModule(
    private val baseHost: String
) {
...
    @Provides
    @Named("authInterceptor")
    fun providesAuthInterceptor(
        @Named("authToken") authToken: String
    ): Interceptor {
        return Interceptor { chain ->
            var request = chain.request()

            request = request.newBuilder()
                .addHeader("Content-Type", "application/json")
                .addHeader("Authorization", "Bearer $authToken")
                .build()
            val response = chain.proceed(request)
        }
    }
...
    @Provides
    @Singleton
    fun provideOkHttpClient(
        curlInterceptor: CurlInterceptor,
        @Named("authInterceptor") authInterceptor: Interceptor
    ): OkHttpClient {
        val builder = OkHttpClient.Builder()
        builder.addInterceptor(authInterceptor)
        builder.addInterceptor(curlInterceptor)
        return builder.build()
    }
...
    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit {
        return Retrofit.Builder()
                .baseUrl(baseHost)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(okHttpClient)
                .build()
    }
}

然后我有一堆存储库,它们是通过 Retrofit 执行请求的存储库:

class MyApiRepositoryImpl(
    val myRetrofitApi: MyRetrofitApi,
    val uiScheduler: Scheduler,
    val backgroundScheduler: Scheduler
) : MyApiRepository {

    override fun getSomethingFromTheApi(): Observable<DataResource<List<ApiSomethingResponse>>> {
        return myRetrofitApi.getResponseFromEndpoint()
            .map {
                if (it.isSuccessful) {
                    DataResource(it.body()?.list!!, ResourceType.NETWORK_SUCCESS)
                } else {
                    throw RuntimeException("Network request failed code ${it.code()}")
                }
            }
            .subscribeOn(backgroundScheduler)
            .observeOn(uiScheduler)
            .toObservable()
    }
}

这是 Retrofit 的 API 接口(interface):

interface MyRetrofitApi {

    @GET("/v1/something/")
    fun getResponseFromEndpoint(): Single<Response<ApiSomethingResponse>>
}

因此,当我从我的 Interactor/UseCases 调用此 Repository 方法时,它会直接跳过 onError 并显示 UnknownHostException。

到目前为止我尝试了什么

  • 我改用 Volley 的 Retrofit,后来改用 Ion,只是为了确保这与其他客户端无关。我在所有情况下都遇到了相同的异常:

java.net.UnknownHostException:无法解析主机“api.something.something.net”:没有与主机名关联的地址

com.android.volley.NoConnectionError: java.net.UnknownHostException: 无法解析主机“api.something.something.net”: 没有与主机名关联的地址

我用 Retrofit 和 OkHttpClient 尝试了所有可能的配置:

  • 在 OkHttpClient 上,我尝试将 followSslRedirects 设置为 true 和 false。 followRedirects 为 true 和 false。设置 hostnameVerifier 以允许任何主机名通过。设置 SSLSocketFactory 以允许任何未签名的证书通过。

  • 在我的 list 上,我将我的 android:networkSecurityConfig 设置为:

https://github.com/commonsguy/cwac-netsecurity/issues/5

  • 我在我的 Android 设备 (Android Nougat)、带有 Nougat、Marshmellow 和 Oreo 的模拟器以及带有 Nougat 的 Genymotion 模拟器上测试了该应用程序。

  • 我尝试访问一个随机的公共(public)端点 (https://jsonplaceholder.typicode.com/todos/1),它运行良好。所以这不是互联网连接的问题。

  • 我在我的 list 上设置了这三个权限:

    android.permission.INTERNET 
    android.permission.ACCESS_NETWORK_STATE
    android.permission.ACCESS_WIFI_STATE
    

这太奇怪了,因为我设置了一个拦截器来将所有请求转换为 cURL 请求,我复制并粘贴了失败的相同请求到 Postman 中并完美运行。

  • 在我的笔记本电脑上,我使用的是 Cisco AnyConnect,在我的 Android 设备上,我使用的是 Cisco AnyConnect 应用程序,在模拟器和 Genymotion 上使用 AFAIK,它应该使用与托管机器相同的网络。

有几个网站只能通过 VPN 访问,我可以在设备和模拟器上看到它们。但是应用程序仍然无法访问端点 URL。

一些奇怪的事情

是的,这变得更奇怪了。如果我通过设备或模拟器中的 Chrome 浏览器访问端点,我会被重定向到登录页面,那是因为我没有发送授权 token 。现在,如果我通过 chrome://inspect 检查网络响应,我可以获得主机的 IP。现在,如果我通过 IP 更改 NetworkModule 的基本 url,并将此行添加到授权拦截器:

@Provides
@Named("authInterceptor")
fun providesAuthInterceptor(
    @Named("authToken") authToken: String
): Interceptor {
    return Interceptor { chain ->
        var request = chain.request()

        request = request.newBuilder()
            .addHeader("Content-Type", "application/json")
            .addHeader("Authorization", "Bearer $authToken")
            .build()
        val response = chain.proceed(request)
        response.newBuilder().code(HttpURLConnection.HTTP_SEE_OTHER).build() // <--- This one
    }
}

然后我开始得到:

11-13 13:56:01.084 4867-4867/com.something.myapp D/GetSomethingUseCase: javax.net.ssl.SSLPeerUnverifiedException: Hostname <BASE IP> not verified:
        certificate: sha256/someShaString
        DN: CN=something.server.net,OU=Title,O=Company Something\, LLC,L=Some City,ST=SomeState,C=SomeCountryCode
        subjectAltNames: [someServer1.com, someServer2.com, someServer3.com, someServer4.com, andSoOn.com]

我不确定这是否无关紧要,或者这是否真的是向前迈出的一步来解决它。

如有任何提示或建议,我们将不胜感激。

谢谢,

最佳答案

关于android - 翻新请求在 VPN 后面抛出 UnknownHostException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53292330/

相关文章:

Android - 使用 Retrofit 获取错误 "Cannot construct instance of (although at least one create exist)"

java - https 连接安卓

android - Flutter中如何从外部存储获取文件?

php imap_open 给出连接问题

Tomcat:在 Windows 上创建本地 keystore 的问题

gzip - 改造: how to parse GZIP'd response without Content-Encoding: gzip header

android - Jenkins phonegap 错误 : Cannot read property 'install' of undefined

java - 无法从异步任务调用第二个 Activity

iphone - 用于 iOS 的带有 openssl 的 libwebsockets lib 在 SSL_CTX_new() 中崩溃

android - retrofit2 为每个 onNext 返回 1 个对象的可观察对象