Wednesday, 14 September 2011

Using RESTful URLs on your Spring 3 MVC Webapp

This blog comes as an answer to a question from a reader. Now, as a rule, I don’t do request blogs, but I thought that this sounded like an interesting topic. The question refers to my blog from July 11 on accessing request parameters using Spring 3 MVC and was “Do you know how to encode the uuid so it is not visible to the user for security concerns?”. The answer is “Yes”, but that would make for a very short blog.

In this case I’m assuming that the question is talking about using RESTful URLs to maintain session state information between pages. REST is a subject that’s had many words written about it, but putting it in very simplistic terms I’m defining it as not storing session or state information on your web server between browser calls. Even more simply put, it means not using the HttpSession interface implementations. Not maintaining state on the server gives you at least two obvious benefits; the first being improved loading. This is because your server is not using memory for storing state data which may, or may not be needed. The second benefit is scaling. If you’re using a domain with multiple servers, then tying session state to one server is problematic as you can’t usually guarantee that subsequent sequential calls to the domain will be directed to the same physical server.

There are various techniques use to solve this problem, including making everything form based and storing state information in hidden fields, using cookies, and manipulating URLs. This blog demonstrates the third technique of encrypting state data and tagging it onto the end of a URL, so that it’s passed back and forth between the browser and the server as a request parameter:

http://www.mywebsite.com/thepageIwant?session=ThisIsEncodedData

An example scenario for this technique would an e-commerce site. In this case, you’d select a couple of items and add them to your basket. You’d then login and review your basket and then perhaps modify the delivery address before proceeding to pay. In doing all this, you conceptually remain logged in to the server but, in the RESTful world, the server doesn’t remember you.

This example uses three screens to demonstrate this idea. The first screen logs you in, the second contains a URL that includes some session data, and the third displays the session data for review.


The screen shot above shows a simple login screen. Note that I’ve used a clear text password for demonstration purposes only.


The second screen in this scenario is the one that contains the encrypted session information; namely the user name and password, which has both been displayed and glued on to the end of the href of an anchor tag.


The last screen shows the decoded session information demonstrating that we’ve still got it and that it’s correct.

Moving on to the actual Java code for this demo... The simple bean below I’ve called Session and it holds all the state data we want encoding.

public class Session {

 
private static final String SEP = "=/=";

 
private String name;
 
private String password;

 
public Session() {
   
// Blank
 
}

 
public Session(String combined) {

   
String[] split = combined.split(SEP);
    name = split
[0];
    password = split
[1];
 
}

 
public String getName() {
   
return name;
 
}

 
public void setName(String username) {
   
this.name = username;
 
}

 
public String getPassword() {
   
return password;
 
}

 
public void setPassword(String password) {
   
this.password = password;
 
}

 
@Override
 
public String toString() {
   
return name + SEP + password;
 
}
}

If you look at the code above, you’ll see that it contains two features that give it context in the application. The toString(...) returns a string that the application can encode and the single argument constructor can create a full bean from its argument.

This works cohesively with the controller code, which is shown below...

@Controller
public class RewriteController {

 
private static final String ENCODING_KEY = "ThisWillBeTheKey";

 
/**
   * Create the initial blank form
   */
 
@RequestMapping(value = "/loginform", method = RequestMethod.GET)
 
public String getCreateForm(Model model) {

   
model.addAttribute("Session", new Session());
   
return "login.page";
 
}

 
/**
   * This is where you login...
   */
 
@RequestMapping(value = "/login", method = RequestMethod.POST)
 
public String login(Session userDetails, Model model)
     
throws UnsupportedEncodingException {

   
byte[] encodedBytes = encodeURL(userDetails);
    String encodedURL =
new String(Base64.encodeBase64(encodedBytes));
    model.addAttribute
("encodedURL", encodedURL);

   
return "display.encoded.url";
 
}

 
private byte[] encodeURL(Session userDetails)
     
throws UnsupportedEncodingException {

   
RC4Lite rc4 = getRC4Lite();

   
byte[] in = userDetails.toString().getBytes("UTF8");
   
byte[] out = new byte[in.length];

    rc4.encrypt
(in, 0, out, 0, in.length);

   
return out;
 
}

 
private RC4Lite getRC4Lite() throws UnsupportedEncodingException {
   
RC4Lite rc4 = new RC4Lite();
    rc4.setKey
(ENCODING_KEY.getBytes("UTF8"));
   
return rc4;
 
}

 
/**
   * This is where you're still in the REST session and re-validate the user
   */
 
@RequestMapping(value = "/decode", method = RequestMethod.GET)
 
public String someSessionMethod(
     
@RequestParam(value = "session") String sessionParam, Model model)
     
throws UnsupportedEncodingException {

   
byte[] encodedBytes = Base64
        .decodeBase64
(sessionParam.getBytes("UTF8"));
    String decodedString = decodeURL
(encodedBytes);

    Session session =
new Session(decodedString);
    model.addAttribute
(session);

   
return "display.decoded.url";
 
}

 
private String decodeURL(byte[] encodedBytes)
     
throws UnsupportedEncodingException {

   
RC4Lite rc4 = getRC4Lite();

   
byte[] out = new byte[encodedBytes.length];
    rc4.decrypt
(encodedBytes, 0, out, 0, encodedBytes.length);

   
return new String(out);
 
}
}

The controller class above contains three handler methods. The first handler method, getCreateForm(...), is fairly straight forward and simply adds a Session bean the model before displaying the login in form. The second handler method, login(...),
is where all the action takes place. There are two parts to encoding the session data (the name and password). Part one is to encrypt the data and for that I’ve simply borrowed my RC4Lite class from my RC4 Encryption blog1. Once encrypted, the RC4 bytes will need converting into ASCII and for that I’ve used Apache’s Base64 class as described in my base64 blog. Converting to ASCII ensures that the characters are printable and don’t cause any problems. The string is then added to the model where it’s attached to the anchor tag as shown in the following JSP snippet:

<a href='decode?session=${encodedURL}' } ><spring:message code="label.the.next.page"/></a>

The final handler method, someSessionMethod(...) receives the input from the above link, decodes it using a reverse process to encoding It then creates a new Session object and puts that into the model, which is then displayed on the final page.

Remember that this is only a sample, it just demonstrates the idea of encrypting session information and adding to a URL as a parameter. There are improvements you could make here, for example: using a interceptor to authenticate with the server upon each request before doing the business logic in the controller, or using a better encryption class, but in essence the principle applies to any webapp. There are also other techniques you can use for achieving secure web communications, so more on those later perhaps...


1 There are better ways of doing encryption than RC4, it's used here only for convenience in this demonstration.

1 comment:

blong824 said...

This is great! Thanks for the helpful blogs