改装请求将UnknownHostException抛在VPN后面

时间:2018-11-14 02:27:17

标签: android ssl retrofit2 vpn unknown-host

所以我遇到了一个问题,它似乎是从暮光区直接出来的。

问题

我必须从后端访问REST API端点,但事实是,要达到该端点,我需要通过VPN。否则主机将无法访问。在桌面上,一切正常,我打开Postman,点击GET端点并获得响应。但是,当我尝试通过我的Android设备访问同一终结点时,Retrofit会引发UnknownHostException。

上下文

端点URL类似于https://api.something.something.net/。我正在使用Dagger进行依赖项注入,因此我的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 MyRetrofitApi {

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

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

到目前为止我尝试过的事情

  • 我改用了Volley的Retrofit,后来改用了Ion,只是为了确保这与其余客户无关。在所有情况下,我都有相同的例外情况:

java.net.UnknownHostException: Unable to resolve host "api.something.something.net": No address associated with hostname

com.android.volley.NoConnectionError: java.net.UnknownHostException: Unable to resolve host "api.something.something.net": No address associated with hostname

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

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

  • 在清单上,我将android:networkSecurityConfig设置为:

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

  • 我在自己的Android设备(Android牛轧糖)上,牛轧糖,Marshmellow和Oreo的模拟器以及牛轧糖的Genymotion模拟器上测试了该应用程序。

  • 我尝试打了一个随机的公共端点(https://jsonplaceholder.typicode.com/todos/1),并且运行良好。因此,这与互联网连接无关。

  • 我在清单上设置了以下三个权限:

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

这太奇怪了,因为我设置了一个Interceptor来将所有请求转换为cURL请求,我将失败的同一请求复制并粘贴到Postman中,并且运行良好。

  • 在笔记本电脑上,我正在使用Cisco AnyConnect,在Android设备上,我正在仿真器上使用Cisco AnyConnect App和AFAIK,Genymotion应该使用与主机相同的网络。

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

奇怪的事物

是的,这很奇怪。如果我通过设备或仿真器中的Chrome浏览器访问端点,则会重定向到登录页面,这是因为我没有发送授权令牌。现在,如果我通过chrome:// inspect检查网络响应,则可以获取主机的IP。现在,如果我通过IP更改NetworkModule的基本URL,并将此行添加到授权Interceptor中:

@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]

我不确定这是否无关紧要,或者实际上是否是解决它的下一步。

任何提示或建议都值得赞赏。

谢谢

2 个答案:

答案 0 :(得分:0)

我认为此问题可能与ssl证书的有效性有关。为了解决该问题,您应该在HttpClient中设置setSSLSocketFactory。

  private SSLConnectionSocketFactory getSSLSocketFactory() {
  KeyStore trustStore;
  try {
    trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(null, null);
    TrustStrategy trustStrategy = new TrustStrategy() {
        @Override
        public boolean isTrusted(X509Certificate[] chain, String authType) 
    throws CertificateException {
            return true;
        }

    };

    SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
    sslContextBuilder.loadTrustMaterial(trustStore, trustStrategy);
    sslContextBuilder.useTLS();
    SSLContext sslContext = sslContextBuilder.build();
    SSLConnectionSocketFactory sslSocketFactory = new 
    SSLConnectionSocketFactory(sslContext);
    return sslSocketFactory;
       } catch (GeneralSecurityException | IOException e) {
    System.err.println("SSL Error : " + e.getMessage());
      }
   return null;
  }

 HttpClientBuilder.create().setSSLSocketFactory(getSSLSocketFactory())
 .build();

答案 1 :(得分:0)