Following implementation details will be more described in paragraphs below.
- How to access HttpServletRequest instance within the API methods
- How to calculate HMAC in java
- How to read http authorization header by using HttpServletRequest instance
- How to set HMAC in authorization header by using Google Cloud Endpoint client library for Android
System Requirement
Android app send a name of a user. Server should reply with Hi, <name of user>. For example, if app send nipun it should reply with Hi, Nipun. Aforementioned requirement already implemented when Google Cloud Endpoint module is added with Android Studio. Codes from Reference [1] is on content of this post.User Interface
Android client UI has a Button and a EditText. EditText has the id nameUiValue. nameUiValue has been assigned to
private EditText nameEditText;
When a user click on the button it calls the method.
public void sendName(View view)
Before securing the
APIs Endpoint class and client AsyncTask class is show below.
Cloud
MyEndpoint.java
/* For step-by-step instructions on connecting your Android application to this backend module,
see "App Engine Java Endpoints Module" template documentation at
https://github.com/GoogleCloudPlatform/gradle-appengine-templates/tree/master/HelloEndpoints*/ package com.example.nipun.myapplication.backend; import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest; /** * An endpoint class we are exposing */@Api(name = "myApi", version = "v1",
namespace = @ApiNamespace(ownerDomain = "backend.myapplication.nipun.example.com",
ownerName = "backend.myapplication.nipun.example.com", packagePath = "")) public class MyEndpoint { /** * A simple endpoint method that takes a name and says Hi back */
@ApiMethod(name = "sayHi") public MyBean sayHi(HttpServletRequest request, @Named("name") String name) { MyBean response = new MyBean(); response.setData("Hi, " + name); return response; } }
Android Client AsyncTask
class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> { private MyApi myApiService = null; private Context context; @Override protected String doInBackground(Pair<Context, String>... params) { if(myApiService == null) { MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null) .setRootUrl("http://10.0.2.2:8080/_ah/api/") .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() { @Override
public void initialize(AbstractGoogleClientRequest<?>
abstractGoogleClientRequest) throws IOException { abstractGoogleClientRequest.setDisableGZipContent(true);
} }); myApiService = builder.build(); } context = params[0].first; String name = params[0].second; try { return myApiService.sayHi(name).execute().getData(); } catch (IOException e) { return e.getMessage(); } } @Override protected void onPostExecute(String result) { Toast.makeText(context, result, Toast.LENGTH_LONG).show(); } }
Securing Google Cloud Endpoint With HMAC
How to access HttpServletRequest instance within the API methods
In order to get the HMAC value from http authorization header HttpServletRequest must be accessed from the API method. It can be done by using following way.
public MyBean sayHi(HttpServletRequest request, @Named("name") String name)
ApiMethod has included HttpServletRequest as an parameter.
How to calculate HMAC in java
Hmac has been generated according to reference 3 and 4. A new hmac is calculated by using at the endpoint by using AuthManager.java class.
package com.example.nipun.myapplication.backend.auth; import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec; /** * Created by nipun on 7/25/15. */public class AuthManager { private String KEY = "E34swDN1Fk1G44zmw9bL2N5B8R85w290"; private String ALGO = "HmacSHA1"; public boolean authenticationSuccessful(String dataForHmac, String authHeader){ if (authHeader == null) return false; String tempHmacParts[] = authHeader.split(" "); if (tempHmacParts.length != 2) return false; tempHmacParts = tempHmacParts[1].split(":"); if (tempHmacParts.length != 2) return false; if (tempHmacParts[1].equals(hmacDigest(dataForHmac, KEY, ALGO))){ return true; }else { return false; } } /* * * This method is taken from
* http://www.supermind.org/blog/1102/generating-hmac-md5-sha1-sha256-etc-in-java
*/
private String hmacDigest(String msg, String keyString, String algo) { String digest = null; try { SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo);
Mac mac = Mac.getInstance(algo); mac.init(key); byte[] bytes = mac.doFinal(msg.getBytes("ASCII")); StringBuffer hash = new StringBuffer();
for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) { hash.append('0'); } hash.append(hex); } digest = hash.toString(); } catch (UnsupportedEncodingException e) { } catch (InvalidKeyException e) { } catch (NoSuchAlgorithmException e) { } return digest; } }
The method
private String hmacDigest(String msg, String keyString, String algo)
is used to calculate hmac. That method is used to check whether authentication is successful by using this method.
public boolean authenticationSuccessful(String dataForHmac, String authHeader)
Client has class HmacUtil.java to calculate hmac before send it to the server.
package com.blogspot.nipunswritings.hiname; import java.io.UnsupportedEncodingException;import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec; /** * Created by nipun on 7/25/15. */
public class HmacUtil { private static String KEY = "E34swDN1Fk1G44zmw9bL2N5B8R85w290";
private static String ALGO = "HmacSHA1";
/* * * This method is taken from
* http://www.supermind.org/blog/1102/generating-hmac-md5-sha1-sha256-etc-in-java
* but parameters have change here
* */
public static String hmacDigest(String msg) { String digest = null; try { SecretKeySpec key = new SecretKeySpec((KEY).getBytes("UTF-8"), ALGO);
Mac mac = Mac.getInstance(ALGO);
mac.init(key); byte[] bytes = mac.doFinal(msg.getBytes("ASCII")); StringBuffer hash = new StringBuffer();
for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) { hash.append('0'); } hash.append(hex); } digest = hash.toString(); } catch (UnsupportedEncodingException e) { } catch (InvalidKeyException e) { } catch (NoSuchAlgorithmException e) { } return digest; } }
Android client use below method to calculate hmac
public static String hmacDigest(String msg)
How to read http authorization header by using HttpServletRequest instance
@ApiMethod(name = "sayHi") public MyBean sayHi(HttpServletRequest request, @Named("name") String name) throws
HmacMismatchException{ String authHeader = request.getHeader("Authorization");
AuthManager authManager = new AuthManager();
String digestData = request.getMethod()+"+/myApi/v1/sayHi/"+name; if (!authManager.authenticationSuccessful(digestData, authHeader)){ throw new HmacMismatchException(); } MyBean response = new MyBean(); response.setData("Hi, " + name); return response;}
It has added new parameter with the type HttpServletRequest and it has name request. Developers can use getHeader() to get http header. After getting Authorization header data for hmac generation is assigned to digestData. Then it is passed with Authorization header value to
public boolean authenticationSuccessful(String dataForHmac, String authHeader)
of AuthManager. If the authentication is not successful Endpoint send HmacMismatchException to client. HmacMismatchException.java is shown below.
package com.example.nipun.myapplication.backend.auth; /** * Created by nipun on 7/25/15. */
public class HmacMismatchException extends Exception { public HmacMismatchException(){ super("Can't produce received hmac"); } }
How to set HMAC to authorization header by using Google Cloud Endpoint client library for Android
Android client in this blog post send a string,a name, to endpoint. Android user interface use EditText and Button to get user data. When button is clicked EndpointsAsyncTask is executed. It's provided name of the user.
Below method is called when a user touch the button.
public void sendName(View view){ String name = nameEditText.getText().toString(); new EndpointsAsyncTask().execute(new Pair<Context, String>(this, name)); }
Code for EndpointsAsyncTask with http headers setting
class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> { private MyApi myApiService = null; private Context context; @Override protected String doInBackground(Pair<Context, String>... params) { context = params[0].first; final String name = params[0].second; if(myApiService == null) { MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null) .setRootUrl("http://10.0.2.2:8080/_ah/api/") .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() { @Override
public void initialize(AbstractGoogleClientRequest<?>
abstractGoogleClientRequest) throws IOException { abstractGoogleClientRequest.setDisableGZipContent(true);
HttpHeaders httpHeaders =
abstractGoogleClientRequest.getRequestHeaders();
httpHeaders.setAuthorization("hmac "
+"public:"+HmacUtil.hmacDigest("POST+/myApi/v1/sayHi/"+name));
abstractGoogleClientRequest.setRequestHeaders(httpHeaders);
} }); myApiService = builder.build(); } try { return myApiService.sayHi(name).execute().getData();
} catch (IOException e) { return e.getMessage(); } } @Override protected void onPostExecute(String result) { Toast.makeText(context, result, Toast.LENGTH_LONG).show(); } }
Code to set Authorization header is this.
public void initialize(AbstractGoogleClientRequest<?>
abstractGoogleClientRequest) throws IOException { abstractGoogleClientRequest.setDisableGZipContent(true);
HttpHeaders httpHeaders = abstractGoogleClientRequest.getRequestHeaders(); httpHeaders.setAuthorization("hmac "
+"public:"+HmacUtil.hmacDigest("POST+/myApi/v1/sayHi/"+name));
abstractGoogleClientRequest.setRequestHeaders(httpHeaders);}
References
- https://github.com/GoogleCloudPlatform/gradle-appengine-templates/tree/master/HelloEndpoints
- http://stackoverflow.com/questions/15056830/getting-raw-http-data-headers-cookies-etc-in-google-cloud-endpoints
- http://www.supermind.org/blog/1102/generating-hmac-md5-sha1-sha256-etc-in-java
- http://restcookbook.com/Basics/loggingin/
No comments:
Post a Comment