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

spi*_*ce7 25 android http-authentication retrofit okhttp

我正在使用Retrofit来做一个基本的POST请求,我正在为请求提供一个基本的@Body.

@POST("/rest/v1/auth/login")
LoginResponse login(@Body LoginRequest loginRequest);
Run Code Online (Sandbox Code Playgroud)

当我为Retrofit构建界面时,我正在提供我自己的自定义OkHttpClient,而我正在做的就是添加我自己的自定义身份验证:

    @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);
    }
Run Code Online (Sandbox Code Playgroud)

当我使用OKHttp直接发送请求时,以及其他GET请求进行改造,但是当我使用改造来执行POST请求时,我会收到以下错误:

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)
Run Code Online (Sandbox Code Playgroud)

我玩过它.如果我删除身份验证,并指向不需要身份验证的服务器,那么它可以正常工作.

  1. 所以我必须发送信息.
  2. 获取身份验证质询请求.
  3. 响应挑战请求.
  4. 尝试再次重新发送请求,然后抛出错误.

不知道怎么解决这个问题.任何帮助都会很精彩.

Fer*_*nch 39

谢谢,杰西.

万一它有帮助,这是我为Basic auth做的代码.

首先,在MyApplication课堂上的init :

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);
Run Code Online (Sandbox Code Playgroud)

然后是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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Jac*_*ień 15

延伸Naren的答案:

String像这样构建auth :

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

然后你basicAuth转到服务authorization.

@GET("/user") 
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
Run Code Online (Sandbox Code Playgroud)

  • 如果你需要为*all*传出请求添加相同的凭据,那么在你的Retrofit接口上添加`@Header("Authorization")并不是很好,是吗? (3认同)

Jes*_*son 14

您最好的办法是通过RequestInterceptor而不是OkHttp 将您的凭据提供给Retrofit OkAuthenticator.当请求可以重试时,该接口效果最好,但在您的情况下,我们已经在我们发现必要时已经抛出了帖子.

您可以继续使用OkAuthenticator的Credential类,它可以按要求的格式对您的用户名和密码进行编码.您想要的标题名称是Authorization.


Nar*_*ren 6

对于基本授权,您可以提供如下标题:

@GET("/user")
void getUser(@Header("Authorization") String authorization, Callback<User> callback)
Run Code Online (Sandbox Code Playgroud)