Custom Authentication Filter - novalexei/mod_servlet GitHub Wiki

Custom Authentication Filter

No matter how many authentication methods are provided by the server it's never enough. We all want our own authentication procedure which obviously not compatible with all the existing methods. So let's get down to writing custom authentication filter.

Here is what we need from it:

  • On unauthenticated request it will redirect the request to login form.
  • Before redirecting it saves current URL in order to redirect to it after the user was successfully validated (just a good manners)
  • On receiving user-password values from login form it validates them.
  • If the validation succeeded, redirect to initial saved URL
  • On authenticated request, let it through.

Doesn't seem very simple, but we are prepared for challenges by now.

class auth_filter : public servlet::http_filter
{
public:
    void do_filter(http_request& req,
                   http_response& resp, filter_chain& chain) override;
private:
    void generate_login_form(std::ostream& out);
    bool validate_login(const optional_ref<const std::string> user,
                        const optional_ref<const std::string> passwd);
};

FILTER_EXPORT(authFilter, auth_filter)

void auth_filter::do_filter(http_request& req,
                            http_response& resp,
                            filter_chain& chain)
{
    // Check first if this is the login form.
    if (req.get_path_info() == "/login.form")
    {
        // generate the login form.
        generate_login_form(resp.get_output_stream());
        return;
    }
    const URI &uri = req.get_request_uri();
    auto path = uri.path();
    // Now check if the user is trying to login
    auto found = path.rfind("login");
    if (found != string_view::npos && found + 5 == path.length())
    {
        // Login attempt. Validate the login information.
        auto uname = req.get_parameter("uname");
        if (!validate_login(uname, req.get_parameter("psw")))
        {
            // Validation failed forward to login form
            req.forward("/login.form");
            return;
        }
        // Validation succeeded
        http_session &session = req.get_session();
        session.set_principal(new named_principal(uname ? *uname : ""));
        // Check if we have stored URL to redirect to
        auto redirectURL = session.get<std::string>("login:redirect");
        if (redirectURL)
        {
            // Save the URL and remove it from session.
            // We don't need it any more.
            std::string url{*redirectURL};
            session.erase("login:redirect");
            // redirect
            req.forward(url);
            return;
        }
        // No, just continue to the servlet.
        chain.do_filter(req, resp);
        return;
    }
    // This is not login attempt, maybe we already logged in?
    http_session &session = req.get_session();
    if (session.get_principal())
    {
        // Yes we are, continue the processing.
        chain.do_filter(req, resp);
        return;
    }
    // No we are not. Let's store initial URL to redirect later.
    auto path_view = req.get_path_info();
    std::string path_info{path_view.data(), path_view.length()};
    session.put<std::string>("login:redirect", std::move(path_info));
    // Redirect to login form.
    req.forward("/login.form");
}

It is a bit long, but with it is quite a lot going on here. We also need to implement generate_login_form and validate_login methods. generate_login_form is simple:

void auth_filter::generate_login_form(std::ostream& out)
{
    out << "<!DOCTYPE html>\n"
           "<html>\n"
           "<head>\n"
           "<title>Login form</title>\n"
           "</head>\n"
           "<body>\n"
           "  <form method=\"POST\" action=\"login\">\n"
           "    <label><b>Username:</b></label>\n"
           "    <input type=\"text\" name=\"uname\" required>\n"
           "    <br/> <label><b>Password:</b></label>\n"
           "    <input type=\"password\" name=\"psw\" required>\n"
           "    <br/> <button type=\"submit\">Login</button>\n"
           "  </form>\n"
           "</body>\n"
           "</html>\n";
}

And validate_login might be as fancy as you wish, but for demonstration sake let's just let anyone with password "secret" in:

bool auth_filter::validate_login(const optional_ref<const std::string> user,
                                 const optional_ref<const std::string> passwd)
{
    return user && passwd && *passwd == "secret";
}

We are ready to build and run this filter now. First undo all the basic authentication configuration you did in previous section.

Deploy the filter, configure in web.xml deployment descriptor check_principal_servlet from previous section and auth_filter for url-pattern /* to cover all URLs.

Restart the server and check how authentication filter works.

If this one didn't scare you, than what about Single Sign-On?

To The Programming Guide

⚠️ **GitHub.com Fallback** ⚠️