Spring Surf Development #2 – Secure your Spring WebScript REST API

.
.
In the previous blog post we added a WebScript that listed the different safaris offered by the Safari company. Definitively no company secret in other words. But what if we want to add a WebScript that lists internal company information in a manager section of the website or let a registered user buy a safari? Then we need to add security to our REST API. In this blog post we will continue and build upon the Safari use case using Spring WebScripts. We will:
  1. Explain the basics of WebScript authentication
  2. Secure our Safari REST API by writing an authentication factory that maps Spring WebScript authentication roles to the Safari’s company’s user groups.
  3. Write 3 new secured webscripts: Create Trip, Create Booking & List User’s Bookings that we can use in future when we create the Safari web shop ui.
  4. Test that the WebScripts are secured

If you are only interested to get a quick glance at WebScript security, and don’t want to bother about all the Safari use case code, consider only reading the following sections: “Spring WebScript Security Basics”,  “Securing the Spring WebScript runtime” and possibly “Using HTTP Sessions in Spring WebScripts?”.

If you want to download the sample code you can do that from: Safari – Spring WebScripts REST API sample #2.

If .

Spring WebScript Security Basics

.

Spring WebScripts can be hosted inside a number of different runtime environments:

  • Servlet Runtime (HTTP Access)
  • JSR-168 Runtime (Portlet Access)
  • JSF Runtime (JSF Component Access)

.As you have probaly guessed the Safari REST API is using the Servlet Runtime, something we declared by defining the WebScriptServlet in safari-rest/src/main/webapp/WEB-INF/web.xml in the last blog post. All runtimes support 4 roles or levels of authentication which is declared in the <authentication> element in each webscript’s decriptor file (some-webscript.get.desc.xml):

  • none” – No authentication is required (default)
  • guest” – No authentication is required but the webscript could get access to additional read services
  • user” – Authentication is required
  • admin” – Authentication is required by a user with the admin role

The default authentication level, if the <authentication> element is omitted, is “none”..


The “use case” for the Safari Company

.

The Safari company currently only has 2 persons in its system:
  • erik” – A registered user and therefore a member of the “customer” group.
  • roy” – An employee and therefore a member of the “manager” group but sometimes also buys trips and therefore is a member of the “customer” group as well.
.
The class that will handle users, groups and authentication is the IdentityService. First register it as a Spring bean inside safari-rest/src/main/webapp/WEB-INF/config/web-application-context.xml next to our old TravelService from the previous blog post.
<!-- Safari Services -->
<bean id="travelService" class="com.safari.core.travel.TravelService"/>
<bean id="identityService" class="com.safari.core.travel.IdentityService"/
.
Then create the actual implementation in safari-core/src/main/java/com/safari/core/travel/ and make it look like below:.
package com.safari.core.travel;

import com.safari.core.SafariContext;

import java.util.ArrayList;
import java.util.List;

/**
 * In memory service for demo and testing purposes
 */
public class IdentityService {

   public static final String MANAGER = "manager";
   public static final String CUSTOMER = "customer";

   public boolean authenticate(String username, String password) {
      // Perform simple example authentication
      if (username.equals(password)) {
         SafariContext.setCurrentUser(username);
         return true;
      }
      return false;
   }

   public List<String> getUsers() {
      List<String> users = new ArrayList<String>();
      users.add("erik"); // A registered customer
      users.add("roy"); // Sales manager for the company who sometimes buy trips
      return users;
   }

   public List<String> getGroups(String user) {
      List<String> groups = new ArrayList<String>();
      if (user.equals("erik")) {
         groups.add(CUSTOMER);
      }
      else if (user.equals("roy")) {
         groups.add(MANAGER);
         groups.add(CUSTOMER);
      }
      return groups;
   }

}
Notice that the IdentityService “saves” the current username, if the authentication was successful, by calling SafariContext.setCurrentUser(username) which will make the username available elsewhere in the application. The implementation of SafariContext will be really simple and use the ThreadLocal class to make sure the correct username only is available for the current request/thread. Implement the SafariContext class in safari-core/src/main/java/com/safari/core/ and make it look like below:
package com.safari.core;

public abstract class SafariContext {

  static ThreadLocal<String> authenticatedUserNameThreadLocal = new ThreadLocal<String>();

  public static void setCurrentUser(String userName) {
    authenticatedUserNameThreadLocal.set(userName);
  }

  public static String getCurrentUser() {
    return authenticatedUserNameThreadLocal.get();
  }
}
So now we have the IdentityService that will handle the authentication (it will actually just do a silly authentication by making sure the username also is the password) and if so store the username so it is available from other parts of the application by doing a simple SafariContext.getCurrentUser().
.
But somehow we will need to make the IdentityService’s authenticate method get called by the webscripts runtime and we also somehow must match the Safari company’s groups (“customer” & “manager”) against the webscript “roles” (“none”, “guest”, “user” & “admin”). To do this we need to add our own custom authenticator factory to the webscripts runtime. (Don’t worry it’s very easy)

Securing the Spring WebScript runtime

As mentioned before the Safari REST API was configured to use the WebScript Servlet Runtime. To add authentication to it simply add the “authenticator” init-param to the WebScriptServlet and safari-rest/src/main/webapp/WEB-INF/web.xml like below:

<!-- Spring WebScripts -->
<servlet>
   <servlet-name>WebScriptServlet</servlet-name>
   <servlet-class>org.springframework.extensions.webscripts.servlet.WebScriptServlet</servlet-class>
   <init-param>
      <param-name>authenticator</param-name>
      <param-value>webscripts.authenticator.safari</param-value>
   </init-param>
</servlet>
.
The value “webscripts.authenticator.safari” is a spring bean reference to the authenticator factory we haven’t created yet. The job for it will be to  to decode the authentication headers from the HTTP request and decide if the username and the password matches (authentication) and if so decide if the user’s privilige matches or exceeds what the privelige that the webscript require (authorization).

This is done by simply extending the org.springframework.extensions.webscripts.AbstractBasicHttpAuthenticatorFactory class (which will do the work of decoding the HTTP headers for us) and respond to its 2 abstract methods: doAuthenticate(String username, String password) and doAuthorize(String username, RequiredAuthentication role).
Name it SafariBasicHttpAuthenticatorFactory, place it in safari-rest/src/main/java/com/safari/rest/auth/ and make it look like below:

package com.safari.rest.auth;

import com.safari.core.travel.IdentityService;
import org.springframework.extensions.webscripts.AbstractBasicHttpAuthenticatorFactory;
import org.springframework.extensions.webscripts.Description;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;

import java.util.ArrayList;
import java.util.List;

public class SafariBasicHttpAuthenticatorFactory extends AbstractBasicHttpAuthenticatorFactory
{

   private IdentityService identityService;

   /**
    * The Safari Identity Service which will perform the authentication.
    *
    * @param identityService The safari identity service
    */
   public void setIdentityService(IdentityService identityService) {
      this.identityService = identityService;
   }

   /**
    * Performs an authentication check using the Identity Service.
    *
    * @param username The username of the user
    * @param password THe password the user tries to log in with
    * @return true if the username and pass word matched a user in the system
    */
   public boolean doAuthenticate(String username, String password) {
      return identityService.authenticate(username, password);
   }

   /**
    * Translate the logged in users groups with the WebScript security
    * role model and decide if user is allowed to execute the WebScript.
    *
    * @param username The username of the logged in user
    * @param role The authority role that is required for the user to execute the webscript
    * @return true if user is authorized to execute the WebScript
    */
   public boolean doAuthorize(String username, Description.RequiredAuthentication role)
   {
      List<String> grantedGroupIds = new ArrayList<String>();
      if (role == Description.RequiredAuthentication.user) {
         /**
          * This method is called after doAuthenticate which means
          * the login was successful and the request was done by a user.
          */
         grantedGroupIds.add(IdentityService.CUSTOMER);
      }
      else if (role == Description.RequiredAuthentication.admin) {
         // Check if user is member of the admin group.
         grantedGroupIds.add(IdentityService.MANAGER);
      }
      if (grantedGroupIds.size() == 0) {
         // No group membership is required for the user.
         return true;
      }
      else {
         // Certain group membership is required user.
         List<String> userGroups = identityService.getGroups(username);
         for (String group : userGroups)
         {
            for (String grantedGroupId : grantedGroupIds) {
               if (group.equals(grantedGroupId)) {
                  return true;
               }
            }
         }
      }
      return false;
   }
}
Then define it as the “webscripts.authenticator.safari” bean in safari-rest/src/main/webapp/WEB-INF/web-application-context.xml:
<!-- Add authentication and authorization support for webscripts (used by the WebScriptServlet) -->
<bean id="webscripts.authenticator.safari" class="com.safari.rest.auth.SafariBasicHttpAuthenticatorFactory">
   <property name="identityService" ref="identityService"/>
</bean>
.
This is basically it! What will happen is that the doAuthenticate(username, password) method will be called if the authentication role for the webscript is “user” or “admin” (remember that “none” and “guest” didn’t require any authentication). If the authentication is successful doAuthorize(username, role) will also be called in which the mapping between webscript authentication roles and safari groups happen.
.
As you might have noticed, if you tried to compile, the AbstractBasicHttpAuthenticatorFactory class is missing. To add it to your project simply modify the safari-rest/pom.xml maven file from the last tuorial:
<!--
   Temporary include the "spring-surf" artifact to get the AbstractBasicHttpAuthenticatorFactory.
   When updating to RC2 or the final 1.0.0 release it will have moved to "spring-webscripts".
-->
<dependency>
   <groupId>org.springframework.extensions.surf</groupId>
   <artifactId>spring-surf</artifactId>
   <version>1.0.0-RC1</version>
</dependency>

<!-- Include the Spring WebScripts runtime -->
<dependency>
   <groupId>org.springframework.extensions.surf</groupId>
   <artifactId>spring-webscripts</artifactId>
   <version>1.0.0-RC1</version>
</dependency>
<!-- Include the Spring WebScript API so we can browse and list our webscripts on the server -->
<dependency>
   <groupId>org.springframework.extensions.surf</groupId>
   <artifactId>spring-webscripts-api</artifactId>
   <version>1.0.0-RC1</version>
</dependency>
So by now you should be ready to build and redploy the entire project by changing into your projects top level directory do:
mvn -f safari-root/pom.xml clean install org.codehaus.mojo:tomcat-maven-plugin:1.0-beta-1:redeploy
If you got an error try “deploy” rather than “redeploy” since the webapp might have been removed since you did the previous tutorial.
To test that we actually have succeded to secure the our WebScripts point your browser to http://localhost:8080/safari-rest/service/index which then shall make your browser display a login dialog. This url is pointing to the pages that lists all deployed webscripts, and in the last tutorial they were publically available since we hadn’t secured the runtime. If you try to login with “erik” (as both username and password) it shall NOT work, since he only is a “user”. But if you try to login with “roy” (as both username and password) it should work.
By now we are actually finished with the security part of this tutorial so lets do a quick recap what we did:
  1. Told the WebScriptServlet in web.xml to use our authenticator: “webscripts.authenticator.safari”.
  2. Defined the authenticator bean in web-application-config.xml.
  3. Implemented the authenticator by extending AbstractBasicHttpAuthenticatorFactory and respond to doAuthenticate(username, password) and doAuthorize(username, role).

… the rest is up to you and how your app is being implemented, perhaps you want to pass in the username and roles to another environment?

.

Using HTTP Sessions in Spring WebScripts?

What could be worth pointing out is that the only place where the authentication details are stored, using this approach, is in the request headers and that currently no sessions are used, in other words ideal for scaling. However if you want to use the HttpSession it’s of course possible to access it both in a regular webscript and in your custom made authenticator class. Simply modify you authentication factory with the changes below:

public class SafariBasicHttpAuthenticatorFactoryWithSession extends AbstractBasicHttpAuthenticatorFactory
{
   ...

   // Add a place to store the request and response when the authenticator is created
   private WebScriptRequest req;
   private WebScriptResponse res;

   ...

   @Override
   public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
   {
   	// Override the create method so we get a change to store the request & response
      this.req = req;
      this.res = res;
      return new BasicHttpAuthenticator(req, res);
   }

   @Override
   public boolean doAuthenticate(String username, String password) {
      if (identityService.authenticate(username, password)) {
      	// ...so we can access the HttpSession after a successful login
         this.req.getHttpServletRequest().getSession().setAttribute("safari.username", username);
         return true;
      }
      return false;
   }

   ...

}
…and to later access the session attribute it in the WebScript:
public class SafariWebScriptWithSessionGet extends SafariWebScript
{
   @Override
   protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) {
      String bar = (String) ((WebScriptServletRequest)req).getHttpServletRequest().getSession().getAttribute("safari.username");
      …
   }
}

.

Creating 3 secured WebScripts

The rest of this tutorial will be about creating a couple of secured WebScripts. We will use these WebScripts in future blog posts when we build a Spring Surf web application that will use them to display Safari trips and bookings.

First create a simple Booking pojo in safari-core/src/main/java/com/safari/core/travel/Booking.java that looks like below:

package com.safari.core.travel;

public class Booking {

   private int id;
   private String username;
   private Trip trip;

   public Booking () {}

   public int getId(){ return id; }
   public void setId(int id) { this.id = id; }

   public String getUsername() { return username; }
   public void setUsername(String username) { this.username = username; }

   public Trip getTrip() { return trip; }
   public void setTrip(Trip trip) { this.trip = trip; }

}
Then open the TravelService class that we implemeted in the previous tutorial, located in safari-core/src/main/java/com/safari/core/travel/, and update it so it looks like below:
package com.safari.core.travel;

import com.safari.core.SafariContext;

import java.util.ArrayList;
import java.util.List;

/**
 * In memory service for testing purposes
 */
public class TravelService {

   private static int tripIndex = 0;
   private static List<Trip> trips = new ArrayList<Trip>();
   private static List<Booking> bookings = new ArrayList<Booking>();

   public TravelService() {
      // Bootstrap data
      trips.add(new Trip(++tripIndex, "Masai Mara Adventurer"));
      trips.add(new Trip(++tripIndex, "Serengeti Explorer"));
      trips.add(new Trip(++tripIndex, "Kruger Wildlife"));
   }

   /**
    * Returns a public list of all available trips
    *
    * @return a list of trips
    */
   public List<Trip> getTrips() {
      return trips;
   }

   /**
    * Finds a trip by id
    *
    * @param tripId The trip id to look for
    * @return the trip if found otherwise null
    */
   public Trip getTrip(int tripId) {
      // Find the trip
      for (Trip trip : trips) {
         if (trip.getId() == tripId) {
            return trip;
         }
      }
      return null;
   }

   /**
    * Administration method to create new trips
    *
    * @param trip The trip to create
    * @return The created trip with a unique id
    */
   public Trip createTrip(Trip trip) {
      // Give trip a unique id and add it
      trip.setId(++tripIndex);
      trips.add(trip);
      return trip;
   }

   /**
    * Lets the current user create a booking for a certain trip
    *
    * @param tripId The trip to create a booking for
    */
   public Booking createBooking(int tripId) {
      // Check that the trip exists
      Trip trip = getTrip(tripId);
      if (trip == null) {
         throw new IllegalArgumentException("Cannot book trip with id '" + tripId + "' since it doesn't exist");
      }

      // Create booking and return it
      Booking booking = new Booking();
      booking.setTrip(trip);
      booking.setUsername(SafariContext.getCurrentUser());
      bookings.add(booking);
      return booking;
   }

   /**
    * Returns a list of the current users bookings
    */
   public List<Booking> getBookings() {
      List<Booking> userBookings = new ArrayList<Booking>();
      for (Booking booking : bookings) {
         if (booking.getUsername().equals(SafariContext.getCurrentUser())) {
            userBookings.add(booking);
         }
      }
      return userBookings;
   }

}
The code should be pretty self explanatory, what could be worth pointing out is that the getBookings() method uses the SafariContext.getCurrentUser() when it makes sure that only the current users bookings are returned.
No let’s create a new webscript that will create a  Trip and only should be available to administrators. Create the descriptor file as safari-rest/src/main/resources/webscripts/com/safari/travel/trip.post.desc.xml. It’s just like any other webscript except that the <authentication> element is set to contain the value of “admin”.
<webscript>
   <shortname>Create Trip</shortname>
   <description>Creates a trip</description>
   <url>/travel/trip</url>
   <format default="json">argument</format>
   <authentication>admin</authentication>
</webscript>

Now let’s continue with the admin WebScript for creating trips and implement the Java controller:

package com.safari.rest.api.travel;

import com.safari.core.travel.Trip;
import com.safari.rest.api.SafariWebScript;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;

import java.util.HashMap;
import java.util.Map;

public class TripPost extends SafariWebScript
{
   protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) {
      try {
         JSONObject json = new JSONObject(req.getContent().getContent());
         Trip trip = new Trip();
         trip.setName(json.getString("name"));
         trip = getTravelService().createTrip(trip);
         Map<String, Object> model = new HashMap<String, Object>();
         model.put("trip", trip);
         return model;
      }
      catch (JSONException e) {
         throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The request body contains badly formatted json");
      } catch (Exception e) {
         throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Trip could not be created: " + e.getMessage());
      }
   }
}
Note that the webscript expects the request body to contain JSON, its in other words not a regular HTML Form post that is being submitted. This is an approach that we will use for all of our webscripts. Also note that we are throwing a special exception if the request isn’t correctly formatted: Status.STATUS_BAD_REQUEST this exception actually maps to the HTTP status code 400. When you create your webscript make sure to use the HTTP status codes to signal to the client what went wrong.Below is the response template that shall be defined in safari-rest/src/main/resources/webscripts/com/safari/travel/trip.post.json.ftl. It will return the new trip and the id it was assigned when “persisted” in the service:
<#escape x as jsonUtils.encodeJSONString(x)>
{
   "id": ${trip.id},
   "name": "${trip.name}"
}
</#escape>
.
Below comes the code for 2 new webscripts for the Safari customers (in other words “users”). First one that allows a user to create a booking and then one that lists the users bookings.

Create safari-rest/src/main/resources/webscripts/com/safari/travel/booking.post.desc.xml and make it look like below and note that the <authentication> element is set to “user”:

<webscript>
   <shortname>Create Booking</shortname>
   <description>Creates a booking</description>
   <url>/travel/booking</url>
   <format default="json">argument</format>
   <authentication>user</authentication>
</webscript>
Then create the Java controller in safari-rest/src/main/java/com/safari/rest/api/travel/BookingPost.java that also expects a json request body.
package com.safari.rest.api.travel;

import com.safari.core.travel.Booking;
import com.safari.rest.api.SafariWebScript;
import org.json.JSONObject;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;

import java.util.HashMap;
import java.util.Map;

public class BookingPost extends SafariWebScript
{
   @Override
   protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
   {
      // Make sure tripId is a number and create the booking
      try {
         JSONObject json = new JSONObject(req.getContent().getContent());
         Booking booking = getTravelService().createBooking(json.getInt("tripId"));
         Map<String, Object> model = new HashMap<String, Object>();
         model.put("booking", booking);
         return model;
      }
      catch (NumberFormatException nfe) {
         throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Parameter tripId is mandatory and must contain an integer value");
      } catch (Exception e) {
         throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Trip could not be booked: " + e.getMessage());
      }
   }
}
Now create the response template in safari-rest/src/main/resources/webscripts/com/safari/travel/booking.post.json.ftl and note how easy it is to render the nested the Trip object inside the Booking:
<#escape x as jsonUtils.encodeJSONString(x)>
{
   "id": ${booking.id},
   "username": "${booking.username}",
   "trip": {
      "id": "${booking.trip.id}",
      "name": "${booking.trip.name}"
   }
}
</#escape>
.
Now we are almost there, go ahead and create the second webscript that lists the user’s bookings, define it in safari-rest/src/main/resources/webscripts/com/safari/travel/bookings.get.desc.xml:
<webscript>
   <shortname>List User's Bookings</shortname>
   <description>Lists the logged in user's bookings</description>
   <url>/travel/bookings</url>
   <format default="json">argument</format>
   <authentication>user</authentication>
</webscript>
Implement its Java controller in safari-rest/src/main/java/com/safari/rest/api/travel/BookingsGet.java:
package com.safari.rest.api.travel;

import com.safari.rest.api.SafariWebScript;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;

import java.util.HashMap;
import java.util.Map;

public class BookingsGet extends SafariWebScript
{
   @Override
   protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
   {
      // Create the template model and fill it with trips
      Map<String, Object> model = new HashMap<String, Object>();
      model.put("bookings", getTravelService().getBookings());
      return model;
   }
}
Now create the response template that lists the bookings in safari-rest/src/main/resources/webscripts/com/safari/travel/bookings.get.json.ftl:
<#escape x as jsonUtils.encodeJSONString(x)>
[<#list bookings as booking>
   {
      "id": ${booking.id},
      "username": "${booking.username}",
      "trip": {
         "id": "${booking.trip.id}",
         "name": "${booking.trip.name}"
      }
   }<#if booking_has_next>,</#if>
</#list>]
</#escape>
Finally, since they are java backed webscripts, we need to defined them as spring beans in safari-rest/src/main/webapp/WEB-INF/config/web-application-context.xm, place them under the TripsGet webscript so the whole list of webscripts look like below:
<!-- Safari WebScripts / REST API calls (id follows pattern with "webscript." + package + webscript name + method -->
<bean id="webscript.com.safari.travel.trips.get"
      class="com.safari.rest.api.travel.TripsGet"
      parent="safariWebscript">
</bean>

<bean id="webscript.com.safari.travel.trip.post"
      class="com.safari.rest.api.travel.TripPost"
      parent="safariWebscript">
</bean>

<bean id="webscript.com.safari.travel.booking.post"
      class="com.safari.rest.api.travel.BookingPost"
      parent="safariWebscript">
</bean>

<bean id="webscript.com.safari.travel.bookings.get"
      class="com.safari.rest.api.travel.BookingsGet"
      parent="safariWebscript">
</bean>
Thats it, we now have 4 webscripts and the Safari REST API is complete. Now lets go ahead and test it the calls.

Testing you WebScripts

First build the entire project and redploy the rest api using:

mvn -f safari-root/pom.xml clean install org.codehaus.mojo:tomcat-maven-plugin:1.0-beta-1:redeploy
Now, to create a trip you will need to be logged in as admin and post a request with json on the request body. If you don’t want to write some javascript that does this you can probably test it from your IDE. If not, there are a great variety of tools, such as curl (command line) or RESTClient (a free FireFox addon that is simple to use).
.
What ever tool you choose create a request like below to create a trip:
POST http://localhost:8080/safari-rest/service/travel/trip
{
  "name": "Spring Safari"
}
If you hadn’t logged in do it with “roy”:”roy” and you shall get a response like below:
{
   "id": 4,
   "name": "Spring Safari"
}
To list all of the Safari company’s trips do a:
GET http://localhost:8080/safari-rest/service/travel/trips
That shall give  you a list where your new trip is included. To go further and create a booking make sure to clear your cookies so you’re not logged into the rest api. Then go ahead and make a request like below and login as “erik”:”erik”
POST http://localhost:8080/safari-rest/service/travel/booking
{
  "tripId": 4
}
Which shall return:
{
   "id": 0,
   "username": "roy",
   "trip": {
      "id": 4,
      "name": "Spring Safari"
   }
}
To later list erik’s bookings create a request like below:
GET http://localhost:8080/safari-rest/service/travel/bookings
Which shall return:
[
   {
      "id": 0,
      "username": "roy",
      "trip": {
         "id": 4,
         "name": "Spring Safari"
      }
   }
]
Ok, that’s it. Hopefully you have found this blog post interesting. Next post will be about creating a Safari web application using Spring Surf.

5 thoughts on “Spring Surf Development #2 – Secure your Spring WebScript REST API

  1. Mark

    Hi Erik

    Thanks for posting this article. I’m really interested in using the Web Script framework to host some services in standalone mode (i.e. not part of Alfresco).

    Can you please explain how I can use script based controllers (e.g. Rhino JavaScript and Groovy)?

    Also, I noticed that Web Scripts when used with Alfresco are available via ‘/service’ (basic auth & ticketed login) and ‘/wcservice’ (Alfresco Explorer auth – e.g. NTLM & Kerberos) endpoints. Does the standalone Web Scripts framework support the same authentication options? i.e. can I create a WebScript and make it available via basic auth, Kerberos, etc?

    Many thanks

    Reply
  2. Mark

    I guessed at how to use a scripted controller. I created folder ‘WEB-INF/classes/webscripts’ then dropped my Web Script descriptor, script/controller and FTL template there.

    I would still be interested in other forms of authentication so if you have any information on Kerberos or LDAP then I’d be much obliged.

    Regards

    Reply
  3. Erik Winlöf Post author

    Hi Mark, good to hear you solved it, sorry I hadn’t got back to you earlier.

    Regarding your other questions, what if you continue to use BASIC HTTP between your client and the rest services and just modify IdentityService.java above to do an LDAP search instead of going against the test data? Would that be sufficient?

    Cheers,

    :: Erik

    PS. Also note that if you use a javascript controller as you said you had done, you obviously don’t need to define a spring bean in web-application-context.xml

    Reply
  4. Ranveer

    Hello,

    I am creating a spring surf application. I am calling custom java class for authentication by extending the surf LoginController. I have added session variables in my class. Now i want to access these variables into my ftl file. How can i get the variables ?

    Is it compulsory to call webscript(written in java) to get session variables ? if yes then how to call ?

    Thanks

    Reply
    1. Erik Winlöf Post author

      If your .ftl file is part of a webscript backed by a Java class you can access the session variable like it was mentioned in the blog post and then put in in the webscript’s model so it can be used from the .ftl file. In other words something like this:


      protected Map executeImpl(WebScriptRequest req, Status status, Cache cache)
      {
      // Create the template model and fill it with trips
      Map model = new HashMap();
      model.put("someSessionValue", req.getHttpServletRequest().getSession().getAttribute("safari.username"));
      return model;
      }

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>