• Mar
    • 27
    • 2012

JavaFX and Spring Security

Posted by In Uncategorized
This entry is part 15 of 15 in the series Building JEE applications in JavaFX 2.0

Disclaimer: Security is a big topic! This post is going to introduce a very basic Spring Security setup. You could probably just get away with this for a small system running on an intranet, or VPN, but for anything with serious security concerns you will need to explore the Spring Security docco and build on this post to suit your needs. At the very least you should look at encrypting passwords sent over the wire and use SSL as your socket layer. I will likely add some more complex setup to First Contact in future posts but until then check out other security articles on the web for more information, or post questions below. 

The great thing about the Internet is that it’s open to everyone. The bad thing about the Internet is that it’s open to everyone. If we’re going to put our data out there on the web chances are we want to secure that data so only those who should see and edit it, can do so.

Security is a messy business. To make a system safe, you have to put up a lot of walls and add locks to every door. Just like securing your house, the more secure you want it, the more keys you have to carry around on your keyring and the more hassles you have getting in the front door yourself.

As usual however, Spring has a framework that will help us out in this area, namely Spring Security. And, as usual, this framework is very good – non-invasive, simple to use and easy to setup. The only challenge is that Spring Security really has come out of the webapp space, so there are a few not-so-common tricks we have to employ to make them work for our JavaFX, client-server based application.

As normal, we’ll start with our code base from the previous post and add to it. The full source code for this post can be found at: http://code.google.com/p/jfxee/source/browse/trunk/first-contact/first-contact-security/

Step 1: Add Spring Security dependencies 

Spring Security is made up of a number of modules of which we need only a couple. In our server POM we need to add the following dependencies:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>

Security is mostly transparent to the client, however when a security violation is triggered (such as when the user tries to access something they are not authorised to) then the server will throw an exception. In order for the client to be able to process these exceptions it must have them on the classpath. As such, we add the following to the common POM so that these core classes are shared between client and server:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>3.1.0.RELEASE</version>
</dependency>

Step 2: Secure the ContactService

There are a number of ways we can define which parts of our application are secured. In web applications it is fairly common to restrict access to certain resources via URLs, so that, for example, all pages under the ‘secure’ directory or all URLs ending with “-admin.htm” are restricted to certain roles. Spring Security supports a powerful expression language for defining this in XML.

We could do this for our client-server application since all of our Service methods are exposed as URLs by the HttpServiceExporter. Given that we are dealing mostly with the Service interfaces in Java and the URL is more or less transparent, a more natural fit is to use Spring Security’s annotation based configuration. There are a couple of different annotation options we can use, but the most powerful is the expression based ones added in version 3.0. With these we can define @PreAuthorize annotations to force authentication before our method is actually executed:

@PreAuthorize("hasRole('myrole')")
public String someSecureMethod()
{
    // this method can only be accessed by users who have 'myrole'
}

As well as being defined at the method level, @PreAuthorize can be applied at the class level, in which case it will apply to all methods within that class.  If a user tries to access any secured method without having the correct role, then Spring Security will prevent this by throwing an AccessDeniedException back to the client.

The roles used in the system are completely up to us and defined through configuration (we will configure our roles in the next step). For our application, let’s define a role called ‘USER’ and another called ‘ADMIN’. In order to use our system at all, a user must have the ‘USER’ role, and in order to add or edit data the user must have the ‘ADMIN’ role.

We can secure our ContactServiceImpl by simply adding the following annotations:

import org.springframework.security.access.prepost.PreAuthorize;

@PreAuthorize("hasRole('USER')")
public class ContactServiceImpl implements ContactService
{
    public List searchContacts(String[] keywords)
    {
        ...
    }

    public Contact getContact(Long contactId)
    {
        ...
    }

    @PreAuthorize("hasRole('ADMIN')")
    public Contact updateContact(Contact updatedContact)
    {
        ...
    }
}

(see the full source code for this class here)

Step 3: Configure Spring Security

Securing our Service is all good and well, but we still need to configure Spring Security to tell it how users should be authenticated and the type of security setup we want. Since our server is a web-based application (which our client connects to over HTTP) we can make use of a lot of the standard Spring Security web stuff.

The web implementation of Spring Security uses a HTTP Filter to intercept calls to the server and manage the security access, so we need to setup this Filter in our web.xml by adding the following config:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Next we need to add some security configuration to our Spring XML configuration (i.e. first-contact-servlet.xml). There’s a little problem here however, in web-land our Filter and our Servlet are defined as two separate entities, which means our Servlet-specific XML file won’t apply to the Filter.

We need to create a new XML configuration file; one that will apply to our application as a whole, not just our servlet. Luckily Spring defines a way to do this via the ContextLoaderListener, which is configured by a ‘context-param’. To do this we add the following to our web.xml file:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/first-contact-app.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

(you can see the full web.xml file here)

This setup will now result in two files being loaded on startup. The first is ‘first-contact-app.xml‘, which should contain all our application-wide configuration, and the second is ‘first-contact-servlet.xml‘, which should contain the minimal configuration just for our servlet.

When there is only a single servlet in use (which is how we currently have things) then the separation is not such a big deal, but when you have multiple servlets in place (for example if you add a SpringMVC or Struts web front-end to your application), then it’s important to ensure the core configuration gets shared, while the servlet specific stuff is not. In general, I put all the service, database, transaction and security configuration in the ‘app’ configuration, and include only the HTTP service exporter configuration in the ‘servlet’ file.

Here are the two updated configuration files. Included is the information needed for the Security setup. I’ll talk through this in detail below.

first-contact-app.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:data="http://www.springframework.org/schema/data/jpa"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="

http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context-2.5.xsd


http://www.springframework.org/schema/data/jpa


http://www.springframework.org/schema/data/jpa/spring-jpa.xsd


http://www.springframework.org/schema/tx


http://www.springframework.org/schema/tx/spring-tx-3.0.xsd


http://www.springframework.org/schema/security


http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.zenjava.firstcontact.service"/>

    <!-- Database Setup -->

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="first-contact" />
    </bean>

    <data:repositories base-package="com.zenjava.firstcontact.service"/>

    <!-- Transaction Setup -->

    <tx:annotation-driven/>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!-- Security Configuration -->

    <security:http create-session="always">
        <security:http-basic/>
    </security:http>

    <security:global-method-security pre-post-annotations="enabled"/>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider>
            <security:user-service>
                <security:user name="admin" password="admin" authorities="ADMIN,USER" />
                <security:user name="user" password="user" authorities="USER" />
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>

</beans>

first-contact-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="/contact.service" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="contactServiceImpl"/>
        <property name="serviceInterface" value="com.zenjava.firstcontact.service.ContactService"/>
    </bean>

</beans>

So let’s look at the additional Spring configuration in more detail. First, we added the Spring namespace to the top of the XML file, no surprises here. Next we defined the ‘http’ element security like so:

<security:http create-session="always">
    <security:http-basic/>
</security:http>

This is a shortcut configuration for setting up all the normal Spring Security beans needed (and there are a number of them) for HTTP based authentication. If you want to understand all this in detail, take the time to read the Spring Security documentation, but in general you can just read this as ‘use web based security’ and move on with your life.

The only two special settings we’ve used are: the ‘create-session’ flag, which is needed to ensure a web session is created to store our authentication in; and the ‘http-basic’ setting, which tells Spring we’ll be using HTTP-BASIC AUTH for login – as you’ll see in the next section, we don’t actually use this but Spring needs to be told something here and this setting is the simplest to use.

Next, we tell Spring that we will be using annotation based authentication. As I mentioned earlier, there are a number of options, including Spring Security’s own @Secured annotation, and the JSR-235 annotations. We’re going to use the Pre/Post Authorization annotations however, and so we configure this like so:

<security:global-method-security pre-post-annotations="enabled"/>

Finally, we need to provide an AuthenticationManager. This is the core component that does user authentication (i.e. it logs users in and out and assigns them roles). In this simple setup we are going to use the most basic of AuthenticationManagers which allows us to hard code all our users and roles directly into the XML file, like so:

<security:authentication-manager alias="authenticationManager">
    <security:authentication-provider>
        <security:user-service>
            <security:user name="admin" password="admin" authorities="ADMIN,USER" />
            <security:user name="user" password="user" authorities="USER" />
        </security:user-service>
    </security:authentication-provider>
</security:authentication-manager>

We’ve define two users, one called ‘admin’ and another called ‘user’ (with matching passwords). We’ve assigned each of these users the corresponding roles via the ‘authorities’ attribute. We can edit this file and add as many users and roles as we want – no other configuration is needed.

If your application has extremely simple security requirements then just maybe you will be able to get away with this setup. In most cases however, you will want to authenticate against a database of users, using encrypted passwords and secure communication channels. In even more advanced scenarios you might want to hook into an LDAP server or single-sign-on CAS server. Spring Security supports all of this (and so much more), and mostly this is achieved just by changing the above AuthenticationManager configuration to something more advanced. I will likely add database driven authentication with encrypted databases in a future post, but if you can’t wait for that check out tutorials like this one.

Believe it or not, we’ve just secured our application. If you try to run your server now and then connect to it with your client, you should get an AccessDeniedException stopping you from accessing all that sensitive contact information.

That’s great! Just one fairly major problem: we can’t yet login so that we can actually access that data.

Step 4: Add a Security Service

In our configuration above we told Spring Security that we wanted to use HTTP Basic Authentication. This was a bit of white lie to make Spring Security happy so that we could then go off and do our own thing. While we could use HTTP Basic Authentication, a simpler and more natural way for us to authenticate with our server is for us to make a simple Java call to a ‘login’ method on our Service, passing in our username and password. It turns out this is not too hard to implement in Spring Security.

First we create a new Service for this, we’ll call it the SecurityService, but the name is arbitrary and completely up to us. Like our ContactService, the SecurityService has a few aspects to it. Firstly the Service interface goes in our common module:

package com.zenjava.firstcontact.service;

public interface SecurityService
{
    void login(String userName, String password);
}

Then we add the implementation to the server module (we’ll leave the implementation body empty for now and deal with this below):

package com.zenjava.firstcontact.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.inject.Inject;

@Service
public class SecurityServiceImpl implements SecurityService
{
    private static final Logger log = LoggerFactory.getLogger(SecurityServiceImpl.class);

    public void login(String userName, String password)
    {
        // todo: login with Spring Security
    }
}

Next we can expose this service over HTTP via the first-contact-servlet.xml configuration file:

<bean name="/security.service" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
    <property name="service" ref="securityServiceImpl"/>
    <property name="serviceInterface" value="com.zenjava.firstcontact.service.SecurityService"/>
</bean>

And then in our client module, we can setup a connection to this service in our FirstContactAppFactory class. We need to refactor our code here slightly so as to be able to reuse the HTTP Request Executor:

@Bean
public SecurityService securityService()
{
    return createService("security.service", SecurityService.class);
}

@Bean
public ContactService contactService()
{
    return createService("contact.service", ContactService.class);
}

@Bean
public HttpInvokerRequestExecutor httpInvokerRequestExecutor()
{
    return new CommonsHttpInvokerRequestExecutor();
}

protected  T createService(String endPoint, Class serviceInterface)
{
    HttpInvokerProxyFactoryBean factory = new HttpInvokerProxyFactoryBean();
    String serverUrl = String.format("http://localhost:8080/%s", endPoint);
    factory.setServiceUrl(serverUrl);
    factory.setServiceInterface(serviceInterface);
    factory.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor());
    factory.afterPropertiesSet();
    return (T) factory.getObject();
}

With our SecurityService now available, we can look at implementing that login method. This involves two steps: first we use the AuthenticationManager (configured above) to validate the username and password and provide an ‘Authentication’ object for us. Second we need to place that ‘Authentication’ object (which represents the logged in user) in the SecurityContextHolder provided by Spring Security. Since we’re using the standard web-based setup, this SecurityContextHolder is associated with the current web session, so each client will be associated with a different instance.

The code for this turns out to be remarkably simple:

@Service
public class SecurityServiceImpl implements SecurityService
{
    private static final Logger log = LoggerFactory.getLogger(SecurityServiceImpl.class);

    @Inject private AuthenticationManager authenticationManager;

    public void login(String userName, String password)
    {
        // never log password information!
        log.info("Logging in user '{}'", userName);
        UsernamePasswordAuthenticationToken authToken
                = new UsernamePasswordAuthenticationToken(userName, password);
        Authentication authentication = authenticationManager.authenticate(authToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        log.debug("User '{}' successfully logged in with authorities: '{}'", userName, authentication.getAuthorities());
    }
}

Now all we need is a Login screen to call this from the client.

Step 5: Add a Login screen

Adding a Login screen is now fairly simple. The general flow is not overly different to our existing Contact management screens – we gather from data from the user and call a Service method.

First some FXML, I’m lazy, so I have adapted this from official getting started with FXML guide:

Login.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:id="root" fx:controller="com.zenjava.firstcontact.gui.login.LoginPresenter" xmlns:fx="http://javafx.com/fxml">

    <center>
        <GridPane alignment="top_center" hgap="8" vgap="8"
                  style="-fx-padding: 40 0 0 0">
            <children>
                <Label text="Sign In"
                       style="-fx-font: NORMAL 14 Tahoma;"
                       GridPane.columnIndex="0" GridPane.rowIndex="0"/>

                <Label text="Username:"
                       GridPane.columnIndex="0" GridPane.rowIndex="1"
                       labelFor="$usernameField"/>
                <TextField fx:id="usernameField" prefColumnCount="10"
                           GridPane.columnIndex="1" GridPane.rowIndex="1"
                           onAction="#login"/>

                <Label text="Password:"
                       GridPane.columnIndex="0" GridPane.rowIndex="2"
                       labelFor="$passwordField"/>
                <PasswordField fx:id="passwordField" prefColumnCount="10"
                               GridPane.columnIndex="1" GridPane.rowIndex="2"
                               onAction="#login"/>

                <Button fx:id="submitButton" text="Login"
                        GridPane.columnIndex="1" GridPane.rowIndex="3"
                        onAction="#login"/>

                <Label fx:id="statusText"
                       GridPane.columnIndex="0" GridPane.rowIndex="4" GridPane.columnSpan="2"
                       style="-fx-text-fill: #ff0000;"/>
            </children>
        </GridPane>
    </center>

</BorderPane>

Next a Presenter for the above FXML. This handles the login button click (and enter key) to call onto our SecurityService with the username and password. The only slight difference to our other server calls is that we actually handle login exceptions, in particular the BadCredentialsException, and tell the user what went wrong (we should really be doing better error handling everywhere, but that’s another post!).

LoginPresenter.java

package com.zenjava.firstcontact.gui.login;

import com.zenjava.firstcontact.gui.main.MainPresenter;
import com.zenjava.firstcontact.service.SecurityService;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;

import javax.inject.Inject;

public class LoginPresenter
{
    private static final Logger log = LoggerFactory.getLogger(LoginPresenter.class);

    @FXML private Node root;
    @FXML private TextField usernameField;
    @FXML private PasswordField passwordField;
    @FXML private Label statusText;

    @Inject private SecurityService securityService;
    @Inject private MainPresenter mainPresenter;

    public Node getView()
    {
        return root;
    }

    public void login(ActionEvent event)
    {
        statusText.setText(null);

        final String username = usernameField.getText();
        final String password = passwordField.getText();

        final Task loginTask = new Task()
        {
            protected Void call() throws Exception
            {
                // never log password information!
                log.info("Logging in as user '{}'", username);
                securityService.login(username, password);
                return null;
            }
        };

        loginTask.stateProperty().addListener(new ChangeListener()
        {
            public void changed(ObservableValue source, Worker.State oldState, Worker.State newState)
            {
                if (newState.equals(Worker.State.SUCCEEDED))
                {
                    log.info("Successfully logged in as user '{}'", username);
                    mainPresenter.showSearchContacts();
                }
                else if (newState.equals(Worker.State.FAILED))
                {
                    Throwable exception = loginTask.getException();
                    if (exception instanceof BadCredentialsException)
                    {
                        log.debug("Invalid login attempt");
                        statusText.setText("Invalid username or password");
                    }
                    else
                    {
                        log.error("Login failed", exception);
                        statusText.setText("Login error: " + exception);
                    }
                }
            }
        });

        new Thread(loginTask).start();
    }
}

We need to load this Presenter and FXML in our FirstContactAppFactory:

@Bean
public LoginPresenter loginPresenter()
{
    return loadPresenter("/fxml/Login.fxml");
}

And we need to sort out our MainPresenter so it can show the Login page when requested to do so:

public class MainPresenter
{
    ...

    @Inject private LoginPresenter loginPresenter;

    public void showLogin()
    {
        log.info("Showing Login page");
        contentArea.setCenter(loginPresenter.getView());
    }

    ...
}

Finally we want to show our Login page on startup, so we make a minor tweak to FirstContactApp:

...
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FirstContactAppFactory.class);
MainPresenter mainPresenter = context.getBean(MainPresenter.class);
mainPresenter.showLogin();
...

Hey presto! We now have a secured client-server application with Login screen.

Step 6: Try it out

Start your server now, and then run an instance of the client. You should be prompted with the following login screen:

Try entering an invalid username and password and check that you get a suitable error message. Next try logging with username ‘user’ and password ‘user’. You should be taken to the normal search screen, which you have permission to access. If you edit or add a Contact however, you will see an AccessDeniedException in the stack trace since you only have the ‘USER’ role, which does not have permission to edit data (our error handling is currently very poor, but you can fix this easily – I’ll probably look at doing so in a future post).

Run another instance of the client and this time login with username ‘admin’ and password ‘admin’. Now try editing Contact information. Since the ‘admin’ user has the ‘ADMIN’ role you can now do everything without any error.

As mentioned, this is a very basic Security setup, just to give you the idea and provide a base to work off. We’re missing simple features like ‘logout’ and we don’t actually associate Contacts with the logged in user when they are created so anything one user creates, the other can see and vice-versa. Additionally our users are all stored in an XML config file, which is not at all flexible and our passwords are stored in plain text, meaning anyone with access to the XML file can read them.

Spring Security provides the means to improve on all of this, and if you’re in a hurry, you can hunt through the Spring Security documentation to work out how. I’ll do my best to add the features most proper JEE applications need over the coming months as time permits, but if you have specific areas that you’d like to see fleshed out, post questions below.

The full source code for this post can be found at: http://code.google.com/p/jfxee/source/browse/trunk/first-contact/first-contact-security/

Series Navigation<< Captains Log

7 Comments

  • Slavko
    March 29, 2012

    Great post, thanks! And, just to say that I admire your knowledge and willingness to share it with the rest of us. :)

  • Alan
    March 31, 2012

    Good stuff. I like anything you create in this blog. I don’t know much about EJB and Application Servers and all
    that hype. If you needed ideas, there you go.
    And the probability that you already know this and is just being thorough is high, but i’ll take my chances:
    In the fxml, you can drop the tag on the parent elements and other most obvious ones that are default
    in javafx 2.1, as posted here. See you and thanks for sharing.

    • Alan
      March 31, 2012

      Correcting;
      tag

    • Alan
      March 31, 2012

      <children>
      The lt and gt sign was getting interpreted as and html element, there it is.

  • May 5, 2012

    I was curious about if you ever considered modifying the page layout of your site? Its very well written; I love what you have got to state. But maybe you could add a little more in the way of written content so people might connect to it better. Youve got a great deal of text for only having one or two images. Maybe you can space it out better?

Leave a Comment