Jaas authentication mechanism – is it possible to force j_security_check to go to a specific page?

A proper answer to my question in the title would be – no. There are no good solutions of doing this, and even if you can come up with something that would work there are always black holes and pits to fall in that are difficult to handle. Not to mention that if you do manage to do something it should be considered a hack rather than a soluton. Still, there are things that can be done, and I will show an example here. But please have in the back of your head what I’ve just said, this is not a fool-proof solution.

The way Jaas works is that you specify some protected resources like this in your web.xml:

 <security-constraint>

  <web-resource-collection>
   <web-resource-name>PROTECTED AREA</web-resource-name>
   <description>Require users to authenticate</description>
   <url-pattern>/pages/protected/*</url-pattern>
  </web-resource-collection>

And then, if you try to send a request or get a page that is protected, JAAS brings up the login for you. This can be either BASIC (which means that a dialog box of your browser will be rendered), or FORM based, where you can specify your login pages like this:

   <login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
     <form-login-page>/myLoginPage.faces</form-login-page>
     <form-error-page>/myLoginFailed.faces</form-error-page>
    </form-login-config>
   </login-config>

[/sourceode]

The form you use to login would be something like this:


<form method="POST" name="loginform" action="j_security_check">
                        <table style="vertical-align: middle;">
                        <tr><td>Username:</td><td><input type="text" name="j_username"/></td></tr>
                        <tr><td> Password:</td><td><input type="password" name="j_password"/></td></tr>
                       <tr><td><input type="submit" value="Login" /></td></tr>

If you successfully login, JAAS will forward you to the page you initially requested. If your login fails, it will bring up the loginFailed page (if you used FORM method).This works quite well, but there can be situations where you would like the user to always go to the same page when logging in, or even select which page to go to BEFORE logging in. My example let’s you handle this, up to some point at least. As I said initially, there are no good solutions to do this.But to simplify, let’s say that we use FORM based login, and in our login page the user can select whether he/she wants to go to page A or page B. We will use radio-boxes to let the user choose between this. To give you an idea about the flow we want to follow I wil lgive you a brief overview:

  • First, when starting or logging in to our application, the url have to go to a fixed protected page, let’s call it navigation.jsp e.g. When we try to reach this page, JAAS will trigger our authentication form.
  • We don’t set the action of our login form to “j_security_check,” but to a servlet called Login.
  • When we press Login, the servlet gets the username, password and selectedPage from the request.
  • The servlet then builds up a redirectUrl, sets it in the session, but redirects to j_security_check and passes the username and password as parameters.
  • J_security_check will approve our logon, and will forward us to the initial requested page (navigation.jsp)
  • In navigation jsp, we get the session parameter, and then redirect again. The user is now at the page he/she selected.

The form we will use to log on will in other words be something like this:


		<form method="POST" name="loginform" action="/Login">
                        <table style="vertical-align: middle;">
 			<tr><td>Username:</td><td><input type="text" name="j_username"/></td></tr>
                         <tr><td>assword:</td><td><input type="password" name="j_password"/></td></tr>
                       		<td>A</td><td><input type="radio" name="targetPage" value="a" checked="checked" /> </td>
                         <td>B</td><td><input type="radio" name="targetPage" value="b" /> </td>
        	                 <tr><td><input type="submit" value="Login" /></td></tr>
 		</table>
 	</form>

We see that the radio button with value ‘a’ is checked by default, and that the action now points to our servlet instead. Our servlet would be something like this:


public class Login extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        String target = req.getParameter("targetPage");
        String user = req.getParameter("j_username");
        String pass = req.getParameter("j_password");

        String redirectUrl = "";

        if(target.equalsIgnoreCase("a"))
             redirectUrl = "TheUrlWeWantToGoToForPageA";
        else if(target.equalsIgnoreCase("b"))
            redirectUrl = "TheUrlWeWantToGoToForPageB";
        else
            redirectUrl = "SomeDefaultPageJustInCaseSomethingHappens";

        req.getSession().setAttribute("redirectUrl", redirectUrl);
        resp.sendRedirect("j_security_check?j_username="+user+"&j_password="+pass);
        return;
    }
}

And our navigation page which will handle the redirection based on the user’s selection is very plain and simple:


<%
    String url = (String)request.getSession().getAttribute("redirectUrl");
    response.sendRedirect(url);
%>

If you have made it this far, you should be able to do a logon to your application, and be forwarded to the page you selected in the login form. But let’s say the login failed, you passed some wrong credentials, the way it is now it would bring up a blank page because it’s the login servlet that is “in control.” In other words, we need to do some more manual work in the login servlet before we pass things on to the security check. If the wrong username or password was entered, we should also give the user a note about this, so we will add some functionality to our login page to display potential messages returned from the login servlet.First we modify our login page to get a statusMessage from the request, and if it is not empty, we display it. Before the login-form itself we add this code:

 <%

String statusMessage= "";

statusMessage = (String)request.getSession().getAttribute("statusMessage");
request.getSession().removeAttribute("statusMessage");

boolean renderMessage = false;
if(StringUtils.isNotBlank(statusMessage))
 renderMessage = true;

%>

Then at the bottom of this login page we add some code to display the message (if it’s there):

 <%
 if(renderMessage)
 {
%>
 <table align="center" width="35%">
 <tr>
 	<td>
 		<table align="center">
 			<tr>
 				<td>
 				<%= statusMessage %>
 				</td>
 			</tr>
 		</table>
 	</td>
 </tr>
 </table>
<%
 }
%>

Then we also add some tests in our servlet to detect different “cases” that might arise. Note that to do this properly you would need some methods that could give you information from the database or the security domain you use. For simplicity I imagine I have a class DBHelper that provides me some utility methods:


public class Login extends HttpServlet
{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        String target = req.getParameter("targetPage");
        String user = req.getParameter("j_username");
        String pass = req.getParameter("j_password");

        String redirectUrl = "";
        String statusMessage = "";

        DBHelper dbHelper = new DBHelper();

        //Test if user exists
        if(dbHelper.doesTheUserExist(user))
        {
            //To avoid blank screen on wrong password test if password is valid
            if(dbHelper.isThePasswordCorrect(user, pass))
            {
                        if(target.equalsIgnoreCase("a"))
                            redirectUrl = "TheUrlWeWantToGoToForPageA";
                        else if(target.equalsIgnoreCase("b"))
                            redirectUrl = "TheUrlWeWantToGoToForPageB";
                        else
                            redirectUrl = "SomeDefaultPageJustInCaseSomethingHappens";

                        req.getSession().setAttribute("redirectUrl", redirectUrl);
                        resp.sendRedirect("j_security_check?j_username="+user+"&j_password="+pass);
                        return;
            }
            else
            {            statusMessage = "The login failed - combination of username/password is invalid. Please try again.";
                        redirectUrl = Configurator.getInstance().getHost()+"/"+Configurator.getInstance().getContextRoot()+"/login.faces";
            }
        }
        else
        {
            statusMessage = "The user you tried to log on with doesn't exist.";
            redirectUrl = Configurator.getInstance().getHost()+"/"+Configurator.getInstance().getContextRoot()+"/login.faces";
        }

        //If we reach this point we should go back to the login page. We also set the status message to explain to the user.
        req.getSession().setAttribute("callbackMessage", callbackMessage);
        resp.sendRedirect(redirectUrl);
    }
}

Ok, as I said in the beginning this “solution” should not be considered a proper one, passing the credentials through the parameter string e.g. is not actually a good way of doing things. But I’ll leave that for you to decide.. 😉