Reputation: 864
I am trying to consume a RESTful API using AA. My API receives email and password request parameters (not JSON!) and returns an APIKey
(which I use Jackson2 to deserialize).
Ideally, I want to use a regular old Map<String, String>
to send the email and password, but it appears AA requires me to use a MultiValueMap
(which is a Map<K,List<V>>
), or a custom class (Event
, which has no source shown).
When using a MultiValueMap
, an array
is sent. I am not sending an array of email and passwords, I am sending a single email and password:
// LoginFragment.java
MultiValueMap<String, String> credentials = new LinkedMultiValueMap<String, String>();
credentials.add("email", email);
credentials.add("password", password);
APIKey resp = userRest.login(credentials);
// UserRest.java
@Post("user/login")
public APIKey login(MultiValueMap credentials);
Which trips up my API, because it expects a String rather than an array of Strings.
So I'm thinking I have to create a custom Credentials
object to hold my email and password, and somehow get it serialized to be sent to the server. Could someone help me out with this?
Upvotes: 0
Views: 1671
Reputation: 4418
I know you already figured it out but check out the rest interceptor I use. Basically it attaches an api_key,access_token, and a hmac_sig to each request in the headers. Then you can validate the credientials server side
@EBean(scope = Scope.Singleton)
public class RestInterceptor implements ClientHttpRequestInterceptor {
private int requestCount = 0;
@Pref
MyPrefs_ myPrefs;
private RequestListener mRequestListener;
public interface RequestListener {
void report(int count);
}
public void setOnRequestListener(RequestListener requestListener) {
this.mRequestListener = requestListener;
}
public ClientHttpResponse intercept(HttpRequest request, byte[] data, ClientHttpRequestExecution execution)
throws IOException {
if (mRequestListener != null) {
requestCount++;
mRequestListener.report(requestCount);
}
HttpHeaders headers = request.getHeaders();
long unixTime = System.currentTimeMillis() / 1000L;
headers.add("request_time", String.valueOf(unixTime));
if (myPrefs.accessToken().exists()) {
headers.add("access_token", myPrefs.accessToken().get());
String hmacInput; //left this part out but basically do something unique to the request here and do the same on the other side.
String hmacKey = myPrefs.accessToken().getOr("");
try {
String hmacSig = hmacSha1(hmacInput, hmacKey);
headers.add("hmac_sig", hmacSig);
}
catch (InvalidKeyException e) {
e.printStackTrace();
}
catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
if (myPrefs.userId().exists()) {
headers.add("user_id", String.valueOf(myPrefs.userId().get()));
}
headers.add("api_key", "somerandomstring");
ClientHttpResponse t = execution.execute(request, data);
if (mRequestListener != null) {
requestCount--;
mRequestListener.report(requestCount);
}
return t;
}
public void resetRequestCount() {
this.requestCount = 0;
}
public static String hmacSha1(String value, String key) throws UnsupportedEncodingException,
NoSuchAlgorithmException, InvalidKeyException {
String type = "HmacSHA1";
SecretKeySpec secret = new SecretKeySpec(key.getBytes(), type);
Mac mac = Mac.getInstance(type);
mac.init(secret);
byte[] bytes = mac.doFinal(value.getBytes());
return bytesToHex(bytes);
}
private final static char[] hexArray = "0123456789abcdef".toCharArray();
private static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}
Upvotes: 1
Reputation: 2997
Have you not looked at using the built in Authentication mechanisms that Android Annotations provides? Like Basic Auth or OAuth? This might be a cleaner solution.
https://github.com/excilys/androidannotations/wiki/Authenticated-Rest-Client
I have used the Basic Auth options - https://github.com/excilys/androidannotations/wiki/Rest%20API
You just need to add a method to your interface:
void setHttpBasicAuth(String username, String password);
Then call that before making the API call. There should be a similar option for OAuth.
EDIT: You can create a Login POJO to POST to your API:
@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
"name",
"password"
})
public class Login{
@JsonProperty("name")
private String name;
@JsonProperty("password")
private String password;
}
and then in your API Interface you can do the following:
@Post("user/login")
public APIKey login(Login credentials);
This will then POST your data to the /user/login method. You might need to add an interceptor depending on what kind of data you wish to parse ie converters = { MappingJacksonHttpMessageConverter.class }
etc.
Upvotes: 1