基本认证的 Android OkHttp

我正在使用 OkHttp 库开发一个新项目,它的易用性给我留下了深刻的印象。我现在需要使用基本身份验证。不幸的是,缺乏可用的示例代码。我正在寻找一个示例,说明如何在遇到 HTTP 401头时将用户名/密码凭据传递给 OkAuthenticator。我看到了这个答案:

改进 POST 请求 w/Basic HTTP Authentication: “无法重试流式 HTTP 主体”

但是没有让我走得太远。回购上的示例也没有基于身份验证的示例。是否有人有一个要点或其他代码示例,让我指向正确的方向?谢谢你的帮助!

102552 次浏览

Try using 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;
}
});

UPDATE:

Renamed to Authenticator

Here's the updated 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;
}
})

The aforementioned solution has one drawback: httpClient adds authorization headers only after receiving 401 response. Here's how my communication with api-server looked like: enter image description here

If you need to use basic-auth for every request, better add your auth-headers to each request or use a wrapper method like this:

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();
}

Update Code for okhttp3:

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;
}


}

As pointed out by @agamov:

The aforementioned solution has one drawback: httpClient adds authorization headers only after receiving 401 response

@agamov proposed then to "manually" add authentication headers to each request, but there is a better solution: use an 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);
}


}

Then, simply add the interceptor to an OkHttp client that you will be using to make all your authenticated requests:

OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new BasicAuthInterceptor(username, password))
.build();

Okhttp3 with 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() {
...

All answers are good but no one said, that for some requests content-type is required, you should add a content-type to your request like this:

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

If you don't add it, you will get Unauthorized message and you will waste a lot of time to fix it.

I noticed on Android with some server APIs like django you should add a word in token

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

, where that problematic word is that "Token ". Overall you should carefully see rules of those specific server APIs about how to compose requests.

Someone asked for a Kotlin version of the interceptor. Here is what I came up with and it works great:

        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()

In OkHttp3, you set the authorization on the OkHttpClient itself by adding the authenticator() method. After your original call comes back with the 401 response, the authenticator() adds the Authorization header

 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();

Although it's more secure, if you don't want to spam the server with all the 401 requests in the first place, you can use something called preauthentication, where you send the Authorization header to begin with on your requests

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

This is a snippet for 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();

Do a request now. Basic auth will go as client already has that.

    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());


}
});

In my case it only worked when I integrated authorization into the header (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();

You can try this code for a no frills approach.

    String credentials = username + ":" + password;


final String basic = "Basic " +
java.util.Base64.getEncoder().encodeToString(credentials.getBytes());


Request request = new Request.Builder().header("Authorization",
basic).url(connectString).post(body)