【问题标题】:Android OkHttp with Basic Authentication具有基本身份验证的 Android OkHttp
【发布时间】:2014-04-24 18:21:39
【问题描述】:

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

Retrofit POST request w/ Basic HTTP Authentication: "Cannot retry streamed HTTP body"

但这并没有让我走得太远。 OkHttp github repo 上的示例也没有基于身份验证的示例。有没有人有要点或其他代码示例让我指出正确的方向?感谢您的帮助!

【问题讨论】:

标签: android okhttp


【解决方案1】:

尝试使用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 接口。甲骨文粉丝?
  • 我已经实现了这一点,但我仍然在几乎所有请求上都收到“无法重试流式 HTTP 正文”。是否有另一件事需要调整才能使这项工作正常进行?谢谢
  • OkAuthenticator 链接给出 404 -- square.github.io/okhttp/javadoc/com/squareup/okhttp/…
  • 在 OkHttp 2.0 中重命名为Authenticator
  • Authenticator 的新链接
【解决方案2】:

这是更新后的代码:

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

【讨论】:

  • 这是使用 OkHttp 2.x 的当前答案
【解决方案3】:

上述解决方案有一个缺点: httpClient 仅在收到 401 响应后才添加授权标头。 以下是我与 api-server 的通信方式:

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

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

【讨论】:

【解决方案4】:

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

}

【讨论】:

  • 这个需要顶一下:)
  • 感谢您的跟进!
  • 如何将其插入方法中?我不断收到编译器错误:“无法应用 OKHttpClient 中的身份验证器(匿名 okhttp3.Authenticator)”
  • 啊,我明白了,Authenticator 是一个接口。
  • @nuss +1 never give up 评论 XD
【解决方案5】:

正如@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();

【讨论】:

  • 这非常有效。正是我需要避免多次 401 呼叫。我的 API 要求对所有调用进行身份验证。
  • 这个拦截器有 kotlin 版本吗? @Alphaaa
  • @KBJ 这个拦截器与 OkHttp 一起工作,OkHttp 是一个 Java 库。 HTTP 库通常允许使用拦截器,所以如果你找到一个用于 Kotlin 的,你可以实现类似的东西:)
  • 在我看来,如果所有端点都需要身份验证,或者我们有满足此要求的有限端点列表,我们可以使用 Interceptor 接口。当我们不知道哪些端点需要身份验证时,我们可以使用 Authenticator 接口。当我们需要时,我们可以混合使用这两种方法来避免双重请求(401 -> 200),并避免在我们不需要的地方添加 Authorization 标头(安全问题)。
  • 这不是最有效的方法。原因是,每次我们向服务器发出请求时,都会执行拦截器代码。所以,这样我们将创建一个request.newBuilder()eveytime。其中,应将身份验证器添加到仅限客户端的身份验证器中。
【解决方案6】:

带有 base 64 身份验证的 Okhttp3

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

【讨论】:

  • okhttp3.Credentials.basic(user, pass) 这样做,所以我认为它应该是首选(只是因为它导致需要维护的代码更少)。
  • @francescoforesti 根据文档,这将导致一组挑战/响应请求,如果您不想这样做,这会更好。
  • @Shorn okhttp3.Credentials.basic(user, pass) 不会发出任何请求或更改任何行为,它只是将用户名和密码转换为基本的身份验证字符串。
【解决方案7】:

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

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

如果不添加,会收到Unauthorized消息,会浪费很多时间去修复。

【讨论】:

    【解决方案8】:

    我注意到在带有一些服务器 API(如 django)的 Android 上,您应该在令牌中添加一个词

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

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

    【讨论】:

    • 取决于服务器,当然还有令牌类型,例如 Bearer 令牌值应该类似于 Bearer 6utt8gglitylhylhlfkghriyiuy4fv76876d68
    【解决方案9】:

    有人要求提供 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()
    

    【讨论】:

    • 随着语言和技术的发展,很高兴看到我的问题不断出现新的答案。
    • 拦截器是错误的工具!请改用身份验证器。你会避免很多问题。
    【解决方案10】:

    在 OkHttp3 中,您通过添加 authenticator() 方法来设置 OkHttpClient 本身的授权。在您的原始呼叫返回 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 请求的垃圾邮件,您可以使用一种称为 preauthentication 的方法,在其中发送 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();
    

    【讨论】:

    • HttpHeaders 定义在哪个库中?
    • implementation "com.google.guava:guava:20.0" 但该值只是“授权”字符串
    【解决方案11】:

    这是 OkHttp 客户端的 sn-p:

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

    【讨论】:

      【解决方案12】:

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

      【讨论】:

        猜你喜欢
        • 2020-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-27
        • 2013-03-03
        • 1970-01-01
        • 2016-01-09
        • 1970-01-01
        相关资源
        最近更新 更多