Android OkHttp mit Standardauthentifizierung

70

Ich verwende die OkHttp-Bibliothek für ein neues Projekt und bin beeindruckt von der Benutzerfreundlichkeit. Ich muss jetzt die Standardauthentifizierung verwenden. Leider mangelt es an funktionierendem Beispielcode. Ich suche ein Beispiel für die Übergabe von Benutzernamen- / Kennwortanmeldeinformationen an den OkAuthenticator, wenn ein HTTP 401-Header gefunden wird. Ich habe diese Antwort angesehen:

POST-Anforderung mit einfacher HTTP-Authentifizierung nachrüsten: "Gestreamter HTTP-Body kann nicht wiederholt werden"

aber es hat mich nicht zu weit gebracht. Die Samples auf dem OkHttp Github Repo enthielten auch kein authentifizierungsbasiertes Sample. Hat jemand einen Kern oder ein anderes Codebeispiel, um mich in die richtige Richtung zu lenken? Danke für deine Hilfe!

Kerr
quelle
Bitten Sie darum, dass dies implementiert wird github.com/square/okhttp/issues/2143
pomo

Antworten:

39

Versuchen Sie es mit OkAuthenticator :

client.setAuthenticator(new OkAuthenticator() {
  @Override public Credential authenticate(
      Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
    return Credential.basic("scott", "tiger");
  }

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

AKTUALISIEREN:

In Authenticator umbenannt

Jesse Wilson
quelle
Jesse - Danke für die schnelle Antwort. Ich sehe jetzt, dass ich die OkAuthenticator-Schnittstelle implementieren muss. Oracle-Fan?
Kerr
Ich habe dies implementiert, erhalte jedoch bei fast allen Anforderungen immer noch die Meldung "Gestreamter HTTP-Text kann nicht wiederholt werden". Gibt es noch etwas zu optimieren, damit dies funktioniert? Danke
CottonBallPaws
OkAuthenticator Link geben eine 404 - square.github.io/okhttp/javadoc/com/squareup/okhttp/…
Daniele Segato
12
In OkHttp 2.0 in Authenticator umbenannt.
Jesse Wilson
4
Neuer Link für Authenticator
Evan Knowles
98

Code für okhttp3 aktualisieren:

import okhttp3.Authenticator;
import okhttp3.Credentials;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;

public class NetworkUtil {

private final OkHttpClient.Builder client;

{
    client = new OkHttpClient.Builder();
    client.authenticator(new Authenticator() {
        @Override
        public Request authenticate(Route route, Response response) throws IOException {
            if (responseCount(response) >= 3) {
                return null; // If we've failed 3 times, give up. - in real life, never give up!!
            }
            String credential = Credentials.basic("name", "password");
            return response.request().newBuilder().header("Authorization", credential).build();
        }
    });
    client.connectTimeout(10, TimeUnit.SECONDS);
    client.writeTimeout(10, TimeUnit.SECONDS);
    client.readTimeout(30, TimeUnit.SECONDS);
}

private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
        result++;
    }
    return result;
}

}
nuss
quelle
8
Dies muss nach oben gehen :)
Ionut
Vielen Dank für das Follow-up!
Kerr
2
Wie fügt man dies in eine Methode ein? Ich erhalte immer wieder den Compilerfehler: "Authentifikator in OKHttpClient kann nicht angewendet werden (anonym okhttp3.Authenticator)"
Mike6679
1
ahhh ich verstehe, Authenticator ist eine Schnittstelle.
Mike6679
8
@nuss +1 für den never give upKommentar XD
Banana
58

Wie von @agamov hervorgehoben:

Die oben genannte Lösung hat einen Nachteil: httpClient fügt Autorisierungsheader erst nach Erhalt der 401-Antwort hinzu

@agamov schlug dann vor, jeder Anforderung "manuell" Authentifizierungsheader hinzuzufügen, aber es gibt eine bessere Lösung: Verwenden Sie eine Interceptor:

import java.io.IOException;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

public class BasicAuthInterceptor implements Interceptor {

    private String credentials;

    public BasicAuthInterceptor(String user, String password) {
        this.credentials = Credentials.basic(user, password);
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request authenticatedRequest = request.newBuilder()
                    .header("Authorization", credentials).build();
        return chain.proceed(authenticatedRequest);
    }

}

Fügen Sie dann einfach den Interceptor zu einem OkHttp-Client hinzu, mit dem Sie alle Ihre authentifizierten Anforderungen stellen:

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new BasicAuthInterceptor(username, password))
    .build();
Alphaaa
quelle
2
Das funktioniert perfekt. Genau das, was ich brauchte, um mehrere 401-Anrufe zu vermeiden. Meine API erfordert die Authentifizierung aller Aufrufe.
Hackmodford
Gibt es eine Kotlin-Version dieses Interceptors? @Alphaaa
KBJ
@KBJ Dieser Interceptor funktioniert mit OkHttp, einer Java-Bibliothek. HTTP-Bibliotheken ermöglichen normalerweise die Arbeit mit Interceptors. Wenn Sie also einen für Kotlin finden, können Sie etwas Ähnliches implementieren :)
Alphaaa
Meiner Meinung nach können wir die Interceptor-Schnittstelle verwenden, wenn alle Endpunkte eine Authentifizierung erfordern oder wenn wir eine endliche Liste von Endpunkten mit dieser Anforderung haben. Wenn wir nicht genau wissen, welche Endpunkte eine Authentifizierung erfordern, können wir die Authenticator-Schnittstelle verwenden. Bei Bedarf können wir diese beiden Ansätze mischen, um doppelte Anforderungen (401 -> 200) zu vermeiden und das Hinzufügen von Autorisierungsheadern an Stellen zu vermeiden, an denen wir sie nicht benötigen (Sicherheitsproblem).
Ultraon
54

Hier ist der aktualisierte Code:

client.setAuthenticator(new Authenticator() {
  @Override
  public Request authenticate(Proxy proxy, Response response) throws IOException {
    String credential = Credentials.basic("scott", "tiger");
    return response.request().newBuilder().header("Authorization", credential).build();
  }

  @Override
  public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
    return null;
  }
})
David Gageot
quelle
2
Dies ist die aktuelle Antwort mit OkHttp 2.x
IanTimmis
38

Die oben genannte Lösung hat einen Nachteil: httpClient fügt Autorisierungsheader erst nach Erhalt der 401-Antwort hinzu. So sah meine Kommunikation mit dem API-Server aus: Geben Sie hier die Bildbeschreibung ein

Wenn Sie Basic-Auth für jede Anforderung verwenden müssen, fügen Sie Ihre Auth-Header besser zu jeder Anforderung hinzu oder verwenden Sie eine Wrapper-Methode wie die folgende:

private Request addBasicAuthHeaders(Request request) {
    final String login = "your_login";
    final String password = "p@s$w0rd";
    String credential = Credentials.basic(login, password);
    return request.newBuilder().header("Authorization", credential).build();
}
Agamov
quelle
Guter Fang. Ich habe eine verbesserte Antwort geschrieben, die sich aus Ihrer ergibt
Alphaaa
8

Okhttp3 mit Base 64 Auth

String endpoint = "https://www.example.com/m/auth/"
String username = "user123";
String password = "12345";
String credentials = username + ":" + password;

final String basic =
        "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
Request request = new Request.Builder()
        .url(endpoint)
        .header("Authorization", basic)
        .build();


OkHttpClient client = SomeUtilFactoryClass.buildOkhttpClient();
client.newCall(request).enqueue(new Callback() {
...
Rangierer
quelle
4
okhttp3.Credentials.basic(user, pass)tut dies, also denke ich, dass es bevorzugt werden sollte (nur weil es zu weniger Code führt, um es zu erhalten).
Franceco Foresti
@francescoforesti Laut dem Dokument führt dies zu einer Reihe von Herausforderungs- / Antwortanforderungen. Dies ist besser, wenn Sie dies nicht möchten.
Geschoren
3
@Shorn okhttp3.Credentials.basic(user, pass)stellt keine Anforderungen oder ändert kein Verhalten, sondern konvertiert einfach den Benutzernamen und das Kennwort in eine grundlegende Authentifizierungszeichenfolge.
FrederikNS
4

Jemand fragte nach einer Kotlin-Version des Abfangjägers. Folgendes habe ich mir ausgedacht und es funktioniert großartig:

        val client = OkHttpClient().newBuilder().addInterceptor { chain ->
        val originalRequest = chain.request()

        val builder = originalRequest.newBuilder()
                .header("Authorization", Credentials.basic("ausername", "apassword"))
        val newRequest = builder.build()
        chain.proceed(newRequest)
    }.build()
Rich Gabrielli
quelle
Es ist schön zu sehen, dass meine Frage immer wieder beantwortet wird, wenn sich Sprachen und Techniken weiterentwickeln.
Kerr
Interceptor ist das falsche Werkzeug dafür! Verwenden Sie stattdessen Authenticator. Sie werden viele Probleme vermeiden.
Dawid Hyży
4

In meinem Fall hat es nur funktioniert, wenn ich die Autorisierung in den Header integriert habe (OkHttp Version 4.0.1):

Request request = new Request.Builder()
    .url("www.url.com/api")
    .addHeader("Authorization", Credentials.basic("username", "password"))
    .build();

Request response = client.newCall(request).execute();
Codev
quelle
3

Ich habe auf Android mit einigen Server-APIs wie Django bemerkt, dass Sie ein Wort in Token hinzufügen sollten

Request request = new Request.Builder()
    .url(theUrl)
    .header("Authorization", "Token 6utt8gglitylhylhlfkghriyiuy4fv76876d68")
    .build();

, wo dieses problematische Wort das "Token" ist. Insgesamt sollten Sie die Regeln dieser spezifischen Server-APIs zum Erstellen von Anforderungen sorgfältig lesen.

CodeToLife
quelle
Hängt vom Server und natürlich vom Token-Typ ab, zum Beispiel sollte ein Bearer-Token-Wert so etwas wieBearer 6utt8gglitylhylhlfkghriyiuy4fv76876d68
Ahmet Gokdayi
3

In OkHttp3 legen Sie die Berechtigung für sich OkHttpClientselbst fest, indem Sie die authenticator()Methode hinzufügen . Nachdem Ihr ursprünglicher Anruf mit der Antwort 401 zurückkommt, wird the authenticator()der AuthorizationHeader hinzugefügt

 new OkHttpClient.Builder()
        .connectTimeout(10000, TimeUnit.MILLISECONDS)
        .readTimeout(10000, TimeUnit.MILLISECONDS)
        .authenticator(new Authenticator() {
           @Nullable
           @Override
           public Request authenticate(@NonNull Route route, @NonNull Response response) {
             if (response.request().header(HttpHeaders.AUTHORIZATION) != null)
               return null;  //if you've tried to authorize and failed, give up

             String credential = Credentials.basic("username", "pass");
             return response.request().newBuilder().header(HttpHeaders.AUTHORIZATION, credential).build();
          }
        })
        .build();

Obwohl es sicherer ist, können Sie, wenn Sie den Server überhaupt nicht mit allen 401-Anforderungen als Spam versenden möchten, eine sogenannte Vorauthentifizierung verwenden, bei der Sie den AuthorizationHeader zunächst für Ihre Anforderungen senden

String credentials = Credentials.basic("username", "password");
Request httpRequest = new Request.Builder()
                 .url("some/url")
                 .header("content-type", "application/json") 
                 .header(HttpHeaders.AUTHORIZATION, credentials)
                 .build();
Seekatze
quelle
2

Alle Antworten sind gut, aber niemand sagte, dass für einige Anfragen der Inhaltstyp erforderlich ist. Fügen Sie Ihrer Anfrage einen Inhaltstyp wie folgt hinzu:

Request request = new Request.Builder()
        .url(url)
        .addHeader("content-type", "application/json") 
        .post(body)
        .build();

Wenn Sie es nicht hinzufügen, erhalten Sie eine nicht autorisierte Nachricht und verschwenden viel Zeit, um es zu beheben.

Nosov Pavel
quelle
0

Dies ist ein Ausschnitt für OkHttp Client:

  OkHttpClient client = new OkHttpClient.Builder()
               .authenticator(new Authenticator() {
              @Override public Request authenticate(Route route, Response 
   response) throws IOException {
                   if (response.request().header("Authorization") != null) {
                      return null; // Give up, we've already attempted to 
   authenticate.
                   }

                  System.out.println("Authenticating for response: " + response);
                  System.out.println("Challenges: " + response.challenges());
                   String credential = Credentials.basic(username, password);
                   return response.request().newBuilder()
                           .header("Authorization", credential)
                           .build();
               }
           }) .build(); 

Machen Sie jetzt eine Anfrage. Die Basisauthentifizierung wird ausgeführt, da der Client dies bereits hat.

    Request request = new Request.Builder().url(JIRAURI+"/issue/"+key).build();
                client.newCall(request).enqueue(new Callback() {
                    @Override
                   public void onFailure(Call call, IOException e) {
                       System.out.println("onFailure: "+e.toString());
                    }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    System.out.println( "onResponse: "+response.body().string());

                }
            });
Vayuj Rajan
quelle