Wicket HTTPS Tutorial Part Three: Creating a Secure Form Submit From a Non Secure Page

The third part of my Wicket HTTPS tutorial describes how you can submit a form by using HTTPS protocol when the form is added to a non secure page (served over HTTP).

This is a somewhat common use case in web applications even though it violates the fundamental principle of HTTPS protocol (trust).

Thus, I felt obliged to provide solution for it.

You should not use this approach in your Wicket application. If you are considering of using this approach in your application, you must read a blog post titled: Your login form posts to HTTPS, but you blew it when you loaded it over HTTP.

Required Steps

Our goal can be achieved by following these these steps:

  1. Enabling and configuring the HTTPS support of Apache Wicket
  2. Creating the login form, which submits itself to a secure url
  3. Adding the created login form to a non secure page
  4. Creating a secure page, which processes the information of the login form

If you are not familiar with the HTTPS support of Apache Wicket, you should read the first and second part of my Wicket HTTPS tutorial, because I will skip the concepts, which are introduced in my previous blog entries.

Creating the Login Form

The creation of login form is divided into two smaller phases:

  • Creating a url generator which generates the login url
  • Creating the actual login form

I will describe these phases next.

First, I created a url generator, which is capable of generating the absolute login url. The implementation of the url generator is following:

public class UrlGenerator {
    private static final char CONTEXT_PATH_SEPARATOR = '/';

    public static String generateAbsoluteLoginUrl(String protocol, Integer port, HttpServletRequest request) {
        StringBuilder urlBuilder = new StringBuilder();

        urlBuilder.append(protocol);
        urlBuilder.append("://");
        urlBuilder.append(request.getServerName());
        if (port != null) {
            urlBuilder.append(":");
            urlBuilder.append(port);
        }

        String contextPath = request.getContextPath();

        if (contextPath != null) {
            urlBuilder.append(contextPath);
            if (!contextPath.isEmpty() && contextPath.charAt(contextPath.length()-1) != CONTEXT_PATH_SEPARATOR) {
                urlBuilder.append(CONTEXT_PATH_SEPARATOR);
            }
        }

        urlBuilder.append(WicketApplication.LOGIN_HANDLER_PATH);

        return urlBuilder.toString();
    }
}

Second, I created a login form, which creates an absolute action url. The action url address of a form can be manipulated by overwriting the onComponentTag() method. The implementation of that method should create the absolute action url and replace the relative action url with it. My implementation uses the UrlGenerator class to create the secure login url. The source code of the login form is given in following:

public class LoginForm extends StatelessForm<LoginDTO> {

    private static final String TAG_ATTRIBUTE_ACTION_NAME = "action";
    private static final String PROTOCOL_HTTPS_PREFIX = "https";

    private static final String WICKET_ID_FEEDBACK = "feedback";
    private static final String WICKET_ID_USERNAME = "username";
    private static final String WICKET_ID_PASSWORD = "password";

    public LoginForm(String id) {
        super(id, new CompoundPropertyModel(new LoginDTO()));
        init();
    }

    public LoginForm(String id, IModel iModel) {
        super(id, iModel);
        init();
    }

    private void init() {
        add(new FeedbackPanel(WICKET_ID_FEEDBACK));
        add(new TextField(WICKET_ID_USERNAME)
                .setRequired(true)
        );
        add(new PasswordTextField(WICKET_ID_PASSWORD)
                .setRequired(true)
        );
    }

    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);

        HttpServletRequest request = this.getWebRequest().getHttpServletRequest();

        //Create an absolute url, which points to a page used to process login information
        String securedAbsoluteActionUrl = UrlGenerator.generateSecureLoginUrl(PROTOCOL_HTTPS_PREFIX, WicketApplication.HTTPS_PORT, request);

        tag.put(TAG_ATTRIBUTE_ACTION_NAME, securedAbsoluteActionUrl);
    }
}

Adding the Login Form to a Non Secure Page

The next step is to add the login form to a non secure page. The source code of the login page is given in following:

public class LoginPage extends WebPage {
    private static final String WICKET_ID_HOMEPAGE_LINK = "homepageLink";
    private static final String WICKET_ID_LOGINFORM = "loginForm";

    public LoginPage(PageParameters parameters) {
        super(parameters);
        init();
    }

    protected void init() {
        add(new BookmarkablePageLink(WICKET_ID_HOMEPAGE_LINK, HomePage.class));
        add(new LoginForm(WICKET_ID_LOGINFORM));
    }
}

Creating a Secure Page for Processing the Login Information

The last step is to create a secure page, which processes the given login information. This page implements following functions:

  • Form validation. Since the creation of an absolute login url seems to bypass the built-in form validation support of Apache Wicket, the page should verify that both username and password are entered.
  • Login. The login handler page must verify that the username and password entered by user are valid.
  • Flow control. If the login is valid, user is redirected to the homepage of the application. Otherwise the user is redirected back to the login page.

The source code of the login handler page is given in following:

@RequireHttps
public class LoginHandlerPage extends WebPage {

    private final String MESSAGE_KEY_LOGIN_FAILED = "error.login.failed";
    private final String MESSAGE_KEY_PASSWORD_EMPTY = "loginForm.password.Required";
    private final String MESSAGE_KEY_USERNAME_EMPTY = "loginForm.username.Required";

    private final String PARAM_USERNAME = "username";
    private final String PARAM_PASSWORD = "password";

    private final String USERNAME = "foo";
    private final String PASSWORD = "bar";

    public LoginHandlerPage(final PageParameters parameters) {
        String username = parameters.getString(PARAM_USERNAME);
        String password = parameters.getString(PARAM_PASSWORD);

        boolean requiredInformationEntered = isRequiredInformationEntered(username, password);

        if (requiredInformationEntered) {
            if (isLoginValid(username, password)) {
                storeUserInfoToSession(username);
                redirectToTargetPage(HomePage.class);
            }
            else {
                //Sets a feedback message to session. This is necessary,
                //because the feedback message must exists across requests.
                getSession().error(getString(MESSAGE_KEY_LOGIN_FAILED));
                redirectToTargetPage(LoginPage.class);
            }
        }
        else {
            redirectToTargetPage(LoginPage.class);
        }
    }

    /**
     * Wicket built-in validation is not done (I guess the reason is the generation
     * of absolute url, which bypasses some of Wicket Form functions). This method
     * validates that the username and password are entered.
     * @param username
     * @param password
     * @return  true if both username and password are entered and false otherwise.
     */
    private boolean isRequiredInformationEntered(String username, String password) {
        boolean isValidLogin = true;

        if (username.isEmpty()) {
            getSession().error(getString(MESSAGE_KEY_USERNAME_EMPTY));
            isValidLogin = false;
        }

        if (password.isEmpty()) {
            getSession().error(getString(MESSAGE_KEY_PASSWORD_EMPTY));
            isValidLogin = false;
        }

        return isValidLogin;
    }

    private void storeUserInfoToSession(String username) {
        WicketHttpsSession session = WicketHttpsSession.get();
        //Invalidates the current session and creates a new secure session.
        //The created secure session cannot be accessed when using http
        //protocol. This option should be used when only https protocol is
        //used after the user has logged in.
        //session.replaceSession();
        session.setAuthenticatedUsername(username);
    }

    private boolean isLoginValid(String username, String password) {
        if (username.equals(USERNAME) && password.equals(PASSWORD)) {
            return true;
        }
        return false;
    }

    private void redirectToTargetPage(Class pageClass) {
        setResponsePage(pageClass);
    }
}

Final Words

I have now described how you can create a secure form submit from a page, which is served over HTTP protocol. You can also download an example Maven project, which is tested with Apache Wicket 1.4.15. It contains the complete source code of a simple example application, and hopefully it will help you to get a better understanding of the concepts, which are described in this blog entry.

UPDATE: You should also check out the blog entry written by George Armhold: Wicket: submitting a form over SSL from an unsecured page. His solution to this problem is a bit cleaner than mine.

4 comments… add one
  • Esko Mar 26, 2011 @ 22:14

    Thanks for figuring all this stuff out! I do have to say that this looks almost atrociously complex though when comparing to what it really does...

    • Petri Mar 27, 2011 @ 19:35

      I totally agree with you. Also, I don't like the fact that this solution bypasses parts of the Wicket's implementation (like form validation for example), but I was unable to find a solution for that. I think that this solution is a somewhat ugly hack, but if you cannot use a secure login page, this might be the only way to implement a safe login form.

  • Ronan Mar 15, 2012 @ 0:24

    Thanks for taking the time to put this together. It made life easy for me when readying my Wicket application for SSL.

    Ronan

    • Petri Mar 24, 2012 @ 10:26

      Hi Ronan,

      I am glad to hear that I could help you out.

Leave a Reply