Skip to main content

Web Application Security

About a 10 to 20 minute read


    I was looking for a Java Security Framework to use for my web applications. Obviously, there's JAAS but that provides security on the JVM and class level (something not usually needed for a web application) and therefore overly complex for most use cases. That's when I stumbled on Apache Shiro which is an open source project that provides security similar to JAAS, yet, with a simpler API. Unfortunately, after some experimenting with the framework, I came to the conclusion that it's not well suited for web applications. Despite it's claim for simplicity and security, there are a few issues with Apache Shiro:

  • No CDI injection support. Programming a web applications server-side code in Java EE, you have the benefit of a container managed environment. The container is responsibly for creation of beans, handling transactions, and closing resources. Since Apache Shiro is not just for web applications, it needs to work out of a container managed environment. A common scenario in a web app is to have the Realm (which handles the User database) as a Stateless EJB. That way, you don't have to create an instance of an EntityManager manually, handle it's transactions, and close it down; this can all be left to the container. Unfortunately, because Shiro instantiates an instance of the user defined Realm using the "new" keyword, you can't inject an EntityManager (or anything).
  • No JSF tag library. Though, I tend to write my view with "plain HTML" (really XHTML and just use the Faces Servlet as an XHTML parser), as a Java Framework that "supports" web apps you would think Shiro would have one. Instead, they have a JSP tag library. Which is odd considering that Java Server Faces replaced Java Server Pages as the default component view framework about ten years ago!
  • Text based setup. In its attempt to offer simple integration, Shiro uses an INI file as its initialization configuration. Its claim is to offer simple configuration to those unfamiliar with the Java API (then why use a Java Security Framework if you're unfamiliar with Java?). Though it is pretty easy to use, it's one more thing to learn. Also, it gets more complex (and possibly pointless to use) when you're using you're own Realm (which is almost always the case). Unfortunately, most of the tutorials for Shiro emphasize the use of the INI file, yet show little examples of a programmatic implementation (other than simply defining its API).  

    Here's a tutorial for implementing Apache Shiro that identifies some more of its pitfalls. Though, the tutorial is two years old, it doesn't seem Apache Shiro changed that much. It seems I'm bashing Shiro pretty bad but it is useful for certain scenarios and is arguably better than JAAS. However, I believe the best security for a Java web app is still a homegrown implementation. So, let's look at how we can implement one.  

Note: I heard speculation that Java EE 8 will include a standardized API for security. Also, the Servlet 3.0 API came equipped with security methods (login, logout, authenticate), however, I believe it works with a pre-configured Realm for the Server and there's not much documentation on how to use it. 


Security Implementation

    First, let's take a look at a basic application flow to understand what exactly we will need to do:

  • User attempts to access a restricted resource.
  • Server checks if the User is authenticated. If they are, they may continue to the resource. If they are not:
  • User is redirected to the login page.
  • User enters their credentials into a supplied form and submits.
  • Server checks credentials. If credentials are approved, they may continue to the originally requested resource. If they are not:
  • User is redirected to the login page. An error message is displayed.

Note: This implementation will not include Role based authentication (limiting access to resources depending on User Roles) though it's not hard to add. Also, I will be using "plain HTML" and not the JSF MVC. Therefore, no backing beans or expression language. So, in order to display the error message, upon redirect you will have to create an HTTP GET request to the Server (POST/Redirect/GET pattern). Simple enough to implement, but I will not do so in the following code.

So, let's first create our login.xhtml page:


<html>
 <head>
  
  <link src="css/login.css"></link>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
 </head>
 <body>
  <form action="auth" method="post">
   <input id="username" name="username" placeholder="Enter Username" required="required" type="text" />
   <input id="password" name="password" placeholder="Enter Password" required="required" type="password" />
   <input id="rememberMe" name="rememberMe" type="checkbox" />
   <input id="submit" name="login" type="submit" value="Login" />
  </form>
</body>
</html>

Note: If you notice any weird characters in the above HTML snippet (&lt; and &gt;), it's because they're escape characters (< and > respectively) to differentiate HTML to display from HTML code.

   In the above HTML, we have a basic form. The form's action attribute is set to "auth", this is the location on the server for our Servlet which will come later ("auth" = "app_location/auth", "/auth" = "/auth"; so use the abbreviated syntax, without the preceding slash, or include the app path). Also, note the method attribute is set to "post", when submitting a form, especially for logging in, should be an HTTP POST request. The name attributes on each <input> item specifies the name of parameter we will retrieve in our Servlet.

    When the user presses the Submit button, the form is submitted to our Servlet. So, let's create the Servlet:

@WebServlet("/auth")
public class AuthServlet extends HttpServlet{
 private static final long serialVersionUID = 4986296505493977503L;
 
 @Inject
 SecurityBean security;
 
 @Override
 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  if(request.getParameter("login") != null){
   //login
   String username = request.getParameter("username");
   String password = request.getParameter("password");
   boolean rememberMe = "true".equals(request.getParameter("rememberMe"));
   System.out.println("username: " + username + " password: " + password + " rememberMe: " + rememberMe);
   login(username, password, rememberMe, request, response);
  }else if(request.getParameter("logout") != null){
   //logout
   logout(request, response);
  }
 }
 
 private void login(String username, String password, boolean rememberMe, 
                    HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  User user = security.getUser(username, password);
  if(user != null){
   request.getSession().setAttribute("user", user);
   String token;
   if(rememberMe){
    token = security.createToken(user.getUserId(), true);
   }else{
    token = security.createToken(user.getUserId(), false);
   }
   CookieHandler.addCookie(response, token, username, CookieHandler.COOKIE_NAME, rememberMe);
  }
  String path = request.getContextPath();
  if(user == null){
   response.sendRedirect(path + "/login.xhtml");
  }else{
   String location = request.getParameter("location");
   if(location != null && !location.isEmpty()){
    response.sendRedirect(path + location);
   }else{
    response.sendRedirect(path + "/home.xhtml");
   }
  }
 }
 
 private void logout(HttpServletRequest request, HttpServletResponse response) throws IOException{
  if(CookieHandler.containsCookie(request, CookieHandler.COOKIE_NAME)){
   request.getSession().removeAttribute("user");
   CookieHandler.removeCookie(response, CookieHandler.getCookie(request, CookieHandler.COOKIE_NAME));
   response.sendRedirect(request.getContextPath() + "/login.xhtml");
  }
 }
 
}


    There's a few things going on here so let's have a look. First, the @WebServlet("/auth") annotation provides the URL of the Servlet relative to the application path. Next, we use the @Inject annotation to inject a container managed instance (CDI) of an EJB. This will be our "Realm", it will handle all the interactions with the database, we will code this soon. Then we have the doPost method, which is called when an HTTP POST request is made to the Servlet. Within this method, we have an if-statement which checks to see what "type" of button called it (name attribute on the button in the HTML): login or logout. If login button was pressed, it obtains the values associated with the form and passes them into the login method, if logout button was pressed (haven't made a button for this but it's easy to do!) it calls the logout method. Simple!

    The login method calls a method of the EJB which checks the database for the username and compares the entered password with the one stored in the database (obviously is hashed); returns null if it's not a match and returns the User if it is. For brevity, I'm going to stop explaining every detail, especially since most of it is self explanatory.

    You may have noticed the use of a CookieHandler class, which contains static methods that create and delete Cookies that are sent back and forth to the Client to retain Session state. Here's that class:

public class CookieHandler {
 
 public static final String COOKIE_NAME = "rememberMeCookie";
 
 public static void addCookie(HttpServletResponse response, String token, String username, String cookieName, boolean longLived){
  Cookie cookie = new Cookie(cookieName, token);
  String value;
  try{
   JSONObject obj = new JSONObject();
   obj.put("username", username);
   obj.put("token", token);
   value = obj.toString();
   cookie.setValue(value);
  }catch(Exception e){
   e.printStackTrace();
  }
  cookie.setPath("/");
  if(longLived) cookie.setMaxAge(2 * 604800);
  cookie.setHttpOnly(true);
  response.addCookie(cookie);
 }
 
 public static void removeCookie(HttpServletResponse response, Cookie cookie){
  cookie.setValue(null);
  cookie.setMaxAge(0);
  cookie.setPath("/");
  response.addCookie(cookie);
 }
 
 public static String getTokenFromCookie(HttpServletRequest request, String cookieName){
  if(containsCookie(request, cookieName)){
   String value = getCookie(request, cookieName).getValue();
   try{
    JSONObject obj = new JSONObject(value);
    return obj.getString("token");
   }catch(Exception e){
    e.printStackTrace();
    if(value != null){
     return value;
    }
   }
  }
  return null;
 }
 
 public static String getUsernameFromCookie(HttpServletRequest request, String cookieName){
  if(containsCookie(request, cookieName)){
   String value = getCookie(request, cookieName).getValue();
   try{
    JSONObject obj = new JSONObject(value);
    return obj.getString("username");
   }catch(Exception e){
    e.printStackTrace();
   }
  }
  return null;
 }
 
 public static boolean containsCookie(HttpServletRequest request, String cookieName){
  Cookie[] cookies = request.getCookies();
  if(cookies != null){
   for(Cookie c : cookies){
    if(cookieName.equals(c.getName())){
     return true;
    }
   }
  }
  return false;
 }
 
 public static Cookie getCookie(HttpServletRequest request, String cookieName){
  Cookie[] cookies = request.getCookies();
  if(cookies != null){
   for(Cookie c : cookies){
    if(cookieName.equals(c.getName())){
     return c;
    }
   }
  }
  return null;
 }
 
}

Note: I do use a JSON library dependancy.

   Now, time for the SecurityBean, the EJB that uses an EntityManager to access the database:

@Stateless
public class SecurityBean {
 @PersistenceContext(unitName = "Security")
 private EntityManager em;
 private TypedQuery cQuery;
 private TypedQuery tQuery;
 
 public String createToken(String userId, boolean longLived) {
  System.out.println("CREATE TOKEN:");
  try{
   String t = Encryption.createToken();
   String salt = Encryption.createSalt();
   Token token = new Token(userId, Encryption.encrypt(t, salt), salt, Encryption.createTokenExpiredDate(longLived));
   em.persist(token);
   return t;
  }catch(Exception e){
   e.printStackTrace();
   return null;
  }
 }
 
 public boolean compareToken(String userId, String token) {
  System.out.println("COMPARE TOKEN:");
  try{
   tQuery = em.createNamedQuery("getUserToken", Token.class);
   tQuery.setParameter("userId", userId);
   List tokens = tQuery.getResultList();
   if(tokens != null){
    boolean b;
    for(Token t : tokens){
     b = Encryption.compare(token, t.getSalt(), t.getEncryptedToken());
     if(b){
      return true;
     }
    }
   }
   return false;
  }catch(Exception e){
   e.printStackTrace();
   return false;
  }
 }
 
 public void addUser(String username, String password, User user){ //should have a User parameter
  System.out.println("ADD USER:");
  try{
   String salt = Encryption.createSalt();
   Credential c = new Credential(username, Encryption.encrypt(password, salt), salt, user);
   em.persist(c);
  }catch(Exception e){
   e.printStackTrace();
  }
 }
 
 public boolean comparePassword(String username, String password){
  System.out.println("COMPARE PASSWORD:");
  try{
   cQuery = em.createNamedQuery("getCredentials", Credential.class);
   cQuery.setParameter("userName", username);
   List credentials = cQuery.getResultList();
   if(credentials == null || credentials.size() > 1){
    return false;
   }
   return Encryption.compare(password, credentials.get(0).getSalt(), credentials.get(0).getEncryptedPassword());
  }catch(Exception e){
   e.printStackTrace();
   return false;
  }
 }
 
 public User getUser(String username, String password){
  System.out.println("GET USER:");
  try{
   cQuery = em.createNamedQuery("getCredentials", Credential.class);
   cQuery.setParameter("userName", username);
   List credentials = cQuery.getResultList();
   if(credentials != null && !credentials.isEmpty()){
    boolean b = Encryption.compare(password, credentials.get(0).getSalt(), credentials.get(0).getEncryptedPassword());
    if(b){
     return credentials.get(0).getUser();
    }
   }
  }catch(Exception e){
   e.printStackTrace();
  }
  return null;
 }
 
 public User getUserFromToken(String username, String token){
  System.out.println("GET USER FROM TOKEN:");
  try{
   cQuery = em.createNamedQuery("getCredentials", Credential.class);
   cQuery.setParameter("userName", username);
   List credentials = cQuery.getResultList();
   if(credentials != null && !credentials.isEmpty()){
    User user = credentials.get(0).getUser();
    if(user != null){
     boolean b = compareToken(user.getUserId(), token);
     if(b){
      return user;
     }
    }
   }
  }catch(Exception e){
   e.printStackTrace();
  }
  return null;
 }
 
}

    You may have noticed the use of the @PersistenceContext() annotation. This is because we're using JPA (Java Persistence API) to map Entities to the database. So, make sure to set up the database with your Server and create your persistence.xml file in the src/META_INF/ folder of your dynamic web project. Setting up the database is out of the scope of this post. Also, you may have noticed three classes we haven't defined yet: Encryption, Token, and Credential. Encryption is a class which contains static methods related to hashing and comparing passwords. Token and Credential are both Entity classes which get mapped to the database. Here's the Encryption class:

public class Encryption {
 //application specific salt (a.k.a. pepper); if database is compromised and salt is obtained, the attacker won't be able to retrieve password without knowing this value
 private static final String sugar = "2489c2a3-ef19-49b6-bb41-72cbdd76ab13";
  
 public static String createToken(){
  return UUID.randomUUID().toString();
 }
  
 public static String createSalt(){
  return UUID.randomUUID().toString();
 }
 
 public static String encrypt(String password, String salt){
  try{
   KeySpec spec = new PBEKeySpec(password.toCharArray(), new String(salt + sugar).getBytes(), 20000, 128);
   SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
   byte[] hash = factory.generateSecret(spec).getEncoded();
   return Base64.getEncoder().encodeToString(hash);
  }catch(Exception e){
   e.printStackTrace();
   return null;
  }
 }
 
 public static boolean compare(String password, String salt, String encryptedPassword){
  String e = encrypt(password, salt);
  return e.equals(encryptedPassword);
 }
 
 public static Date createTokenExpiredDate(boolean longLived){
  Calendar calendar = Calendar.getInstance();
  if(longLived){
   calendar.add(Calendar.DAY_OF_MONTH, 14); //expires two weeks from now
   return calendar.getTime();
  }else{
   Date now = calendar.getTime();
   return new Date(now.getTime() + (60000 * 120)); //expires two hours from now
  }
 }
 
}

    Some things to know about the Encryption class are to make sure the "sugar" variable has a unique value (don't reuse the value here; switch it up). This value is private and should never be shared. Also, use this class's encrypt method to store passwords and tokens in the database, never store a password or token un-hashed! Next are the Token and Credential Entities. These classes are very similar. The Credential class stores a User's username and hashed password (along with the salt: random sequence of values) and a map to the User Entity (out of the scope of this post but is a simple class containing a User's information, such as, name, email, phone number, etc.). The Token class stores  a user ID and a hashed password (called a token; along with it's salt) and an expiration date. The Credential class if for when a User logs in. The Token class is for when a User is already logged in but needs to be remembered (such as used in a Cookie). It's much safer to send the Client a time sensitive token then the User's password. Here's the classes:

Credential:

@Entity
@NamedQueries({@NamedQuery(name = "getCredentials", query = "SELECT c FROM Credential c WHERE c.userName = :userName"),
    @NamedQuery(name = "deleteCredentials", query = "DELETE FROM Credential c WHERE c.userName = :userName")})
public class Credential implements Serializable {
 private static final long serialVersionUID = 2555682746921997545L;
 
 @Id @GeneratedValue
 private long id;
 
 private String userName;
 
 private String encryptedPassword;
 private String salt;
 
 @OneToOne(cascade = CascadeType.PERSIST)
 private User user;
 
 public Credential(){
  
 }
 
 public Credential(String username, String encryptedPassword, String salt, User user){
  this.userName = username;
  this.encryptedPassword = encryptedPassword;
  this.salt = salt;
  this.setUser(user);
 }

        //getters/setters
}
 

Token:

@Entity
//similar to the credentials entity but contains a token instead of a password and an expiration date for the token
@NamedQueries({@NamedQuery(name = "deleteUserTokens", query = "DELETE FROM Token t WHERE t.userId = :userId"),
    @NamedQuery(name = "getUserToken", query = "SELECT t FROM Token t WHERE t.userId = :userId")})
public class Token implements Serializable{
 private static final long serialVersionUID = 4524105948739050425L;
 @Id @GeneratedValue
 private long id;
 
 private String userId;
 
 private String encryptedToken;
 private String salt;
 
 @Temporal(TemporalType.TIMESTAMP)
 private Date expirationDate;
 
 public Token(){
  
 }

 public Token(String userId, String token, String salt, Date expirationDate){
  this.userId = userId;
  this.encryptedToken = token;
  this.salt = salt;
  this.expirationDate = expirationDate;
 }
        //getters/setters
}

    Now, our web apps security should work when on the login.xhtml page. But how do we redirect to the login page if the User isn't authenticated when attempting to access a restricted resource? That's where a Filter comes in. A Filter is similar to a Servlet but allows you to run code before and after reaching a Servlet. This means we can filter on the restricted directory (or the whole application as we do), check if the User is authenticated and respond accordingly. This is the last class we have to write I swear! Filter:

@WebFilter("/*")
public class AuthFilter implements Filter {
 
 @Inject
 SecurityBean security;
 
 @Override
 public void init(FilterConfig config) throws ServletException {

 }
 
 @Override
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest) req;
  HttpServletResponse response = (HttpServletResponse) res;
  User user = (User) request.getSession().getAttribute("user");
  
  if(user == null) System.out.println("doFilter: user == null");
  
  String path = request.getRequestURI();
  if(user != null && path.equals(request.getContextPath() + "/login.xhtml")){
   response.sendRedirect(request.getContextPath() + "/home.xhtml");
  }else if(path.equals(request.getContextPath() + "/login.xhtml")){
   chain.doFilter(req, res);
   return;
  }
  request.getSession().setAttribute("location", path);
  
  if(user == null){
   //check to see if remember me token was set
   if(CookieHandler.containsCookie(request, "rememberMeCookie")){
    //remember me token was set
    String username = CookieHandler.getUsernameFromCookie(request, CookieHandler.COOKIE_NAME);
    String token = CookieHandler.getTokenFromCookie(request, CookieHandler.COOKIE_NAME);
    if(username != null && token != null){ //remember me cookie contains username and token parameters
     user = security.getUserFromToken(username, token);
     if(user != null){
      //credentials were correct
      request.getSession().setAttribute("user", user);
      CookieHandler.addCookie(response, token, username, CookieHandler.COOKIE_NAME, true);
     }else{
      //credentials are wrong from the previously set remember me cookie, so delete it
      CookieHandler.removeCookie(response, CookieHandler.getCookie(request, CookieHandler.COOKIE_NAME));
     }
    }
   }
  }
  
  if(user == null && !path.endsWith("/auth")){
   //send to login page
   response.sendRedirect(request.getContextPath() + "/login.xhtml");
  }else{
   //send to correct page
   chain.doFilter(req, res);
  }
  
 }
 
 @Override
 public void destroy() {
  
 }
 
}

    And then we have a decently secure web application. There are some other things you may want to secure which are out of the scope of this post but for instance a WebSocket. WebSockets are a different protocol than HTTP but connect with an HTTP upgrade request, so it is possible to access the HTTP Session (which is what we use to retain state and secure our application) probably within the open method using a customized EndpointConfig class. Well, I hope this tutorial/post is useful for someone trying to implement web application security with Java. Questions, comments, and concerns are welcome.

Comments

Popular posts from this blog

Face detection and live filters

Live video filters are becoming a popular trend fueled by Facebook (through their purchase of Msqrd) and Snapchat incorporating the features into their apps. These filters apply images or animations to your face using face tracking software. This technology has been around for awhile but is becoming increasingly more common due to the powerful CPU's that our mobile phones now have. Google provides an API that provides face tracking abilities through the Google Play Services library called Mobile Vision. I'm going to use their API to build a basic live filter app. The end result will look something like this:


    The bounding box wraps around the detected face and the sunglasses are the filter I chose (which is just a PNG image) which are drawn over the eyes. You could use any PNG image (with alpha for the background) you want, you will just have to adjust the layout according to where the image should be displayed. As you move your head, the box and sunglasses are redrawn…

Setting Up Connection Pooling With Elastic Beanstalk

Amazon's Elastic Beanstalk is a service which automatically scales your application when needed. It uses Amazon's Elastic Compute Cloud (EC2) instances as deployable containers which when your app requires more resources more containers will be deployed. This removes the need to manually configure your EC2 instance whenever you need more connections or resources and attempts to add simplicity to the maintenance aspect of your application. So, when you get more users of your app, your app will scale accordingly.

    Unfortunately, along with the ability to scale automatically, comes less control and configuration. Things you would normally have the ability to configure to your liking, such as your server, you no longer can. Amazon attempts to address this issue with configuration files. You can provide configuration files which can set up your server. These files are either written in JSON or the horrible format YAML. Though these files provide some level of control, you ca…

Android Guitar Tuner

Recently I created a guitar tuner application for Android that is written with pure Java (no C++ or NDK usage). The design was inspired by the Google Chrome team's guitar tuner web app using the WebAudio API. I wanted to code a version written natively for Android that didn't have to rely on a WebView, the WebAudio APIs, or server-side logic. Also, I wanted this application to be available to as many versions of Android as possible (whereas the WebAudio API may only be supported in more recent versions of WebView available only on newer flavors of Android). So, I coded it from scratch. I used a portion of the open source TarsosDSP project (their YIN algorithm) to help with the pitch detection.

    The application is available in the Google Play Store for Android: https://play.google.com/store/apps/details?id=com.chrynan.guitartuner. The project is completely open source and the code can be found on the GitHub repository: https://github.com/chRyNaN/Android-Guitar-Tuner. Fi…