As we move into the third article in this series, we will add in the bread and butter of what Akka does, actors to perform the processing of information for our endpoints. We also cover authentication and how to lock down some of our endpoints. While it seems like a lot, I actually found it easier than Spring to pick up the first time. Albeit there was a little bit more code to write. This a pretty fast leap so if you feel a bit overwhelmed with what is being done or something is not making sense, take a look at "Akka HTTP - A Beginning" or "REST and Postgres and Testing, Oh My!" and as always feel free to look at the full code for this series here. So without further delay let us jump right in.

So first thing as before, I will go through some of the new dependencies we added. This week we added two major sets. BCrypt was introduced to encrypt/decode our passwords. Then for authentication we added in JJWT for processing our JSON Web Tokens.

<!-- Spring BCrypt Dependencies -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <!-- End of Spring BCrypt Dependencies -->

        <!-- JSON Web Token Dependencies -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.1</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.1</version>
            <scope>runtime</scope>
        </dependency>
        <!-- End of JSON Web Token Dependencies -->

I know, I know, I went with Spring Security to use BCrypt, but it is an up-to-date library and it provides security. If for some reason our database ever got hacked we would want our users passwords to remain a mystery to the thief or at least make it as painful as possible to extract the information. For our JSON Web Tokens, we went with the JJWT library because it is also kept up-to-date and has incorporated features to prevent many of the downfalls of JWT's.

We did add an application.conf file to our project. This serves as a point for the actor system to get configurations from. We can create custom configurations, as well as, set up some Akka specific ones like demonstrated here. For our example, we added in a custom dispatcher to be used with our actors making database calls and also our API Secret that will be used for encoding/decoding the JWT's.

Since we are locking down our endpoints we will lose the ability to add users without having a user added at run time. So here is the default user to the program.

route-blocking-dispatcher {
    type = Dispatcher
    executor = "thread-pool-executor"
    thread-pool-executor {
        fixed-pool-size = 176
    }
    throughput = 1
}

api-secret = "Your Secret Code"

admin {
    username = "admin"
    password = "Your Password"
}

After this, I just created a simple class to hold a JSON Web Token String. It simply makes it easier to serialize/deserialize the token and to define it more explicitly for passing it around the program as needed. Notice that we follow the same pattern we used previously for using it with Jackson.

package com.pelaghisoftware.server.auth;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * Object containing a JWT for easy 
 * Serialization/Deserialization to JSON
 */
public class JWTObject
{
    public String jwt;

    /**
     * Base Constructor
     */
    public JWTObject(){};

    /**
     * Constructor to set the JWT
     * @param jwt A JWT
     */
    public JWTObject(@JsonProperty("jwt") String jwt)
    {
        this.jwt = jwt;
    }

    /**
     * Getter for a JWT
     * @return String. A JWT
     */
    @JsonGetter("jwt")
    public String getJwt()
    {
        return jwt;
    }

    /**
     * Setter for a JWT
     * @param jwt A JWT
     */
    public void setJwt(String jwt)
    {
        this.jwt = jwt;
    }
}

From here, I would think the best place to begin would be with AuthOperations. This is where we perform our JWT encoding/decoding, pull out the Authorization header from the HTTP requests and have a couple of messages that we can pass to our actors so that they can perform operations on them. As these methods are global helper methods, I took the liberty of making them static. I am sure there a million ways to do this, but this was the route I chose. I will break out the individual parts, but the entire class can be found here.

So at the very top you will notice that we get our secret key from the application.conf file. As we are using SHA-256, the key must be a minimum of 256 bits in length otherwise the program with throw an exception. This is a beautiful, built-in feature that prevents you from using a weak key.

//Secret Key used for encoding/decoding a JWT
private static String secretKey = ConfigFactory
	.load()
    .getString("api-secret");

Next we can create a JWT by passing in the id, issuer, subject, and the amount of time, in milliseconds, that the JWT is valid for. In a nutshell, it takes these values, sets them in a JWT, and then encodes it into a JWT string. If you are unfamiliar with JWT's, please take a look here to get more information.

/**
* Creates a JWT
* @param id The id for the JWT
* @param issuer The name of the issuer of the JWT
* @param subject The subject of the JWT
* @param ttlMillis Expiration time in Millis
* @return String. A JWT with the input parameters added.
*/
public static String createJWT(String id,
                               String issuer,
                               String subject,
                               long ttlMillis)
{
	//Gets the current Date and Time
	long nowMillis = System.currentTimeMillis();
	Date now = new Date(nowMillis);

	//Creates the secret key
	SecretKey key = getSecretKey();

	//Builds the JWT
	JwtBuilder builder = Jwts.builder().setId(id)
		.setIssuedAt(now)
		.setSubject(subject)
		.setIssuer(issuer)
		.signWith(key);

	//If there is an expiration, add it to the JWT
	if(ttlMillis > 0)
	{
		long expMillis = nowMillis + ttlMillis;
		Date exp = new Date(expMillis);
		builder.setExpiration(exp);
	}

	//Create the JWT String and return it.
	return builder.compact();
}

Now we need a way to decode a JWT sent by the user. the decodeJWT method takes in a JWT and does just that. Keep in mind that it will throw exceptions if the JWT cannot be decoded. This can happen, for instance, because the JWT is expired or because the user tampered with the JWT.

/**
* Decodes a JWT
* @param jwt A JWT to try decoding
* @return Claims from the JWT
* @throws UnsupportedJwtException
* @throws MalformedJwtException
* @throws SignatureException
* @throws ExpiredJwtException
* @throws IllegalArgumentException
*/
public static Claims decodeJWT(String jwt) throws UnsupportedJwtException,
                                                  MalformedJwtException,
                                                  SignatureException,
                                                  ExpiredJwtException,
                                                  IllegalArgumentException
{
	//Create the builder
	JwtParserBuilder parserBuilder = Jwts.parserBuilder();

	//Parse the JWT. Creates an exception if unsuccessful.
	Claims claims = parserBuilder.setSigningKey(getSecretKey())
		.build()
		.parseClaimsJws(jwt).getBody();

	return claims;
}

Since, in this example, we are expecting the JWT to be placed into a Bearer token within the Authorization header of the HTTP request, I created a helper that can extract the JWT from the request. It simply checks that first the authorization header is present and then that the value associated with it is a Bearer token. If everything is good to go, you will get back an Optional with the JWT inside, otherwise you will get back an empty Optional.

/**
* Function to get the Authorization Header from an HttpRequest
*/
public static final Function<HttpHeader, Optional<JWTObject>> getAuthorizationHeader = header ->
{
	//Checks if the authorization header is present
	if(header.is("authorization"))
	{
		//Checks to make sure the value is a Bearer token
		if(header.value().contains("Bearer") &&
		   header.value().split(" ")[0].equals("Bearer"))
		{
			//Get the JWT from the value
			String jwt = header.value().split(" ")[1];

			return Optional.of(new JWTObject(jwt));
		}
	}
	return Optional.empty();
};

Then we come to the main meat and potatoes of this class. We need our program to have some way to actually check whether a valid JWT was sent with the request and our authCheck method does just that. Without this or something similar it would be impossible to lock down an endpoint. This is also the first introduction to sending a message to an actor to perform some processing.

If we want to lock down an endpoint, we place a valid AuthResolver actor into the parameters. If it is null, we will skip the authentication process and the endpoint will not be protected. This method will send the JWTObject from the parameters to the authResolver ref to validate it. If it is valid, it will process the Supplier that was sent immediately following, otherwise it will bypass that and send an Unauthorized message to our ResponseResolver to send an Unauthorized response to the user.

Also notice that there are some asynchronous operations going on when performing an ask to an actor. Since the authentication check of the JWT must be done prior to moving on you will see a join call to make the program block and wait for the operation to complete. However you should notice that, the final result is returned without waiting and will be resolved elsewhere once the operation is completed.

/**
* Checks whether a request is authenticated and then returns a HttpResponse
* @param authResolver Actor to perform auth operations
* @param responseResolver Actor to create HttpResponses
* @param jwt JWT to check
* @param duration Time duration of async operations before they fail
* @param response A Supplier that provides the follow on operations after
*                 an auth check is successful
* @return CompletionStage a completable future that resolves to an HttpResponse
*/
public static final CompletionStage<HttpResponse> authCheck(ActorRef authResolver,
                                                            ActorRef responseResolver,
                                                            Optional<JWTObject> jwt,
                                                            Duration duration,
                                                            Supplier<CompletionStage<HttpResponse>> response)
{
	Boolean authCheck = false;
	
    //Check for if authentication should happen
	if(authResolver != null)
	{
		authCheck = ask(authResolver, jwt, duration)
			.thenApply(Boolean.class::cast)
			.toCompletableFuture()
			.join();
	}
	//Runs if no auth should be done
	else
	{
		authCheck = true;
	}

	CompletionStage<HttpResponse> result;

	//Run the follow on operations if auth is good to go
	if(authCheck)
	{
		result = response.get();
	}
	//Send Unauthorized message to the user if auth failed
	else
	{
		result = ask(responseResolver, 
                     new AuthOperations.Unauthorized(), 
                     duration)
			.thenApply(HttpResponse.class::cast);
	}

	return result;
}

Next we have a couple of messages that are used between the actors to make processing a bit easier and to allow for different paths of execution.

/**
* Message that contains a JWT
*/
public static class JwtMessage
{
	public JWTObject jwt;

	public JwtMessage(){}

	public JwtMessage(String jwt)
	{
		this.jwt = new JWTObject(jwt);
	}
}

/**
* Message for an unauthorized access
*/
public static class Unauthorized
{
	public ErrorMessage message = ErrorMessage.unauthorized();
	public StatusCode code = StatusCodes.UNAUTHORIZED;
}

And finally, the last method is a private helper that will decode our secret key into Base 64 and create a secret key. This is needed to prevent some code duplication when encoding/decoding the JWT.

/**
* Creates a SecretKey
* @return SecretKey. The api-secret for encoding/decoding a JWT
*/
private static SecretKey getSecretKey()
{
	byte[] secret = Base64.getDecoder().decode(secretKey);
	SecretKey key = Keys.hmacShaKeyFor(secret);

	return key;
}

With the hard work of authentication done, we will move into the three actors we created starting with the AuthResolver. Again, I will break it down by method so to view the entire class take a look here.

The first thing we will do is create our props and our constructor. We keep a private class level variable with the ActorRef to perform our SITE_USER table database operations. We have our props method which allow the actor system to instantiate an actor of this class.

private ActorRef siteUserAccessor;

/**
* Create props for a AuthResolver
* @param siteUserAccessor ActorRef to the SiteUserAccessor Actor
* @return Props. The props to initialize the actor in the actor system.
*/
public static Props props(ActorRef siteUserAccessor)
{
	return Props.create(AuthResolver.class, 
                        () -> new AuthResolver(siteUserAccessor));
}

/**
* Constructor
* @param siteUserAccessor ActorRef to the SiteUserAccessor Actor
*/
public AuthResolver(ActorRef siteUserAccessor)
{
	this.siteUserAccessor = siteUserAccessor;
}

We will cover the method used to receive messages in a minute, but first lets look at the two private helper methods in this class. Covering them first will help to make the createReceive method easier to understand.

The checkPassword method does just that. Using BCrypt it checks a password, the user provided, against an encoded password in the database. Basically we pass in two user objects and let BCrypt work it's magic by seeing if an encoded version of the input password matches the encoded version stored in the database. Likewise, if for some reason the user did not exist in the database, an empty Optional would be passed in and the method would return false.

/**
* Check an incoming user's password to the database
* @param user incoming user
* @param dbUser a known user in the Database
* @return Boolean. True if valid. Else False.
*/
private Boolean checkPassword(User user, Optional<User> dbUser)
{
	PasswordEncoder encoder = new BCryptPasswordEncoder();
	Boolean result = false;

	if(dbUser.isPresent())
	{
		result = encoder.matches(user.getEncryptedPassword(),
                                 dbUser.get().getEncryptedPassword());
	}

	return result;
}

After a check to see if the user is validated, we need to create a message so our ResponseResolver actor can either return a response with a JWT in the body or return an error response. Here we return a message that either has a JWT present in it, if the user logged in successfully, or no JWT if the attempt failed.

/**
* Create a JWT
* @param loginValid Boolean. login is valid is true
* @param username String. Username of the user to creat a JWT for
* @return Message with either a JWT, if valid, or empty if invalid
*/
private AuthOperations.JwtMessage createJWT(Boolean loginValid,
                                            String username)
{
	AuthOperations.JwtMessage result;

	//Only build the JWT if the user's login is valid.
	if(loginValid)
	{
		String jwt = AuthOperations.createJWT(UUID.randomUUID().toString(),
                                          	  "pelaghisoftware.com",
                             		          username, 
                               		          86400000);

		result = new AuthOperations.JwtMessage(jwt);
	}
	//Returns an object with no JWT
	else
	{
		result = new AuthOperations.JwtMessage();
	}

	return result;
}

Then we come full circle back to our createReceive method. Like I said before, any incoming messages to this actor get routed here.  The first case that we will handle is if a message is an instance of a User. We will take the message, check if the user exists in the database by asking the SiteUserAccessor to give us the user from the database, then we will check the password, try to get a JWT, or nothing if the login failed, and finally, since the this response is asynchronous, we will pipe the response back to the actor that originally sent the message.

The other case is if we receive an Optional we will try to check whether a JWT is valid and then send a message back to the actor that originally sent the message stating whether the JWT is valid or not. Remember previously, that an invalid JWT will throw a few exceptions, we catch them here and send back false because we know the JWT is invalid if we get an exception.

/**
* Runs when receiving a message
* @return
*/
@Override
public Receive createReceive()
{
	return receiveBuilder()
		//Check a login and return a JWT
		.match(User.class, value ->
		{
			CompletableFuture<AuthOperations.JwtMessage> jwtMessage =
                           ask(siteUserAccessor,
                               new DBOperations.GetEntity(value.getUserName()),
                               Duration.ofSeconds(1))
                           .thenApply(Optional.class::cast)
                           .thenApply(userOption -> checkPassword(value, userOption))
                           .thenApply(Boolean.class::cast)
                           .thenApply(loginValid -> createJWT(loginValid, value.getUserName()))
                           .thenApply(AuthOperations.JwtMessage.class::cast)
                           .toCompletableFuture();

			pipe(jwtMessage, 
                 context().dispatcher()).to(sender());
		})
        //Check a JWT and send whether it is valid back to the sender.
        .match(Optional.class, jwt ->
        {
			Boolean isValid = true;
			
            try
			{
				JWTObject toCheck = (JWTObject)jwt.get();
				Claims claims = AuthOperations.decodeJWT(toCheck.jwt);
			}
			catch (UnsupportedJwtException |
                   MalformedJwtException |
                   SignatureException |
                   ExpiredJwtException |
                   IllegalArgumentException |
                   NoSuchElementException e)
			{
				isValid = false;
			}

			sender().tell(isValid, self());
		})
		.build();
}

That was quite a bit to take in, but now we finished walking through our first actor in the system.

At this point we needed some common messages that can be sent to our database accessor actors to perform our CRUD operations. Keeping the messages general here allow us to reuse these same messages across most of these actors as the program expands and scales out. I won't go into too much detail on these as they are really just POJO's.

package com.pelaghisoftware.data.actors.operations;

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

/**
 * Messages for Database Operations
 */
public class DBOperations
{
    /**
     * Message to get a single entity
     */
    public static class GetEntity
    {
        public String id;
        public Optional<?> entity;

        public GetEntity(){};

        public GetEntity(String id)
        {
            this.id = id;
        }

        public GetEntity(Optional<?> entity)
        {
            this.entity = entity;
        }

        public GetEntity(String id, Optional<?> entity)
        {
            this.id = id;
            this.entity = entity;
        }
    }

    /**
     * Message to get a list of all entities
     */
    public static class GetAllEntities
    {
        public List<?> entities = new ArrayList<>();

        public GetAllEntities(){}

        public GetAllEntities(List<?> entities)
        {
            this.entities = entities;
        }
    }

    /**
     * Message to insert and entity
     */
    public static class InsertEntity
    {
        public String id;
        public Optional<?> entity;
        public boolean completed;

        public InsertEntity(){}

        public InsertEntity(String id)
        {
            this.id = id;
        }

        public InsertEntity(Optional<?> entity)
        {
            this.entity = entity;
        }

        public InsertEntity(boolean completed)
        {
            this.completed = completed;
        }

        public InsertEntity(Optional<?> entity, boolean completed)
        {
            this.entity = entity;
            this.completed = completed;
        }
    }

    /**
     * Message to update an entity
     */
    public static class UpdateEntity
    {
        public String id;
        public Optional<?> entity;
        public boolean completed;
        public boolean notFound;

        public UpdateEntity(){}

        public UpdateEntity(String id)
        {
            this.id = id;
        }

        public UpdateEntity(Optional<?> entity)
        {
            this.entity = entity;
        }

        public UpdateEntity(boolean completed)
        {
            this.completed = completed;
        }

        public UpdateEntity(boolean completed, boolean notFound)
        {
            this.completed = completed;
            this.notFound = notFound;
        }
    }

    /**
     * Message to delete an entity
     */
    public static class DeleteEntity
    {
        public String id;
        public Optional<?> entity;
        public boolean completed;
        public boolean notFound;

        public DeleteEntity(){}

        public DeleteEntity(String id)
        {
            this.id = id;
        }

        public DeleteEntity(Optional<?> entity)
        {
            this.entity = entity;
        }

        public DeleteEntity(boolean completed)
        {
            this.completed = completed;
        }

        public DeleteEntity(boolean completed, boolean notFound)
        {
            this.completed = completed;
            this.notFound = notFound;
        }
    }
}

With our common messages out of the way, I would like to walk through the SiteUserAccessor actor. This one is a little more straight forward, and for the most part just translates the operations we did inside our endpoints in the last article into an actor so we don't possibly block our server from taking in new requests.

We include our BCryptPasswordEncoder here as well so we don't store plain text passwords in the database. Just like before we have our props method which will allow the actor system to initialize this actor and like all Java classes we have the constructor. Then as we saw above the main part of the actor is it's createReceive method. Inside here we use our User DAO to perform the CRUD operations on our SITE_USER table. I'm not going to walk through every operation in this because I think they are easy to follow, however if you find them confusing or want some clarification, leave a comment and I will do my best to explain and/or update the article to clarify.

package com.pelaghisoftware.data.actors;

import akka.actor.AbstractActor;
import akka.actor.Props;
import com.pelaghisoftware.data.actors.operations.DBOperations;
import com.pelaghisoftware.data.dao.Dao;
import com.pelaghisoftware.data.dao.impl.UserDao;
import com.pelaghisoftware.data.entity.User;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Optional;

/**
 * Actor to access the database information for a user entity
 */
public class SiteUserAccessor extends AbstractActor
{
    private final Logger logger = LoggerFactory.getLogger(SiteUserAccessor.class);

    //Used to encrypt passwords and validate passwords
    private final PasswordEncoder encoder = new BCryptPasswordEncoder();

    private final Dao<User> userDao;

    /**
     * Creates props for a new SiteUserAccessor
     * @param sessionFactory Session Factory to create new sessions with the database
     * @return
     */
    public static Props props(SessionFactory sessionFactory)
    {
        return Props.create(SiteUserAccessor.class, () -> new SiteUserAccessor(sessionFactory));
    }

    /**
     * Constructor
     * @param sessionFactory Session Factory to create new sessions with the database
     */
    public SiteUserAccessor(SessionFactory sessionFactory)
    {
        userDao = new UserDao(sessionFactory);
    }

    /**
     * Runs when receiving a message
     * @return
     */
    @Override
    public Receive createReceive()
    {
        return receiveBuilder()
                .match(DBOperations.GetEntity.class, value ->
                    //Send the return message to the sender
                    getSender().tell(userDao.get(value.id), self())
                )
                .match(DBOperations.GetAllEntities.class, value ->
                    //Send the return message to the sender
                    getSender().tell(userDao.getAll(), self())
                )
                .match(DBOperations.InsertEntity.class, value ->
                {
                    boolean completed = false;
                    Optional<User> responseUser = Optional.empty();

                    //Check to make sure value received isn't empty.
                    if(value.entity.isPresent())
                    {
                        User user = (User)value.entity.get();

                        Optional<User> userCheck = 
                        	userDao.get(user.getUserName());

                        //Check to see if user is in the database
                        if(userCheck.isPresent())
                        {
                            responseUser = userCheck;
                            completed = false;
                        }
                        //Check the incoming user to see if the
                        //password is null
                        else if(user.getEncryptedPassword() == null)
                        {
                            completed = false;
                        }
                        //Incoming user is good to go. 
                        //Insert the new user in the database.
                        else
                        {
                            String encodedPassword =
								encoder.encode(
                                	user.getEncryptedPassword());
							user.setEncryptedPassword(
                            	encodedPassword);
                            completed = userDao.insert(user);
                        }
                    }

                    //Send the return message to the sender
                    getSender().tell(
                    		new DBOperations.InsertEntity(responseUser, 
                    									  completed), 
                            self());
                })
                .match(DBOperations.UpdateEntity.class, value ->
                {
                    boolean completed = false;
                    boolean notFound = false;

                    //Check to make sure value received isn't empty.
                    if(value.entity.isPresent())
                    {
                        User user = (User)value.entity.get();

                        Optional<User> userCheck = 
                        	userDao.get(user.getUserName());

                        //Check to make sure the user exists 
                        //in the database
                        if(userCheck.isPresent())
                        {
                            //If the incoming value does not 
                            //have a password set the password 
                            //to the old one
                            if(user.getEncryptedPassword() == null)
                            {
                                user.setEncryptedPassword(
                                userCheck.get()
                                	.getEncryptedPassword());
                            }
                            else
                            {
                                String encodedPassword = 
									encoder.encode(
        								user.getEncryptedPassword());

								user.setEncryptedPassword(
        							encodedPassword);
                            }

                            //Update the user in the database
                            completed = userDao.update(user);
                        }
                        //If the incoming user is not found
                        //in the database
                        else
                        {
                            notFound = true;
                        }
                    }

                    //Send the return message to the sender
                    getSender().tell(
                    	new DBOperations.UpdateEntity(completed, 
                        							  notFound), 
                        self());
                })
                .match(DBOperations.DeleteEntity.class, value ->
                {
                    boolean completed = false;
                    boolean notFound = false;

                    //Check to make sure value received 
                    //isn't empty.
                    if(value.entity.isPresent())
                    {
                        User user = (User)value.entity.get();

                        Optional<User> userCheck =
                        	userDao.get(user.getUserName());

                        //Check to make sure the incoming 
                        //user exists in the database
                        if(userCheck.isPresent())
                        {
                            completed = userDao.delete(user);
                        }
                        else
                        {
                            notFound = true;
                        }
                    }

                    //Send the return message to the sender
                    getSender().tell(
                    	new DBOperations.DeleteEntity(completed,
                        							  notFound),
                        self());
                })
                .build();

    }
}

Now on to our final actor. To take the processing of the responses off of the server and also to make responsibilities a bit more explicit, I created the ResponseResolver actor. We take in a message for our CRUD Operations or our JWT messages and do simple processes then we tell the sender of that message a HttpResponse with the appropriate Status Code, Content-Type, and Body. There is a helper method, createResponse, which helps prevent duplication of code in creating responses.

package com.pelaghisoftware.server.actors;

import akka.actor.AbstractActor;
import akka.actor.Props;
import akka.http.javadsl.model.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pelaghisoftware.data.actors.operations.DBOperations;
import com.pelaghisoftware.server.auth.operations.AuthOperations;
import com.pelaghisoftware.server.response.messages.ErrorMessage;

/**
 * Actor to create an HttpResponse
 */
public class ResponseResolver extends AbstractActor
{
    private final ObjectMapper mapper = new ObjectMapper();

    /**
     * Create the props needed to initialize a Response 
     * resolver in the actor system
     * @return Props for a Response Resolver.
     */
    public static Props props()
    {
        return Props.create(ResponseResolver.class, 
                            () -> new ResponseResolver());
    }

    /**
     * Constructor
     */
    public ResponseResolver(){}

    /**
     * Runs when a message is received
     * @return
     */
    @Override
    public Receive createReceive()
    {
        return receiveBuilder()
                //Response that returns a list of all entities
                .match(DBOperations.GetAllEntities.class, value ->
                {
                    HttpResponse response = 
                    	createResponse(StatusCodes.OK,
                                       ContentTypes.APPLICATION_JSON,
                                       value.entities);

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                //Response that returns an entity
                .match(DBOperations.GetEntity.class, value ->
                {
                    HttpResponse response = null;
                    //Checks if the entity is present
                    if(value.entity.isPresent())
                    {
                        response = 
                      		createResponse(StatusCodes.OK,
                                           ContentTypes.APPLICATION_JSON, 
                                           value.entity.get());
                    }
                    //Runs if the requested entity is not found
                    else
                    {
                        response = 
                        	createResponse(StatusCodes.NOT_FOUND, 
                            			  ContentTypes.APPLICATION_JSON, 
                                          ErrorMessage.resourceNotFoundMessage());
                    }

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                //Response that responds to a request to insert an entiry
                .match(DBOperations.InsertEntity.class, value ->
                {
                    HttpResponse response = null;
                    //runs if the requested entity already exists.
                    if(value.entity.isPresent())
                    {
                        response = 
                        	createResponse(StatusCodes.BAD_REQUEST,
                            		       ContentTypes.APPLICATION_JSON, 
                                           ErrorMessage.userAlreadyExistsMessage());
                    }
                    //runs if there was a failure on insert.
                    else if(!value.completed)
                    {
                        response =
                        	createResponse(StatusCodes.BAD_REQUEST, 
                        		           ContentTypes.APPLICATION_JSON, 
                                           ErrorMessage.badRequestMessage());
                    }
                    //runs when the entity was inserted 
                    //and is good to go
                    else
                    {
                        response = 
                        	createResponse(StatusCodes.NO_CONTENT, 
                        				   null, 
                        				   null);
                    }

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                //Response for when a user is updated
                .match(DBOperations.UpdateEntity.class, value ->
                {
                    HttpResponse response = null;
                    //Runs when the incoming user was not found
                    if(value.notFound)
                    {
                        response = 
                        	createResponse(StatusCodes.NOT_FOUND,
                                           ContentTypes.APPLICATION_JSON, 
                                           ErrorMessage.userDoesNotExistMessage());
                    }
                    //Runs if there is an error in updating
                    else if(!value.completed)
                    {
                        response = 
                        	createResponse(StatusCodes.BAD_REQUEST,
                            			   ContentTypes.APPLICATION_JSON, 
                                           ErrorMessage.badRequestMessage());
                    }
                    //Runs if the user was updated and is good to go
                    else
                    {
                        response = 
                        	createResponse(StatusCodes.NO_CONTENT, 
                            			   null, 
                                           null);
                    }

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                //Response for when a user is deleted
                .match(DBOperations.DeleteEntity.class, value ->
                {
                    HttpResponse response = null;

                    //Runs when the incoming user was not found
                    if(value.notFound)
                    {
                        response = 
                        	createResponse(StatusCodes.NOT_FOUND,
                            			   ContentTypes.APPLICATION_JSON, 
                                           ErrorMessage.userDoesNotExistMessage());
                    }
                    //Runs if there is an error in deleting
                    else if(!value.completed)
                    {
                        response = 
                        	createResponse(StatusCodes.BAD_REQUEST,
                            			   ContentTypes.APPLICATION_JSON, 
                                           ErrorMessage.badRequestMessage());
                    }
                    //Runs if the user was deleted and is good to go
                    else
                    {
                        response = 
                        	createResponse(StatusCodes.NO_CONTENT,
                            			   null, 
                                           null);
                    }

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                //Response for login
                .match(AuthOperations.JwtMessage.class, value ->
                {
                    HttpResponse response = null;

                    //runs if login failed.
                    if(value.jwt == null)
                    {
                        response = 
                        	createResponse(StatusCodes.UNAUTHORIZED,
                            			   ContentTypes.APPLICATION_JSON,
                                           ErrorMessage.usernamePasswordIncorrect());
                    }
                    //runs if login succeeded
                    else
                    {
                        response = 
                        	createResponse(StatusCodes.OK,
                            			   ContentTypes.APPLICATION_JSON, 
                                           value.jwt);
                    }

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                //Response for when an unauthorized operation occurs.
                .match(AuthOperations.Unauthorized.class, value ->
                {
                    HttpResponse response = 
                    	createResponse(value.code,
                        			   ContentTypes.APPLICATION_JSON,
                                       value.message);

                    //Send the response back to the sender
                    sender().tell(response, self());
                })
                .build();
    }

    /**
     * Create an HttpResponse
     * @param statusCode The Status Code for the response
     * @param type The Content-Type for the response
     * @param object The object to serialize into the message
     * @return HttpResponse. Response to the user
     */
    private HttpResponse createResponse(StatusCode statusCode,
    								    ContentType type, 
                                        Object object)
    {
        try
        {
            HttpResponse response = 
            	HttpResponse.create().withStatus(statusCode);

            if(type != null && object != null)
            {
                response = 
                	response.withEntity(type,
                        				mapper
                        					.writeValueAsString(object)
                        					.getBytes());
            }

            return response;
        }
        catch (JsonProcessingException e)
        {
            return HttpResponse
            	.create()
                .withStatus(StatusCodes.INTERNAL_SERVER_ERROR);
        }
    }
}

If you are still with me at this point, I promise we are almost done. We did a couple changes to the class with our main method and we offloaded the Routes (Endpoints) for users to a separate class to improve readability and also to give more flexibility in determining what routes will actually be accessible. With this it is possible to remove entire routes without removing the code for them and also allows each route to be updated easier and more independently from the others. I will break down a couple of methods inside this class and the entire class can be viewed here.

The first is the getUserRoutes method. This is a method where we concat all of the Route generating methods into one return, so that way we can use a nice simple and clean method inside our createRoute method in our Server class which has our main method.

/**
* Get all routes concatenated together for cleanliness 
* in server code 
* @return Route. Endpoints for accessing User entities
*/
public Route getUserRoutes()
{
	return concat(
		getAllUsersRoute(),
		getUser(),
		addUser(),
		updateUser(),
		deleteUser()
	);
}

Next I will break down one of the routes as they all follow the same general pattern. Notice, as they were previously written in our createRoute method, each route starts with a type of matcher based on the HTTP Method.  Next we create a Supplier (Function without an input) that will perform the operations after authentication happens. In this instance, we ask the UserAccessor actor to give us a list of all users. We then pass this response to the ResponseResolver actor to create the HttpResponse future we will send back to the user in  completeWithFuture at the bottom of the method.

private Route getAllUsersRoute()
{
	return get(() ->
		path(segment("user"), () ->
			optionalHeaderValue(AuthOperations
									.getAuthorizationHeader, 
								jwt ->
			{
                //Function to get all user entities after 
                //authentication is complete
                Supplier<CompletionStage<HttpResponse>> 
                	allUsersResponse = () ->
						ask(userAccessor, 
							new DBOperations.GetAllEntities(),
							duration)
						.thenApply(List.class::cast)
						.thenCompose(userList ->
							ask(responseResolver,
								new DBOperations.GetAllEntities(
									(List<User>)userList),
								duration))
						.thenApply(HttpResponse.class::cast);

				//Perform Authentication and returns 
				//an HttpResponse
				CompletionStage<HttpResponse> response =
					AuthOperations.authCheck(authAccessor,
											 responseResolver,
											 jwt,
											 duration,
											 allUsersResponse);

				return completeWithFuture(response);
			}))
	);
}

Finally we made a few updates to the main class. First, I renamed it to make it's responsibility a bit clearer. I will only cover the differences, but if you want to view the file in it's entirety click here.

First we create a map to store our actors. This allows us to pass the references down and be used in processing the user's request. I also created a duration of 1 second to be used as the timeout for all requests.

final static Map<String, ActorRef> dataAccessors = new HashMap<>();

final static Duration duration = Duration.ofSeconds(1);

Next inside our main method we create the actors and place them in our dataAccessors map. Notice on the UserAccessor we create it using a different Dispatcher using our route-blocking-dispatcher config. This sets up the three actors we need to process our requests right now.  

 //Create the necessary actors and place them in a map
dataAccessors.put("UserAccessor",
				  system.actorOf(
                  	SiteUserAccessor.props(sessionFactory)
            			.withDispatcher("route-blocking-dispatcher"), 					  	"UserAccessor"));
dataAccessors.put("AuthAccessor",
				  system.actorOf(
    			  	AuthResolver.props(
                    	dataAccessors.get("UserAccessor")),
                  		"AuthAccessor"));
dataAccessors.put("ResponseResolver",
				  system.actorOf(
				  	ResponseResolver.props(), 
                    "ResponseResolver"));

We are now ready to grab the admin user from the config and insert them into the database if the admin user doesn't exist.

try
{
	String adminPassword = ConfigFactory
        					.load()
                            .getConfig("admin")
                            .getString("password");

	User admin = new User(adminUserName, 
    					  adminPassword);
                          
	dataAccessors.get("UserAccessor")
    	.tell(new DBOperations.InsertEntity(
        		Optional.of(admin)), 
              ActorRef.noSender());
}
catch (ConfigException e)
{
	logger.info("No admin user passed in config.");
}

Finally we updated the createRoute method to get a reference to our actors, use the getUserRoutes method from our UserRoutes class, and also our route to login. Additionally, you will notice that we are using futures to complete our responses. This prevents the main server from blocking and possibly freezing or not taking in new requests in the event of a high volume of requests.

private Route createRoute()
{
	//Gets all necessary ActorRefs for 
    //easier/cleaner use
	ActorRef responseResolver = 
    	dataAccessors.get("ResponseResolver");
	ActorRef authAccessor = 
    	dataAccessors.get("AuthAccessor");
	ActorRef userAccessor = 
    	dataAccessors.get("UserAccessor");

	//Provider for routes related to user entities
	UserRoutes userRoutes = new UserRoutes(authAccessor, 
    									   userAccessor,
                                           responseResolver, 
                                           duration);

	return concat(
		//Adds the user entity routes
		userRoutes.getUserRoutes(),
		//Adds the route for authentication
		post(() ->
			concat(
				path(segment("auth"), () ->
				entity(Jackson.unmarshaller(User.class),
					user ->
				{
					CompletionStage<HttpResponse> response =
						ask(authAccessor, 
                        	user, 
                        	duration)
                    	.thenApply(AuthOperations
                    				.JwtMessage
                            	    .class::cast)
						.thenCompose(message -> 
                    		ask(responseResolver, 
                        		message, 
                            	duration)
                    	.thenApply(HttpResponse.class::cast));

					return completeWithFuture(response);
				})
			)
		))
	);
}

Now we should be ready to send some requests. First we will need to send a POST request to the /auth route. The body of the request should be in the following format:

{
	"userName": "admin",
	"password": "Your Password"
}

If this is successful it will return the following JSON:

{
    "jwt": "A new JWT"
}

From here we just need to include this in the Authorization Header of each request to an endpoint that is secured. Here is a screenshot from Postman where I sent the header.

For our other endpoints they more or less stayed the same. However now you will notice that it is not necessary to put in the users password when deleting a user and now we never return a user's password with the requests.

Whew!!!! I think I made way to much for one post, but I when I got started, I just couldn't stop halfway. I really appreciate you reading through to the end, but in the event that you have an attention span like mine, you can find the entire source code here. We walked through how to use actors and also some basic authentication. Keep in mind that if you want to put code similar to this live you should follow the guidance on OWASP to defeat the Top Ten Vulnerabilities. With that I will finish up and see you in the next article.

Ci vediamo presto!!!!!