Monday, May 13, 2013

Writing REST Services in Java: Part 8 JSR 303 Validation


Previous Post : Part Seven: Moving To Production

Get the Code: https://github.com/iainporter/rest-java

The introduction of JSR 303 for validation really simplifies the process of validation. It consists of a meta data model using annotations and an API for running validations against annotated classes.

 It cleanly separates the concerns and prevents pojo classes from being cluttered with custom validation code.

In this rest sample project the DTO objects that comprise the API layer are annotated with validations. The service tier is then responsible for handling any validation failures and wrapping them in ValidationExceptions.

Using this approach makes it possible to publish your API with all of the DTO classes and service interfaces and rely on the consumer of the API to make their own decisions on Validation handling.

JSR 303 Implementation


The implementation used in the project is Hibernate Validator.

The dependencies in gradle:

'org.hibernate:hibernate-validator:4.3.1.Final'
'javax.validation:validation-api:1.1.0.Final'

for maven:


<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.1.Final</version>
</dependency>

<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>

Applying Constraints



@XmlRootElement
public class CreateUserRequest {

    @NotNull
    @Valid
    private ExternalUser user;

    @NotNull
    @Valid
    private PasswordRequest password;


    public CreateUserRequest() {
    }

    public CreateUserRequest(final ExternalUser user, final PasswordRequest password) {
        this.user = user;
        this.password = password;
    }

    public ExternalUser getUser() {
        return user;
    }

    public void setUser(ExternalUser user) {
        this.user = user;
    }

    public PasswordRequest getPassword() {
        return password;
    }

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

}


The two properties user and password can not be null. The @Valid constraint performs validation recursively on the objects.

The relevant part of ExternalUser.java


@XmlRootElement
public class ExternalUser implements Principal {

    private String id;
    
    @Length(max=50)
    private String firstName;
    
    @Length(max=50)
    private String lastName;
    
    @NotNull
    @Email
    private String emailAddress;

    ............


and PasswordRequest.java


@XmlRootElement
public class PasswordRequest {

    @Length(min=8, max=30)
    @NotNull
    private String password;

    public PasswordRequest() {}

    public PasswordRequest(final String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

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

}



@Length is part of the Bean Validation spec whilst @Email is a Hibernate custom validation


Validating Constraints



First we must create an instance of Validator and then pass it into any service that requires it.

    
    


 @Autowired
    public UserServiceImpl(UsersConnectionRepository usersConnectionRepository, Validator validator) {
        this(validator);
        this.jpaUsersConnectionRepository = usersConnectionRepository;
        ((JpaUsersConnectionRepository)this.jpaUsersConnectionRepository).setUserService(this);
    }


We can call validate on any request object in the service class and throw a Validation Exception if there are any failures


    @Transactional
    public AuthenticatedUserToken createUser(CreateUserRequest request, Role role) {
        validate(request);
        User searchedForUser = userRepository.findByEmailAddress(request.getUser().getEmailAddress());
        if (searchedForUser != null) {
            throw new DuplicateUserException();
        }

        User newUser = createNewUser(request, role);
        return new AuthenticatedUserToken(newUser.getUuid().toString(), newUser.addSessionToken().getToken());
    }


The validate method

   protected void validate(Object request) {
        Set<? extends ConstraintViolation<?>> constraintViolations = validator.validate(request);
        if (constraintViolations.size() > 0) {
            throw new ValidationException(constraintViolations);
        }
    }


The Validation Exception class wraps any errors in a response object to return to the client to provide a meaningful contextual error response.

public class ValidationException extends WebApplicationException {

    private final int status = 400;
    private String errorMessage;
    private String developerMessage;
    private List<ValidationError> errors = new ArrayList<ValidationError>();

    public ValidationException() {
        errorMessage = "Validation Error";
        developerMessage = "The data passed in the request was invalid. Please check and resubmit";
    }

    public ValidationException(String message) {
        super();
        errorMessage = message;
    }

    public ValidationException(Set<? extends ConstraintViolation<?>> violations) {
        this();
        for(ConstraintViolation<?> constraintViolation : violations) {
            ValidationError error = new ValidationError();
            error.setMessage(constraintViolation.getMessage());
            error.setPropertyName(constraintViolation.getPropertyPath().toString());
            error.setPropertyValue(constraintViolation.getInvalidValue() != null ? constraintViolation.getInvalidValue().toString() : null);
            errors.add(error);
        }
    }

    @Override
    public Response getResponse() {
        return Response.status(status).type(MediaType.APPLICATION_JSON_TYPE).entity(getErrorResponse()).build();
    }

    public ErrorResponse getErrorResponse() {
        ErrorResponse response = new ErrorResponse();
        response.setApplicationMessage(developerMessage);
        response.setConsumerMessage(errorMessage);
        response.setValidationErrors(errors);
        return response;
    }

}


Testing Validation

Testing is easy. Just create an instance of Validator and pass it the object to test.

public class PasswordRequestTest {

    protected Validator validator;

    @Before
    public void setUp() {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    @Test
    public void validPassword() {
        PasswordRequest request = new PasswordRequest("password");
        Set<ConstraintViolation<PasswordRequest>> constraints = validator.validate(request);
        assertThat(constraints.size(), is(0));
    }

    public void passwordTooShort() {
        PasswordRequest request = new PasswordRequest(RandomStringUtils.randomAlphanumeric(7));
        Set<ConstraintViolation<PasswordRequest>> constraints = validator.validate(request);
        assertThat(constraints.size(), is(1));
    }

    public void passwordTooLong() {
        PasswordRequest request = new PasswordRequest(RandomStringUtils.randomAlphanumeric(36));
        Set<ConstraintViolation<PasswordRequest>> constraints = validator.validate(request);
        assertThat(constraints.size(), is(1));
    }
}


Testing the API

To test is against the running application start up the server by executing gradle tomcatRun

Execute an curl statement with an invalid request such as the following

curl -v -H "Content-Type: application/json" -X POST -d '{"user":{"firstName","lastName":"Bar","emailAddress":"@##@.com"}, "password":"123"}' http://localhost:8080/java-rest/user


You should see s result similar to this

HTTP/1.1 400 Bad Request Server: Apache-Coyote/1.1 Content-Type: application/json Transfer-Encoding: chunked Date: Mon, 13 May 2013 21:22:23 GMT Connection: close * Closing connection #0 {"errorCode":null,"consumerMessage":"Validation Error","applicationMessage":"The data passed in the request was invalid. Please check and resubmit","validationErrors":[{"propertyName":"user.emailAddress","propertyValue":"@##@.com","message":"not a well-formed email address"},{"propertyName":"password.password","propertyValue":"123","message":"length must be between 8 and 30"}]}


There are lots more cool things you can do with validation. See the spec for more details.

Saturday, March 9, 2013

Writing REST Services in Java: Part 7 Moving To Production

Previous Post : Part Six: Security & Authorization
Get the Code: https://github.com/iainporter/rest-java

This post will discuss the steps needed to configure the project to deploy it in a staging or production environment.
It is only of interest if you wish to use the code and deploy it beyond the in-memory defaults.

Database Configuration

The project makes use of Spring's active profiles.

Profiles can be set with a System property or, when running gradle by setting the property in gradle.properties
Choices are dev | local | staging | production
When running the dev profile the database is an in-memory H2 database.

The first step is to test that the application will run against a real database. The local profile can be used for this purpose.

The configuration file is at 

src/main/resources/META-INF/spring/data-context.xml

You can change the properties to suit your local environment.


Test the application by setting the active profile to local and executing:

gradle clean build integrationTest


If the build is successful there should now be test data in the database.

Adding indexes and Message Store tables

On start up if the tables do not exist they will be created. The next step is to add the relevant indexes by executing the script in:

src/main/resources/schema/indexes.sql


Finally we need to add the tables used by Spring Integration to persist messages on the queues. There are scripts for various database types that ship with the Spring source code in the spring-integration-jdbc.jar
A default script for MySql is available in

src/main/resources/schema/message_store.sql


There are placeholders in data-context.xml for adding the staging and production profile database configurations. Repeat the above steps for each of these environments.

Email Configuration

Both the dev and local profiles use a MockJavaMailSender which does not deliver any messages but stores them in a HashMap. When you are ready to send real mail messages then you will have to configure the mail properties in

src/main/resources/META-INF/spring/email-template-context.xml


As discussed in Part Three you can choose from a variety of different mail services. The simplest is to use a gmail or yahoo account. There are also many useful bulk mail services that you would want to use in production such as CritSend, MailJet, MailChimp, etc.

One final configuration point to mention is that the mail service is backed by several message queues implemented in Spring Integration. These queues have a backing message store to which to temporarily persist messages until they are delivered. When running with the dev or local profiles the message store is a simple in-memory store which will not survive a crash or restart. For staging and production the messages are persisted to the datastore configured for that profile, hence the requirement for the sql script mentioned above to create those tables.

Additional Configuration

There are a few other properties that you can set in

src/main/resources/properties/app.properties


Here you can change how long session tokens should remain active, which authorization method to use, set email preferences, etc. See the comments in that file for full details

The next post will discuss how to write new services and the final post will discuss deploying to the cloud.

Wednesday, January 30, 2013

Writing REST Services in Java: Part 6 Security & Authorization

Previous Post : Part Five: Lost Password
Get the Code: https://github.com/iainporter/rest-java

Part Two dealt with signing up and authenticating users using the conventional email and password approach. Part Four also covered authentication but using the OAuth protocol. This post will cover authorization of services.

To that end what we want to accomplish is:

  • Identify who is making the request
  • Have a high degree of confidence that they are who they say they are
  • Limit access to only those resources that they are allowed to access
  • Prevent malicious tampering of requests or session hijacking


SSL


Ideally all API traffic should be over a secure connection using SSL. If that is not feasible then at the very least all requests that would be open to MITM attacks should be secured, such as account creation, login, etc

Security Filter


All API requests first go through a security filter. An authorization service is registered with the filter on startup. Depending on the level of security you are comfortable with and how much effort you want to subject clients to will determine the choice of service. I'll cover that later in the post. First we need to register the filter with the servlet container.

In src/main/webapp/WEB-INF/web.xml a ResourceFilterFactory is added to the config

        <init-param>
            <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
            <param-value>com.porterhead.rest.filter.ResourceFilterFactory</param-value>
        </init-param>


The ResourceFilterFactory extends the Jersey class RolesAllowedResourceFilterFactory. In it we register the security filter and ensure it is the first filter in line.

@Component
@Provider
public class ResourceFilterFactory extends RolesAllowedResourceFilterFactory {

    @Autowired
    private SecurityContextFilter securityContextFilter;

    @Override
    public List<ResourceFilter> create(AbstractMethod am) {
        List<ResourceFilter> filters = super.create(am);
        if (filters == null) {
            filters = new ArrayList<ResourceFilter>();
        }
        List<ResourceFilter> securityFilters = new ArrayList<ResourceFilter>(filters);
        //put the Security Filter first in line
        securityFilters.add(0, securityContextFilter);
        return securityFilters;
    }
}


The SecurityContextFilter class gathers some information from the request and then delegates to the AuthorizationService implementation to handle authorizing the request and loading the user. If the request is valid a user is returned unless the resource is not access controlled in which case no authorization headers will be required. The user is wrapped in a SecurityContext and added to the ContainerRequest.

    public ContainerRequest filter(ContainerRequest request) {
        String authToken = request.getHeaderValue(HEADER_AUTHORIZATION);
        String requestDateString = request.getHeaderValue(HEADER_DATE);
        String nonce = request.getHeaderValue(HEADER_NONCE);
        AuthorizationRequestContext context = new AuthorizationRequestContext(request.getPath(), request.getMethod(),
                            requestDateString, nonce, authToken);
        ExternalUser externalUser = authorizationService.authorize(context);
        request.setSecurityContext(new SecurityContextImpl(externalUser));
        return request;
    }


Role Based Access


Using JAX-RS annotations we can protect resources based on roles. It is the task of the SecurityContext implementation that was added to the Container Request in the above step that will answer the question of whether the user has the role.

    public boolean isUserInRole(String role) {
        if(role.equalsIgnoreCase(Role.anonymous.name())) {
             return true;
        }
        if(user == null) {
            throw new InvalidAuthorizationHeaderException();
        }
        return user.getRole().equalsIgnoreCase(role);
    }


If anyone can access the resource, as in the case of user/login then it is annotated with @PermitAll

    @PermitAll
    @Path("login")
    @POST


If we want to limit access then we use the annotation @RolesAllowed with a list of allowed roles

    @RolesAllowed({"authenticated"})
    @Path("{userId}")
    @GET
    public Response getUser(@Context SecurityContext sc, @PathParam("userId") String userId) {
        ExternalUser userMakingRequest = (ExternalUser)sc.getUserPrincipal();
        ExternalUser user =  userService.getUser(userMakingRequest, userId);
        return Response.ok().entity(user).build();
    }


Note that we can also use annotations to retrieve the user that was wrapped in the SecurityContext as part of the Authorization procedure. We can then check that this user can not only access this resource method but also at the service level whether they can access the user object that is being requested. In this situation we want to check that they can only request getUser on their own instance. We could add "administrative" role access and allow that user to access any user instance.

Session Token Authorization


The simplest method of authorization is to get a session token on login or sign up and pass that back on every request. This is obviously not secure enough to be transmitted over anything other than an SSL connection. It can be useful for testing services quickly and easily with curl without the overhead of signing the request each time.
    public ExternalUser authorize(AuthorizationRequestContext securityContext) {
        String token = securityContext.getAuthorizationToken();
        ExternalUser externalUser = null;
        if(token == null) {
            return externalUser;
        }
        User user =  userRepository.findBySession(token);
        if(user == null) {
            throw new AuthorizationException("Session token not valid");
        }
        for (SessionToken sessionToken : user.getSessions()) {
            if (sessionToken.getToken().equals(token)) {
                sessionToken.setLastUpdated(new Date());
                userRepository.save(user);
                externalUser = new ExternalUser(user, sessionToken);
            }
        }
        return externalUser;
    }


We extract the token from the request header and query the User Repository for a User with that Session token. Finally, if the query was successful the token is timestamped so that it can be removed by a session reaper on expiry.

Testing Session Token Authorization


1. In src/main/resources/properties/app.properties set the following property

security.authorization.requireSignedRequests=false


2. Start the application by executing

gradle tomcatRun


3. Create a user with the curl statement

curl -v -H "Content-Type: application/json" -X POST -d '{"user": {"firstName":"Foo","lastName":"Bar","emailAddress":"foobar@example.com"}, "password":"password"}' http://localhost:8080/java-rest/user


You should receive back an AuthenticatedUserToken similar to this

< HTTP/1.1 201 Created < Server: Apache-Coyote/1.1 < Location: http://localhost:8080/java-rest/user/d26079db-62e9-4819-964a-be954d2c47ed < Content-Type: application/json < Transfer-Encoding: chunked < Date: Tue, 29 Jan 2013 19:48:05 GMT {"userId":"d26079db-62e9-4819-964a-be954d2c47ed","token":"86e8be75-3eac-45aa-acc3-f043333c8608"}


4. For Session token authorization we only need the token part. Now construct a curl statement based on the location url in the response to GET a user similar to the following

curl -v -H "Content-Type: application/json" -X GET -H "Authorization: 86e8be75-3eac-45aa-acc3-f043333c8608" 'http://localhost:8080/java-rest/user/d26079db-62e9-4819-964a-be954d2c47ed'


You should receive a response similar to this one

< HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Tue, 29 Jan 2013 19:58:13 GMT {"id":"d26079db-62e9-4819-964a-be954d2c47ed","firstName":"Foo","lastName":"Bar","emailAddress":"foobar@example.com","socialProfiles":[],"name":"foobar@example.com","verified":false}


Request Signing Authorization


A surer method of securing requests that does not require an SSL connection other than for passing back and forth credentials is to sign requests. This implementation is based somewhat on the OAuth spec, although does not go as far as to require the request body to be signed. That can easily be plugged in though for an added layer of security.

On login or sign up an AuthenticatedUserToken is returned. An AuthenticatedUserToken is comprised of:
  • userId - passed with every role-based request as a means to identify the user.
  • token - used as a shared secret between the client and the application to verify the hash of a token passed with the request.


To generate a hashed token a String is composed of the following

  • The session token
  • A : separator
  • The relative url of the resource (i.e. user/ff7ffcf0-cfe0-4c1e-9971-3b934612b154) followed by ,
  • The HTTP method (GET, POST, PUT, DELETE) followed by ,
  • A time stamp of the request in Iso8061 format followed by ,
  • A unique nonce token generated by the client


This string is then hashed using SHA-256 and then Base64 encoded. The Authorization header is composed of the user token followed by a : separator and then the hashed signature
In order for the server to reconstitute the string the timestamp and nonce must also be passed as headers.

The main methods to authorize signed requests

    public ExternalUser authorize(AuthorizationRequestContext context) {

        ExternalUser externalUser = null;
        if (context.getAuthorizationToken() != null && context.getRequestDateString() != null && context.getNonceToken() != null) {
            String userId = null;
            String hashedToken = null;
            String[] token = context.getAuthorizationToken().split(":");
            if (token.length == 2) {
                userId = token[0];
                hashedToken = token[1];
                //make sure date and nonce is valid
                validateRequestDate(context.getRequestDateString());
                validateNonce(context.getNonceToken());

                User user = userRepository.findByUuid(userId);
                if (user != null) {
                    externalUser = new ExternalUser(user);
                    if (!isAuthorized(externalUser, context, hashedToken)) {
                        throw new AuthorizationException("Request rejected due to an authorization failure");
                    }
                }
            }
        }
        return externalUser;
    }


    private boolean isAuthorized(ExternalUser user, AuthorizationRequestContext authorizationRequest, String hashedToken) {
        Assert.notNull(user);
        Assert.notNull(authorizationRequest.getAuthorizationToken());
        String unEncodedString = composeUnEncodedRequest(authorizationRequest);
        List<UserSession> sessionTokens = user.getSessions();
        String userTokenHash = null;
        for (SessionToken token : sessionTokens) {
            userTokenHash = encodeAuthToken(token.getToken(), unEncodedString);
            if (hashedToken.equals(userTokenHash)) {
                token.setLastUpdated(new Date());
                userRepository.save(user);
                return true;
            }
        }
        LOG.error("Hash check failed for hashed token: {} for the following request: {} for user: {}",
                new Object[]{authorizationRequest.getAuthorizationToken(), unEncodedString, user.getId()});
        return false;
    }


If the request headers are present then we attempt to authorize the request.
The timestamp is checked to ensure it conforms within the configurable boundaries of the server clock.
The nonce value is checked to ensure it is unique
The user is loaded and we iterate over their session tokens and attempt to verify the request token using the shared secret.
The user may have multiple sessions active at one time due to using different devices. They are sorted based on most recently used for efficiency.

Testing Signed Request Authorization



1. In src/main/resources/properties/app.properties set the following property

security.authorization.requireSignedRequests=true


2. Start the application by executing

gradle tomcatRun


3. Create a user with the curl statement

curl -v -H "Content-Type: application/json" -X POST -d '{"user": {"firstName":"Foo","lastName":"Bar","emailAddress":"foobar@example.com"}, "password":"password"}' http://localhost:8080/java-rest/user


4. Construct a signed request to call user/id GET using the returned AuthenticatedUserToken
{"userId":"ff7b93ad-27d0-49f6-90bd-9937951e5fcc","token":"6eebc0ea-b637-4033-925b-3b3cba9880e4"}
First the string to hash:

  • 6eebc0ea-b637-4033-925b-3b3cba9880e4 (the session token)
  • user/ff7b93ad-27d0-49f6-90bd-9937951e5fcc (the resource url)
  • GET (The Http Method)
  • 2013-01-30T10:50:00+00:00 (Timestamp)
  • 22e327d732 (nonce value)

The full string
6eebc0ea-b637-4033-925b-3b3cba9880e4:user/ff7b93ad-27d0-49f6-90bd-9937951e5fcc,GET,2013-01-30T10:50:00+00:00,22e327d732

Hash the string using SHA-256 and then Base64 encode the result. There is a utility class at com.incept5.rest.util.HashUtil that you can use.
This should render a result similar to
ncYoA5n5s2nFSm7qyvf5hDgL4pmmPOUP3zo/UYfaQKg=

5. Construct the curl statement with relevant headers which should look similar to the following

curl -v -H "Content-Type: application/json" -H "Authorization: ff7b93ad-27d0-49f6-90bd-9937951e5fcc:ncYoA5n5s2nFSm7qyvf5hDgL4pmmPOUP3zo/UYfaQKg=" -H "x-java-rest-date:2013-01-30T10:50:00+00:00" -H "nonce:22e327d732" -X GET -d localhost http://localhost:8080/java-rest/user/ff7b93ad-27d0-49f6-90bd-9937951e5fcc


After executing the curl statement you should get back something like:

< HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Wed, 30 Jan 2013 10:48:56 GMT {"id":"ff7b93ad-27d0-49f6-90bd-9937951e5fcc","firstName":"Foo","lastName":"Bar","emailAddress":"foobar@example.com","socialProfiles":[],"name":"foobar@example.com","verified":false}


Tampering with any of the header values or resubmitting the request should result in a failure such as

< HTTP/1.1 403 Forbidden < Server: Apache-Coyote/1.1 < Content-Type: application/json < Transfer-Encoding: chunked < Date: Wed, 30 Jan 2013 10:52:26 GMT {"errorCode":"40301","consumerMessage":"Not authorized","applicationMessage":"Nonce value is not unique"}


The application has some javascript functions for creating signed requests on the client side (see src/main/webapp/js/javarest.js)
That now covers everything that you need to create and manage simple user accounts in a REST API. The next post will focus on configuring the application for production

Friday, January 25, 2013

Writing REST services in Java: Part 5 Lost Password

Previous Post : Part Four: Facebook Authentication
Get the Code: https://github.com/iainporter/rest-java

Password reset is similar to email registration covered in Part Three. The essential parts involve generating a short-lived unique token, emailing it to the user and handling the return of the token.
  • User clicks on lost password link
  • User enters their email address and submits
  • The server generates a short-lived token and sends email to user address with the Base64 encoded token in an embedded link
  • User clicks on link (or pastes it into browser window)
  • User enters new password which is submitted along with the token to the server
  • Server validates the token and password and matches it up to the User
  • Password is hashed and saved to User account

The Verification Token


The main properties of a VerificationToken are:

  • token - a UUID that is used to identify the token. It is Base64 encoded before being sent
  • expiryDate - time to live for the Token. Configured in app.properties
  • tokenType - enum (lostPassword, emailVerification, emailRegistration)
  • verified - has this token been verified


Verification Token Service


The method for generating and sending the token

    /**
     * generate token if user found otherwise do nothing
     *
     * @param emailAddress
     * @return  a token or null if user not found
     */
    @Transactional
    public VerificationToken sendLostPasswordToken(String emailAddress) {
        Assert.notNull(emailAddress);
        VerificationToken token = null;
        User user = userRepository.findByEmailAddress(emailAddress);
        if (user != null) {
            token = user.getActiveLostPasswordToken();
            if (token == null) {
                token = new VerificationToken(user, VerificationToken.VerificationTokenType.lostPassword,
                        config.getLostPasswordTokenExpiryTimeInMinutes());
                user.addVerificationToken(token);
                userRepository.save(user);
            }
            emailServicesGateway.sendVerificationToken(new EmailServiceTokenModel(user, token, getConfig().getHostNameUrl()));
        }

        return token;
    }

First, find the user by Email Address (line 11). If there is no account matching the address then we don't want to throw an exception but just ignore processing. We could wire in some logic to send an email telling the user that they attempted to change their password but their account does not exist. The main reason for the obfuscation is to prevent malicious trolling of the application to determine if a particular email account is registered.
Once a new token is generated it is passed off for asynchronous processing to the email services gateway.

Email Services Gateway


The service gateway uses Spring Integration to route email tasks. The task is first queued to guarantee delivery and marks the thread boundary of the calling process.
<int:gateway id="emailServicesGateway" service-interface="com.porterhead.rest.gateway.EmailServicesGateway"
                 default-reply-timeout="3000">
        <int:method name="sendVerificationToken" request-channel="emailVerificationRouterChannel"
                    request-timeout="3000"/>
    </int:gateway>


A router polls the queue and routes the email task to the appropriate service.
    <int:channel id="emailVerificationRouterChannel">
        <int:queue capacity="1000" message-store="emailVerificationMessageStore"/>
    </int:channel>

    <int:router id="emailVerificationRouter" input-channel="emailVerificationRouterChannel"
                expression="payload.getTokenType()">
        <int:poller fixed-rate="2000">
            <int:transactional/>
        </int:poller>
        <int:mapping value="emailVerification" channel="emailVerificationTokenSendChannel"/>
        <int:mapping value="emailRegistration" channel="emailRegistrationTokenSendChannel"/>
        <int:mapping value="lostPassword" channel="emailLostPasswordTokenSendChannel"/>
    </int:router>

    <int:channel id="emailLostPasswordTokenSendChannel"/>
    <int:service-activator id="emailLostPasswordSenderService" input-channel="emailLostPasswordTokenSendChannel"
                           output-channel="nullChannel" ref="mailSenderService"
                           method="sendLostPasswordEmail">
    </int:service-activator>



Mail Sender Service


The service loads a velocity template and merges it with the email token model

    public EmailServiceTokenModel sendLostPasswordEmail(final EmailServiceTokenModel emailServiceTokenModel) {
        Map<String, String> resources = new HashMap%lt;String, String>();
         return sendVerificationEmail(emailServiceTokenModel, config.getLostPasswordSubjectText(),
                 "META-INF/velocity/LostPasswordEmail.vm", resources);
    }


When the template has been merged the email is sent using JavaMailSender

    private EmailServiceTokenModel sendVerificationEmail(final EmailServiceTokenModel emailVerificationModel, final String emailSubject,
                                                         final String velocityModel, final Map<String, String> resources) {
        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws Exception {
                MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, MimeMessageHelper.MULTIPART_MODE_RELATED, "UTF-8");
                messageHelper.setTo(emailVerificationModel.getEmailAddress());
                messageHelper.setFrom(config.getEmailFromAddress());
                messageHelper.setReplyTo(config.getEmailReplyToAddress());
                messageHelper.setSubject(emailSubject);
                Map model = new HashMap();
                model.put("model", emailVerificationModel);
                String text = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, velocityModel, model);
                messageHelper.setText(new String(text.getBytes(), "UTF-8"), true);
                      for(String resourceIdentifier: resources.keySet()) {
                   addInlineResource(messageHelper, resources.get(resourceIdentifier), resourceIdentifier);
                }
            }
        };
        LOG.debug("Sending {} token to : {}",emailVerificationModel.getTokenType().toString(), emailVerificationModel.getEmailAddress());
        this.mailSender.send(preparator);
        return emailVerificationModel;
    }


Password Resource Controller

The controller is a simple pass through to the Verification Token Service. Note that we always return 200 to the client regardless of whether an email address was found or not.

    @PermitAll
    @Path("tokens")
    @POST
    public Response sendEmailToken(LostPasswordRequest request) {
        verificationTokenService.sendLostPasswordToken(request.getEmailAddress());
        return Response.ok().build();
    }


Handling the Reset Request


The email sent to the user should contain a link to a static page so the user can input their new password. This, along with the token, is sent to the server.

    @PermitAll
    @Path("tokens/{token}")
    @POST
    public Response resetPassword(@PathParam("token") String base64EncodedToken, PasswordRequest request) {
        verificationTokenService.resetPassword(base64EncodedToken, request.getPassword());
        return Response.ok().build();
    }


Again this a pass through to the Verification Token service.

    @Transactional
    public VerificationToken resetPassword(String base64EncodedToken, String password) {
        Assert.notNull(base64EncodedToken);
        validate(passwordRequest);
        VerificationToken token = loadToken(base64EncodedToken);
        if (token.isVerified()) {
            throw new AlreadyVerifiedException();
        }
        token.setVerified(true);
        User user = token.getUser();
        user.setHashedPassword(user.hashPassword(password));
        //set user to verified if not already and authenticated role
        user.setVerified(true);
        if (user.hasRole(Role.anonymous)) {
            user.setRole(Role.authenticated);
        }
        userRepository.save(user);
        return token;
    }


The token is matched and if it has already been verified an exception is thrown.
The user's password is hashed and reset.

Testing the API


See Part Three for configuring email settings and spring profiles
Start the application by executing:

gradle tomcatRun



Create a new user with a curl request

curl -v -H "Content-Type: application/json" -X POST -d '{"user":{"firstName":"Foo","lastName":"Bar","emailAddress":"<your email address>"}, "password":"password"}' http://localhost:8080/java-rest/user


Send a password reset request using curl

curl -v -H "Content-Type: application/json" -X POST -d '{"emailAddress":"<your email address>"}' http://localhost:8080/java-rest/password/tokens


Clicking on the link in the email will take you to a static page served from web-app.
Enter a new password and submit or alternatively cut and paste the token and use a curl statement

curl -v -H "Content-Type: application/json" -X POST -d '{"password":"password123"}' http://localhost:8080/java-rest/password/tokens/<your token>


To test that it worked you can use the login page at http://localhost:8080/java-rest/index.html or use curl

curl -v -H "Content-Type: application/json" -X POST -d '{"username":"<your email address>","password":"password123"}' http://localhost:8080/java-rest/user/login


You can also go through the complete cycle using the simple web pages provided.

So far in the series I have covered

  • User sign up and login with email
  • User sign up and login with OAuth
  • Email Verification
  • Lost Password

The next posts will focus on accessing role-based resources and session handling.

Monday, January 21, 2013

Writing REST services in Java: Part 4 Facebook Authentication

Previous Post : Part Three: Email Verification
Get the Code: https://github.com/iainporter/rest-java

This post will cover OAuth Authentication with Facebook. Most REST applications provide a user with a choice of signing up with their email address and password or to delegate to a third-party security provider such as Facebook, Twitter, Google, etc. The solution in the rest-java application uses Javascript libraries on the client side for the oauth dance and Spring Social on the server for communicating with the Facebook social graph API.

 Client Code

Whether the client is a web page or a mobile device the desired end result is the same: to get an access token from the Facebook API.

The client code in scr/main/webapp/index.html:


window.fbAsyncInit = function() {
     FB.init({
       appId      : '133718006790561', // App ID
       status     : true, // check login status
       cookie     : true, // enable cookies to allow the server to access the session
       xfbml      : true  // parse XFBML
      });

      $('.fb').on('click', function () {

      FB.login(function(response) {
         console.log(response)
         if (response.authResponse) {
            javaRest.user.loginSocial(response.authResponse.accessToken, 
               function (error) {
               if (error)
                  console.log(error)
               else
                  window.location = 'dashboard.html'
               })
               console.log('Welcome!  Fetching your information.... ');
               FB.api('/me', function(response) {
                   console.log('Good to see you, ' + response.name + '.');
               });
          } else {
             console.log('User cancelled login or did not fully authorize.');
          }
       });

    })

 };
        
// Load the SDK Asynchronously
(function(d){
    var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement('script'); js.id = id; js.async = true;
    js.src = "//connect.facebook.net/en_US/all.js";
    ref.parentNode.insertBefore(js, ref);
}(document));
When registering the app with Facebook we insert the appId in line 03.
Facebook makes a callback with an authorization popup window.
The user accepts and we receive back an access token which we then use to call the API (line 14).

Server API Endpoint


Once the client has an access token they then call the server API which has an endpoint of:

user/login/<providerId>

In this case the providerId is facebook

The json payload is simply {"accessToken":"<the User's Facebook access token>"}

On the server side we want to achieve three things:

1. Access the user's Facebook profile and persist some relevant details
2. Match the profile up with an existing user account or else create a new one
3. Return an Authenticated User Token for API access so that we can treat Facebook logins the same as an email login

User Resource


    @PermitAll
    @Path("login/{providerId}")
    @POST
    public Response socialLogin(@PathParam("providerId") String providerId, OAuth2Request request) {
        OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory<?>) connectionFactoryLocator.getConnectionFactory(providerId);
        Connection<?> connection = connectionFactory.createConnection(new AccessGrant(request.getAccessToken()));
        AuthenticatedUserToken token = userService.socialLogin(connection);
        return getLoginResponse(token);
    }
We get a ConnectionFactory using the ConnectionFactoryLocator that was configured in SocialConfig.java
The factory class then creates a connection using the access token and the service method is called to login the user.

SocialConfig.java

@Configuration
public class SocialConfig {

    @Autowired
    ApplicationConfig config;

    @Autowired
    SocialUserRepository socialUserRepository;

    @Autowired
    UserRepository userRepository;

    @Autowired
    TextEncryptor textEncryptor;

    @Bean
    public ConnectionFactoryLocator connectionFactoryLocator() {
        ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
        registry.addConnectionFactory(new FacebookConnectionFactory(
            config.getFacebookClientId(),
            config.getFacebookClientSecret()));
        return registry;
    }

    @Bean
    public UsersConnectionRepository usersConnectionRepository() {
        JpaUsersConnectionRepository usersConnectionRepository = new 
                JpaUsersConnectionRepository(socialUserRepository, userRepository,
                connectionFactoryLocator(), textEncryptor);

        return usersConnectionRepository;
    }
}

Social User Domain Model


A User has a many-to-one relationship with SocialUser instances. In the SocialUser model we need to capture several important pieces of information such as:

  • providerId - the security implementation provider (facebook)
  • providerUserId - the identifier within the Facebook universe
  • accessToken - the key needed to access the user's profile in Facebook (encrypted in the DB)
We can also capture other useful information such as profileImage, profileUrl, displayName, etc.

Note that we cannot guarantee to get an email address.

User Service


The implementation method for social login is:

@Transactional
public AuthenticatedUserToken socialLogin(Connection<?> connection) {
     List<String> userUuids = 
          jpaUsersConnectionRepository.findUserIdsWithConnection(connection);
     if(userUuids.size() == 0) {
         throw new AuthenticationException();
     }
     User user = userRepository.findByUuid(userUuids.get(0));
     if (user == null) {
         throw new AuthenticationException();
     }
     updateUserFromProfile(connection, user);
     return new AuthenticatedUserToken(user.getUuid().toString(), user.addSessionToken().getToken());
}


The JpaUsersConnectionRepository is used to connect to Facebook and retrieve the User's profile. It then matches it up to an existing user or else creates a new one if this is the first login attempt.
Once we have an API User we then create or update a SocialUser profile and associate it with the User.

JpaUsersConnectionRepository


The relevant method:

/**
* Find User with the Connection profile (providerId and providerUserId)
* If this is the first connection attempt there will be nor User so create one and
* persist the Connection information
* In reality there will only be one User associated with the Connection
*
* @param connection
* @return List of User Ids (see User.getUuid())
*/
public List<String> findUserIdsWithConnection(Connection<?> connection) {
    List<String> userIds = new ArrayList<String>();
    ConnectionKey key = connection.getKey();
    List<SocialUser> users = 
              socialUserRepository.findByProviderIdAndProviderUserId
             (key.getProviderId(), key.getProviderUserId());
    if (!users.isEmpty()) {
        for (SocialUser user : users) {
            userIds.add(user.getUser().getUuid().toString());
        }
        return userIds;
    }
//First time connected so create a User account or find one that is already 
//created with the email address
    User user = findUserFromSocialProfile(connection);
    String userId;
    if(user == null) {
       userId = userService.createUser(Role.authenticated).getId();
    } else {
       userId = user.getUuid().toString();
    }
    //persist the Connection
    createConnectionRepository(userId).addConnection(connection);
    userIds.add(userId);
    return userIds;
}

private User findUserFromSocialProfile(Connection connection) {
    User user = null;
    UserProfile profile = connection.fetchUserProfile();
    if(profile != null && StringUtils.hasText(profile.getEmail())) {
        user = userRepository.findByEmailAddress(profile.getEmail());
    }
    return user;
}

Testing the API



Step 1. Create an application on Facebook and if you want to test it locally set the site url to http://localhost:8080/java-rest
















Step 2. Insert your App Id and App Secret in src/main/resources/app.properties
Step 3. Insert your appId in to the relevant place in the javascript in src/main/webapp/index.html and src/main/webapp/signup.html
Step 4. Start the application by running :
gradle tomcatRun


Step 5. In a browser window request http://localhost:8080/java-rest/index.html

You should be able to login with your Facebook credentials

This can easily be extended to include other OAuth providers such as Twitter or Google
That wraps up this post.
In the next post I will cover the Lost Password API.

Monday, January 14, 2013

Writing REST Services in Java: Part 3 Email Verification

Previous Post : Part Two: User sign up and login  Next PostPart Four: Facebook Authentication  
Get the Code: https://github.com/iainporter/rest-java

In this post I'll discuss the process of verifying an email address after a new sign up. The logic flow for this is:

1. Generate a token for the user and persist it
2. Send an email with an embedded link that includes the token to the user
3. User clicks on the link which takes them to a static page
4. The static page calls the API passing the token
5. If the token is valid the user is set to verified
6. Response returned to the user

The Verification Token Service

This service is responsible for generating tokens, persisting them, communicating with the email services gateway and verifying returned tokens. The main properties of a VerificationToken are:
  • token - a UUID that is used to identify the token. It is Base64 encoded before being sent
  • expiryDate - time to live for the Token. Configured in app.properties
  • tokenType - enum (lostPassword, emailVerification, emailRegistration)
  • verified - has this token been verified

Looking at the method to send an Email Registration token:


    @Transactional
    public VerificationToken sendEmailRegistrationToken(String userId) {
        User user = ensureUserIsLoaded(userId);
        VerificationToken token = new VerificationToken(user,
                VerificationToken.VerificationTokenType.emailRegistration,
                config.getEmailRegistrationTokenExpiryTimeInMinutes());
        user.addVerificationToken(token);
        userRepository.save(user);
        emailServicesGateway.sendVerificationToken(new EmailServiceTokenModel(user,
                token, getConfig().getHostNameUrl()));
        return token;
    }

A new token is generated and persisted.
This token is then sent to the EmailServicesGateway where it is queued and the method returns, leaving the gateway to handle processing of the token asynchronously.

The Email Services Gateway

Email services are implemented with Spring Integration. The context file is at:

 src/main/resources/META-INF/spring/email-services-context.xml

The entry point is the Gateway component which routes to a queue-backed channel. This queue has a message store which in dev and local profiles uses a SimpleMessageStore. For staging and production a datasource is used. I'll cover setting up for production in a later post.

    <int:gateway id="emailServicesGateway" 
           service-interface="com.porterhead.rest.gateway.EmailServicesGateway"
           default-reply-timeout="3000">
    <int:method name="sendVerificationToken" 
            request-channel="emailVerificationRouterChannel" request-timeout="3000"/>
    </int:gateway>

    <int:channel id="emailVerificationRouterChannel">
        <int:queue capacity="1000" message-store="emailVerificationMessageStore"/>
    </int:channel>

A router polls the queue and routes to a channel based on the token type:


<int:router id="emailVerificationRouter" input-channel="emailVerificationRouterChannel"
   expression="payload.getTokenType()">
     <int:poller fixed-rate="2000">
        <int:transactional/>
     </int:poller>
     <int:mapping value="emailVerification" 
          channel="emailVerificationTokenSendChannel"/>
     <int:mapping value="emailRegistration"
          channel="emailRegistrationTokenSendChannel"/>
     <int:mapping value="lostPassword" channel="emailLostPasswordTokenSendChannel"/>
</int:router>

From there the appropriate MailSender method is invoked:


<int:service-activator id="emailRegistrationMailSenderService" 
      input-channel="emailRegistrationTokenSendChannel"
      output-channel="nullChannel" ref="mailSenderService"
      method="sendRegistrationEmail">
</int:service-activator>

The Mail Sender Service

The MailSenderService is responsible for constructing mime messages and sending them to their destination. Velocity is used as the template engine. The templates are in:

 src/main/resources/META-INF/velocity

The handler method:

    private EmailServiceTokenModel sendVerificationEmail(
                final EmailServiceTokenModel emailVerificationModel,
                final String emailSubject, 
                final String velocityModel, 
                final Map<String, String> resources) {
        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws Exception {
              MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,
              MimeMessageHelper.MULTIPART_MODE_RELATED, "UTF-8");
              messageHelper.setTo(emailVerificationModel.getEmailAddress());
              messageHelper.setFrom(config.getEmailFromAddress());
              messageHelper.setReplyTo(config.getEmailReplyToAddress());
              messageHelper.setSubject(emailSubject);
              Map model = new HashMap();
              model.put("model", emailVerificationModel);
              String text = 
                 VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, 
                 velocityModel, model);
                messageHelper.setText(new String(text.getBytes(), "UTF-8"), true);
                    for(String resourceIdentifier: resources.keySet()) {
                       addInlineResource(messageHelper, 
                       resources.get(resourceIdentifier),
                      resourceIdentifier);
                    }
              }
          };
        LOG.debug("Sending Verification Email to : {}",
                   emailVerificationModel.getEmailAddress());
        this.mailSender.send(preparator);
        return emailVerificationModel;
    }
The velocity templates are just bare-bones and will need to be customised to your needs.

Handling the Verification Request


The controller method passes through to the Verification Token Service

@PermitAll
    @Path("tokens/{token}")
    @POST
    public Response verifyToken(@PathParam("token") String token) {
        verificationTokenService.verify(token);
        return Response.ok().build();
    }


The service method to verify the token

    @Transactional
    public VerificationToken verify(String base64EncodedToken) {
        VerificationToken token = loadToken(base64EncodedToken);
        if (token.isVerified() || token.getUser().isVerified()) {
            throw new AlreadyVerifiedException();
        }
        token.setVerified(true);
        token.getUser().setVerified(true);
        userRepository.save(token.getUser());
        return token;
    }

    private VerificationToken loadToken(String base64EncodedToken) {
        Assert.notNull(base64EncodedToken);
        String rawToken = new String(Base64.decodeBase64(base64EncodedToken));
        VerificationToken token = tokenRepository.findByToken(rawToken);
        if (token == null) {
            throw new TokenNotFoundException();
        }
        if (token.hasExpired()) {
            throw new TokenHasExpiredException();
        }
        return token;
    }


The token is decoded and matched from the repository to an existing user. If that user has not already been verified then they are set to verified.

Testing the API

Before we can test the API the email service has to be configured. The context file containing the MailSender bean is at:

src/main/resources/META-INF/spring/email-template-context.xml

Choose a Mail Sender and insert the values into the mail properties. You can use a gmail or yahoo account for testing or there are several useful bulk mail sending options such as CritSend or MailJet.

<beans profile="production, staging">
    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
      <!-- Email provider here-->
      <property name="host" value="<insert your Host name here>"/>
      <property name="port" value="<insert the port here>"/>
      <property name="username" value="<insert your username here>"/>
      <property name="password" value="<insert your password>"/>

        <property name="javaMailProperties">
          <props>
           <prop key="mail.debug">false</prop>
           <prop key="mail.smtp.auth">true</prop>
           <prop key="mail.smtp.starttls.enable">true</prop>
          </props>
        </property>
   </bean>

As you can see only the staging and production profiles are set up to send real mail. When running the dev and local profiles the MockJavaMailSender is invoked which simply stores the Mime messages in memory. To test out sending mail we can temporarily add the dev profile:

<beans profile="dev, production, staging">

and remove it from the other beans profile:

<beans profile="local">

Now start the application by executing:

gradle tomcatRun

Create a new user with the following curl statement (substituting in your real email address):

curl -v -H "Content-Type: application/json" -X POST -d '{"user":{"firstName":"Foo","lastName":"Bar","emailAddress":"<your email address>"}, "password":"password"}' http://localhost:8080/java-rest/user

You should then receive an email with the subject:
"Welcome to the Java-REST sample application"
This is configurable in app.properties although that will be covered in a later post.

You can either click on the link in the email and this will invoke a static page in the application that will forward on the token to another API call to verify the token. Alternatively you can copy the token in the link and construct a curl statement to do the same thing.

A sample curl to verify the token (substitute the token in your email):

curl -v -H "Content-Type: application/json"  -X POST -d  localhost http://localhost:8080/java-rest/verify/tokens/NjM1NjBkNTAtNjEwZi00N2I3LTk0MmQtYWMwMjVkY2MwNTc1

You should see this response:

< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: application/json
< Content-Length: 0
< Date: Mon, 14 Jan 2013 08:30:18 GMT

If you execute the curl again you will receive a 409 response with a message saying the token has already been verified:

< HTTP/1.1 409 Conflict
< Server: Apache-Coyote/1.1
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Mon, 14 Jan 2013 08:30:58 GMT
{"errorCode":"40905","consumerMessage":"Already verified","applicationMessage":"The token has already been verified"}

What if a user changes their email address? In that case we should resend a verification email and have them verify their email address again. In the user/<id> PUT resource handler that is exactly what happens:

    @RolesAllowed({"authenticated"})
    @Path("{userId}")
    @PUT
    public Response updateUser(@Context SecurityContext sc, @PathParam("userId") 
                 String userId, UpdateUserRequest request) {
        ExternalUser userMakingRequest = (ExternalUser)sc.getUserPrincipal();
        if(!userMakingRequest.getId().equals(userId)) {
            throw new AuthorizationException("User not authorized to 
               modify this profile");
        }
        boolean sendVerificationToken = 
                 StringUtils.hasLength(request.getEmailAddress()) &&
                !request.getEmailAddress().equals(userMakingRequest.getEmailAddress());
        ExternalUser savedUser = userService.saveUser(userId, request);
        if(sendVerificationToken) {
            verificationTokenService.sendEmailVerificationToken(savedUser.getId());
        }
        return Response.ok().build();
    }
This is an access controlled resource which will be covered in a Part six: security and authorization.

The next post will cover Facebook authentication.






Saturday, January 12, 2013

Writing REST Services in Java: Part 2 User sign up and Login

Previous Post : Part One: Introduction   Next Post : Part Three: Email Verification
Get the Code: https://github.com/iainporter/rest-java

In this post I will walk through the sign up and login services to demonstrate a vertical slice through the application. I will briefly discuss email verification and Social signup but won't delve into that until the next post. Session handling is also deferred until a later post.

User Sign up

To create a User we need to persist some basic information. At a bare minimum we need an email address and a password. Optionally we would like to capture perhaps a first and last name. 
Once a user has signed up we need them to verify their email address. This is important foremost so that we can uniquely identify the user and provide the ability for them to safely reset their password, as well as other security features that depend on a valid email address being provided.

Most services will follow a similar development pattern to this one which is:

1. Define the domain model
2. Write the Repository interface to persist and retrieve the domain objects
3. Write a Service class to map the API object to the entity and CRUD logic to integrate with the Repository, as well as any other services in support of the particular API.
4. Define the API 
5. Write the Resource Controller

The unit tests will test each layer of the application and the Integration Tests will test the full end to end calls through the application.

The User Domain Object

Entities are defined in the com.incept5.rest.model package. Hibernate annotations are used to manage the joins and Spring Data to manage some of the basic functions common to all entities. All entities have an additional identifier which is a UUID. Some people have a preference to just use a sequential Long to identify an entity. In this application the primary keys are Longs but UUIDs are used in the API calls to persist and retrieve entities.

It comes down to whether you want your urls to look like this:

/user/1

or like this:

/user/38e1e415-acd2-42bb-8a3f-594f13e0d4ea




All entities that we wish to persist are annotated with:

@Entity
@Table(name="table name")

The class is added to the list of known entities in src/main/resources/META-INF/persistence.xml

The beans responsible for persistence are defined in src/main/resources/META-INF/spring/data-context.xml. There are bean profiles for dev, local, staging and production.

The User Repository

The repository tier uses Spring Data which is an excellent framework that takes care of most of the boilerplate headaches with CRUD functionality. The UserRepository class extends the JPARepository interface and has a few custom finder methods defined.

The User Service

The UserService interface defines a method for creating a new User:

public AuthenticatedUserToken createUser(CreateUserRequest request, Role role);

All entities remain within the boundary of the service layer and API objects pass between the Controllers and the services. This allows us to tightly define the contract between clients and the API. All request and responses are defined in the API package. It also prevents potential transaction and hibernate issues by accessing entity objects outside of a transaction. We can also pass API objects to multiple services without worrying about whether that service knows how to persist a particular entity.

It is worth stepping through the implementation of createUser as it will highlight several key features of how service methods are composed.

The method:

@Transactional
public AuthenticatedUserToken createUser(CreateUserRequest request, Role role) {
   if (!request.validate()) {
            throw new ValidationException("The CreateUserRequest was invalid");
   }
   User searchedForUser =  userRepository.findByEmailAddress(request.getUser().getEmailAddress());
   if (searchedForUser != null) {
        throw new DuplicateUserException();
   }

   User newUser = createNewUser(request, role);
   return new AuthenticatedUserToken(newUser.getUuid().toString(), newUser.addSessionToken().getToken());
}


First the API request is validated and if it fails an exception is thrown. This is a common pattern in service methods; to fail fast on an error. All Exceptions extend BaseWebApplicationException which defines an error payload for the response and an appropriate Http status code. In this particular case it will be a 400 error and in the case just below a 409 will be returned for the DuplicateUserException.

If no duplicate is found the a User entity is mapped from the request object and persisted.
Note that the password is saved as a one-way hash salted with the UUID of the User.

Finally an AuthenticatedUserToken object is mapped from the created entity and returned from the method.

See com.incept5.rest.service.UserServiceTest for the full suite of tests to run against this service.

The User Resource Controller

Resource classes are responsible for marshalling and unmarshalling client requests and responses and coordinating calls to services. Resource classes do not access entities but instead use objects in the com.incept5.rest.api package to communicate with services.

There are some JAX-RS annotations to map urls to the correct resource:


@Path("/user")
@Component
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})

The method for sign up is:


    @PermitAll
    @POST
    public Response signupUser(CreateUserRequest request) {
        AuthenticatedUserToken token = userService.createUser(request, Role.authenticated);
        verificationTokenService.sendEmailRegistrationToken(token.getUserId());
        URI location = uriInfo.getAbsolutePathBuilder().path(token.getUserId()).build();
        return Response.created(location).entity(token).build();
    }

We specify that it is a POST and the @PermitAll means that the method is not access controlled. In a later post I will walk through customising a sign up for different types of users (customer, merchant, etc). The general sign up just assigns an authenticated role to a new user.

The logic flow is:

1. Call the User Service to handle the create request. If it is successful an API object will be returned with details of the new User.
2. Call the Email Verification Service to send a welcome email along with an embedded link to verify the email address. The details of this will be discussed in the next post. Note that this an asynchronous call and will return as soon as the Verification Service puts the request onto its queue.
3. A location uri for the new resource is created. This takes the form of user/<uuid>. We will see this in the next section when the API is tested with a curl request.
4. The response is gathered and returned. The Http response will be created (201) and the body will contain an AuthenticatedUserToken. Again I will go into more detail about that later.

Testing the API for User sign up

There are in-memory unit tests in the com.incept5.rest.resource package for testing the Resource classes  but the surest way to test it is by running the code in a servlet container and firing real requests at it.
The integration tests are in the src/test/groovy directory. The tests are written in groovy and executed using the groovy rest client. UserIntegrationTest has tests for both failure and success scenarios.

Testing the API with curl

To test with curl we need to start up the war in a servlet container. The default profile in gradle.properties is dev but if we wanted to test against a real database and a real mail sender we could change that to local or staging and configure the necessary beans. I will discuss how to do that later.
Execute the following command:

gradle tomcatRun

Once the war has finished loading we can execute the following curl request:

curl -v -H "Content-Type: application/json" -X POST -d '{"user":{"firstName":"Foo","lastName":"Bar","emailAddress":"<your email address here>"}, "password":"password"}' http://localhost:8080/java-rest/user

You should see a response like this:

< HTTP/1.1 201 Created
< Server: Apache-Coyote/1.1
< Location: http://localhost:8080/java-rest/user/ff7ffcf0-cfe0-4c1e-9971-3b934612b154
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 12 Jan 2013 09:43:13 GMT
<  * Connection #0 to host localhost left intact
{"userId":"ff7ffcf0-cfe0-4c1e-9971-3b934612b154","token":"5d1cf32e-e468-4a2c-92b6-9b0c377145f9"}

I'll discuss how to use the response in the next post.

Now that we know sign up works we can test out login.
Try the following curl statement:

curl -v -H "Content-Type: application/json" -X POST -d '{"username":"<your email address here>","password":"password"}' http://localhost:8080/java-rest/user/login


The response:



< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Location: http://localhost:8080/java-rest/user/7ef646ba-5e03-4d59-90a9-822daecf3428
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 12 Jan 2013 09:52:21 GMT
<
* Connection #0 to host localhost left intact
{"userId":"7ef646ba-5e03-4d59-90a9-822daecf3428","token":"164797a0-623f-427d-a469-b348e7e2bc59"}

That covers user sign up and login. In the next post I will discuss the email verification process.