簡體   English   中英

改造請求在VPN后面拋出UnknownHostException

[英]Retrofit request throwing UnknownHostException behind VPN

因此,我遇到了一個問題,它似乎直接來自“暮光區”。

問題

我必須從后端訪問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到true和false。 設置hostnameVerifier以允許任何主機名通過。 設置SSLSocketFactory以允許任何未簽名的證書通過。

  • 在清單上,我將android:networkSecurityConfig設置為:

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

  • 我在Android設備(Android Nougat),帶有Nougat,Marshmellow和Oreo的模擬器以及帶有Nougat的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應用程序和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]

我不確定這是否無關,或者實際上是否是解決該問題的第一步。

任何提示或建議,不勝感激。

謝謝,

我認為此問題可能與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();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM