具有基本身份验证的Android OkHttp


70

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

使用基本HTTP身份验证改进POST请求:“无法重试流式HTTP正文”

但这并没有使我走得太远。OkHttp github存储库上的示例也没有基于身份验证的示例。有没有人有要点或其他代码示例来指导我正确的方向?感谢你的协助!


询问有关此要实现github.com/square/okhttp/issues/2143
波莫

Answers:


39

尝试使用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;
  }
});

更新:

重命名为Authenticator


杰西-感谢您的迅速回答。现在,我需要实现OkAuthenticator接口。甲骨文粉丝?
科尔2014年

我已经实现了这一点,但几乎所有请求都仍然收到“无法重试流HTTP正文”的信息。要进行这项工作还有其他要调整的内容吗?谢谢
cottonBallPaws 2014年


12
在OkHttp 2.0中重命名为Authenticator
杰西·威尔逊


98

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

}

8
这需要转到顶部:)
Ionut's

感谢您的跟进!
Kerr

2
您如何将其插入方法中?我一直收到编译器错误:“无法应用OKHttpClient中的身份验证器(匿名okhttp3.Authenticator)”
Mike6679

1
嗯,我知道,身份验证器是一个接口。
Mike6679 '16

8
@nuss +1进行never give up评论XD
香蕉

58

正如@agamov指出的那样:

上述解决方案有一个缺点:httpClient仅在收到401响应后才添加授权标头

@agamov建议然后“手动”向每个请求添加身份验证标头,但是有一个更好的解决方案:使用 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);
    }

}

然后,只需将拦截器添加到您将用于发出所有已认证请求的OkHttp客户端:

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

2
这很完美。正是我需要避免多次401调用。我的API要求对所有调用进行身份验证。
Hackmodford

该拦截器有kotlin版本吗?@Alphaaa
KBJ

@KBJ此拦截器与Java库OkHttp一起使用。HTTP库通常允许与拦截器一起使用,因此,如果您为Kotlin找到一个拦截器,则可以实现类似于以下内容的方法:)
Alphaaa

我认为,如果所有端点都需要身份验证,或者我们有一个满足此要求的端点的有限列表,则可以使用Interceptor接口。当我们不知道到底哪些端点需要身份验证时,可以使用Authenticator接口。当我们需要时,我们可以混合使用这两种方法来避免重复请求(401-> 200),并避免在不需要的地方添加Authorization标头(安全性问题)。
ultraon

54

这是更新的代码:

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

2
这是使用OkHttp 2.x的当前答案
IanTimmis

38

前述解决方案具有一个缺点:httpClient仅在收到401响应后才添加授权标头。这是我与api-server的通讯样子: 在此处输入图片说明

如果您需要为每个请求使用basic-auth,则最好将auth-header添加到每个请求中,或使用如下的包装方法:

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

接得好。我写了一个基于您的改进的答案:stackoverflow.com/a/36056355/2091700
Alphaaa

8

Okhttp3具有base 64身份验证

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

4
okhttp3.Credentials.basic(user, pass)这样做,所以我认为应该首选(只是因为这样会减少维护的代码)。
francesco foresti

@francescoforesti根据doco的说法,这将导致一系列质询/响应请求,如果您不想这样做,那会更好。
Shorn

3
@Shornokhttp3.Credentials.basic(user, pass)不会发出任何请求或更改任何行为,它只是将用户名和密码转换为基本的身份验证字符串。
FrederikNS

5

就我而言,仅当我将授权集成到标头(OkHttp版本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();

4

有人要求提供Kotlin版本的拦截器。这是我想出的,效果很好:

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

很高兴看到随着语言和技术的发展,新的答案不断出现在我的问题上。
Kerr

拦截器是错误的工具!请改用Authenticator。您将避免许多问题。
戴维德·海伊

4

在OkHttp3中,您可以OkHttpClient通过添加authenticator()方法来对自身设置授权。在您的原始呼叫返回401响应后,the authenticator()添加Authorization标题

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

尽管它更安全,但是如果您不想首先对所有401请求进行垃圾邮件处理,则可以使用一种称为“预身份验证”的方法,在该方法中发送 Authorization请求

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

3

我注意到在Android上使用某些服务器API(例如django)时,您应该在令牌中添加一个单词

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

,其中有问题的词是“令牌”。总体而言,您应该仔细查看那些特定服务器API的规则,以了解如何撰写请求。


取决于服务器,当然也取决于令牌类型,例如,Bearer令牌值应类似于Bearer 6utt8gglitylhylhlfkghriyiuy4fv76876d68
Ahmet Gokdayi

2

所有答案都很好,但是没有人说,对于某些请求,需要content-type,您应该像这样向您的请求添加content-type:

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

如果不添加它,则会收到未授权消息,并且会浪费大量时间来修复它。


0

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

立即提出要求。基本身份验证将继续进行,因为客户端已经拥有了。

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

                }
            });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.