Search code examples
androidoauthretrofit2okhttpsignpost

Retrofit OAuth signing with okhttp-signpost get OAuthMessageSignerException for special character


I am using Retrofit 2 to make http request in Android App. The server I talk to require OAuth 1.0 Authorization. I use okhttp-signpost from here to handle OAuth signing.

Here's my build.gradle for library included:

compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'se.akerfeldt:okhttp-signpost:1.1.0'
compile 'com.squareup.okhttp3:okhttp:3.0.0-RC1'
compile 'oauth.signpost:signpost-core:1.2.1.2'

In MyApi class, I define dailyChart as Retrofit GET request:

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

@GET("chart")
Call<ChartResponse> dailyChart(@Query("symbol") String symbol);

Here's how I make dailyChart() GET request:

import se.akerfeldt.okhttp.signpost.OkHttpOAuthConsumer;
import se.akerfeldt.okhttp.signpost.SigningInterceptor;

// for OAuth signing
OkHttpOAuthConsumer consumer = new OkHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new SigningInterceptor(consumer))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(.....)
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build();

MyApi myApi = retrofit.create(MyApi.class);

String symbol = '^KKKL';
Call<ChartResponse> call = myApi.dailyChart(symbol);

But I get the below error due to the ^ character in ^KKKL that I passed to retrofit as @Query parameter:

D/OkHttp: --> GET https://......chart?symbol=^KKKL http/1.1
D/OkHttp: --> END GET
D/OkHttp: <-- HTTP FAILED: java.io.IOException: Could not sign request
E/.......: java.io.IOException: Could not sign request
at se.akerfeldt.okhttp.signpost.SigningInterceptor.intercept(SigningInterceptor.java:48)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:211)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185)
at okhttp3.RealCall.execute(RealCall.java:69)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:91)
.......
Caused by: oauth.signpost.exception.OAuthMessageSignerException: java.net.URISyntaxException: Illegal character in query at index ...: https://.....chart?symbol=^KKKL
at oauth.signpost.signature.SignatureBaseString.generate(SignatureBaseString.java:60)
at oauth.signpost.signature.HmacSha1MessageSigner.sign(HmacSha1MessageSigner.java:51)
at oauth.signpost.AbstractOAuthConsumer.sign(AbstractOAuthConsumer.java:109)
at oauth.signpost.AbstractOAuthConsumer.sign(AbstractOAuthConsumer.java:120)
at se.akerfeldt.okhttp.signpost.SigningInterceptor.intercept(SigningInterceptor.java:46)
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67) 
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:211) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67) 
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185) 
at okhttp3.RealCall.execute(RealCall.java:69) 
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180) 
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:91) 
.......
at oauth.signpost.signature.SignatureBaseString.normalizeRequestUrl(SignatureBaseString.java:65)
at oauth.signpost.signature.SignatureBaseString.generate(SignatureBaseString.java:54)
at oauth.signpost.signature.HmacSha1MessageSigner.sign(HmacSha1MessageSigner.java:51) 
at oauth.signpost.AbstractOAuthConsumer.sign(AbstractOAuthConsumer.java:109) 
at oauth.signpost.AbstractOAuthConsumer.sign(AbstractOAuthConsumer.java:120) 
at se.akerfeldt.okhttp.signpost.SigningInterceptor.intercept(SigningInterceptor.java:46) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67) 
at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:211) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) 
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:67) 
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:185) 
at okhttp3.RealCall.execute(RealCall.java:69) 
at retrofit2.OkHttpCall.execute(OkHttpCall.java:180) 
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:91) 

So I tried to url-encode the symbol, before pass in to Retrofit:

String symbol = '^KKKL';
try {
    query = java.net.URLEncoder.encode(symbol, "UTF-8");
} catch (UnsupportedEncodingException ex) {
    throw new StockHistoryNotFoundException(null, ex);
}

Call<ChartResponse> call = myApi.dailyChart(symbol);

Then I got another error as below. I think Retrofit again encode the already encoded @Query parameter I passed in.

D/OkHttp: <-- 404 Not Found https:.....chart?symbol=%255EKKKL

Anyone know any fix for this?


Solution

  • I solved this problem eventually. In Retrofit 2 documentation, there is this optional element called encoded. What it does is:

    encoded

    Specifies whether the parameter name and value are already URL encoded.

    In MyApi class, I change the @Query symbol to use encoded=true:

    import retrofit2.Call;
    import retrofit2.http.GET;
    import retrofit2.http.Query;
    
    @GET("chart")
    Call<ChartResponse> dailyChart(
        @Query(value="symbol", encoded=true) String symbol
    );
    

    In caller code, encode symbol before passing in to dailyChart:

    String symbol = '^KKKL';
    try {
        query = java.net.URLEncoder.encode(symbol, "UTF-8");
    } catch (UnsupportedEncodingException ex) {
        throw new StockHistoryNotFoundException(null, ex);
    }
    
    Call<ChartResponse> call = myApi.dailyChart(symbol);
    

    Then okhttp-signpost no longer complains about special character issue, & Retrofit doesn't double encode the parameter.