webentwicklung-frage-antwort-db.com.de

Nachrüstung POST request mit Basic HTTP-Authentifizierung: "Gestreamter HTTP-Body kann nicht wiederholt werden"

Ich benutze Retrofit, um eine einfache POST - Anforderung auszuführen, und ich gebe einen grundlegenden @Body für die Anforderung an.

@POST("/rest/v1/auth/login")
LoginResponse login(@Body LoginRequest loginRequest);

Wenn ich die Schnittstelle für Retrofit baue, stelle ich meinen eigenen benutzerdefinierten OkHttpClient zur Verfügung. Alles, was ich dazu tue, ist das Hinzufügen meiner eigenen benutzerdefinierten Authentifizierung:

    @Provides
    @Singleton
    public Client providesClient() {
        OkHttpClient httpClient = new OkHttpClient();

        httpClient.setAuthenticator(new OkAuthenticator() {
            @Override
            public Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
                return getCredential();
            }

            @Override
            public Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
                return getCredential();
            }
        });

        return new OkClient(httpClient);
    }

Dies funktioniert hervorragend, wenn ich Anforderungen direkt mit OKHttp und andere GET-Anforderungen mit Retrofit sende. Wenn ich jedoch Retrofit für die Anforderung von POST verwende, erhalte ich die folgende Fehlermeldung:

Caused by: Java.net.HttpRetryException: Cannot retry streamed HTTP body
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.Java:324)
            at com.squareup.okhttp.internal.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.Java:508)
            at com.squareup.okhttp.internal.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.Java:136)
            at retrofit.client.UrlConnectionClient.readResponse(UrlConnectionClient.Java:94)
            at retrofit.client.UrlConnectionClient.execute(UrlConnectionClient.Java:49)
            at retrofit.RestAdapter$RestHandler.invokeRequest(RestAdapter.Java:357)
            at retrofit.RestAdapter$RestHandler.invoke(RestAdapter.Java:282)
            at $Proxy3.login(Native Method)
            at com.audax.paths.job.LoginJob.onRunInBackground(LoginJob.Java:41)
            at com.audax.library.job.AXJob.onRun(AXJob.Java:25)
            at com.path.Android.jobqueue.BaseJob.safeRun(BaseJob.Java:108)
            at com.path.Android.jobqueue.JobHolder.safeRun(JobHolder.Java:60)
            at com.path.Android.jobqueue.executor.JobConsumerExecutor$JobConsumer.run(JobConsumerExecutor.Java:172)
            at Java.lang.Thread.run(Thread.Java:841)

Ich habe damit rumgespielt. Wenn ich die Authentifizierung entferne und auf einen Server zeige, für den keine Authentifizierung erforderlich ist, funktioniert er problemlos. 

  1. Also muss ich die Informationen senden.
  2. Anforderung der Authentifizierungsanfrage abrufen.
  3. Antworten auf die Herausforderungsanfrage.
  4. Wenn Sie versuchen, die Anforderung erneut zu senden, wird der Fehler ausgegeben.

Nicht sicher, wie man das umgehen kann. Jede Hilfe wäre wunderbar.

25
spierce7

Am besten geben Sie Ihre Nachweise für Retrofit über einen RequestInterceptor anstelle von OkHttps OkAuthenticator ein. Diese Schnittstelle funktioniert am besten, wenn die Anforderung erneut versucht werden kann. In Ihrem Fall haben wir den Post-Body jedoch bereits verworfen, wenn wir herausfinden, dass dies erforderlich ist.

Sie können weiterhin die Credential-Klasse von OkAuthenticator verwenden, die Ihren Benutzernamen und Ihr Kennwort im erforderlichen Format kodieren kann. Der Name des Headers ist Authorization.

14
Jesse Wilson

Danke, Jesse.

Für den Fall, dass es hilft, hier ist der Code, den ich für Basic auth gemacht habe.

Zuerst die Init in MyApplication Klasse:

ApiRequestInterceptor requestInterceptor = new ApiRequestInterceptor();
requestInterceptor.setUser(user); // I pass the user from my model

ApiService apiService = new RestAdapter.Builder()
            .setRequestInterceptor(requestInterceptor)
            .setServer(Constants.API_BASE_URL)
            .setClient(new OkClient()) // The default client didn't handle well responses like 401
            .build()
            .create(ApiService.class);

Und dann der ApiRequestInterceptor:

import Android.util.Base64;
import retrofit.RequestInterceptor;

/**
 * Interceptor used to authorize requests.
 */
public class ApiRequestInterceptor implements RequestInterceptor {

    private User user;

    @Override
    public void intercept(RequestFacade requestFacade) {

        if (user != null) {
            final String authorizationValue = encodeCredentialsForBasicAuthorization();
            requestFacade.addHeader("Authorization", authorizationValue);
        }
    }

    private String encodeCredentialsForBasicAuthorization() {
        final String userAndPassword = user.getUsername() + ":" + user.getPassword();
        return "Basic " + Base64.encodeToString(userAndPassword.getBytes(), Base64.NO_WRAP);
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}
39
Ferran Maylinch

Naren Antwort erweitern:

Sie bauen auth String so auf:

String basicAuth = "Basic " + Base64.encodeToString(String.format("%s:%s", "your_user_name", "your_password").getBytes(), Base64.NO_WRAP);

Und dann übergeben Sie basicAuth als authorization an den Dienst.

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
14
Jacek Kwiecień

Für die Basisberechtigung können Sie einen Header wie folgt angeben:

@GET("/user")
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
6
Naren

Wenn Sie dies mit der neuesten Version von Retrofit/OkHttp tun, reichen die aktuellen Lösungen nicht aus. Retrofit bietet keinen RequestInterceptor mehr an, daher müssen Sie die Interceptors von OkHttp verwenden, um eine ähnliche Aufgabe zu erfüllen:

Erstellen Sie Ihren Interceptor:

public class HttpAuthInterceptor implements Interceptor {
  private String httpUsername;
  private String httpPassword;

  public HttpAuthInterceptor(String httpUsername, String httpPassword) {
    this.httpUsername = httpUsername;
    this.httpPassword = httpPassword;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    Request newRequest = chain.request().newBuilder()
        .addHeader("Authorization", getAuthorizationValue())
        .build();

    return chain.proceed(newRequest);
  }

  private String getAuthorizationValue() {
    final String userAndPassword = "httpUsername" + ":" + httpPassword;
    return "Basic " + Base64.encodeToString(userAndPassword.getBytes(), Base64.NO_WRAP);
  }
}

Sie müssen den Interceptor zu Ihrem OkHttp-Client hinzufügen:

// Create your client
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new HttpAuthInterceptor("httpUsername", "httpPassword"))
    .build();

// Build Retrofit with your client
Retrofit retrofit = new Retrofit.Builder()
    .client(client)
    .build();

// Create and use your service that now authenticates each request.
YourRetrofitService service = retrofit.create(YourRetrofitService.class);

Ich habe den obigen Code nicht getestet, daher müssen möglicherweise geringfügige Änderungen vorgenommen werden. Ich programmiere in Kotlin für Android jetzt.

1
spierce7