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.. 😉

7 Responses to “Jaas authentication mechanism – is it possible to force j_security_check to go to a specific page?”

  1. Heartburn Home Remedy Says:

    I noticed that this is not the first time you mention the topic. Why have you chosen it again?

    • roneiv Says:

      I’m sorry but I don’t fully understand what you mean with your question? 🙂

      I’ve done a couple of posts around jaas and j_security_check, mostly to show how different problems could be solved – as using this method of handling authentication leaves you with few options to “customize” behaviour as might be defined in given requirements.

  2. bilbo Says:

    I do not understand the point of your post. If you want to specify a specific URL to j_security_check, it means that you do not have any control over the protected URL called, maybe something else than navigation.jsp.

  3. Mike Healey Says:

    Hello Eivind

    I have found this blog useful in the absence of any SAP J2EE equivalent.

    However the “Post” does not work for me. My Servlet does not get picked up. I get the error The requested resource does not exist. Details: Go to main page of this application!

    Please could you provide the dump of the web.xml and web-j2ee-engine.xml that you used to achieve this.

  4. roneiv Says:

    Hi Mike,

    Sorry but it’s been a couple of years since I had time to properly maintain this blog – and I don’t have available the exact code-parts/examples I used to create this post. But as far as I know you shouldn’t have to do anything specific neither to web.xml or web-j2ee-engine.xml.

    Could it be a problem with you servlet-mapping in web.xml so that the Login-servlet is never found? I assume the message you get; “The requested resource does not exist” is related to a 404?

    Anyway, it should be possible for you to test this outside of the JAAS scope – simply create a “Hello World” servlet and try to post from it using a html-form. If you can get that to work it shouldn’t be difficult to do the same inside JAAS, unless there are some container/environment-specific details on your side that I’m not aware of.

    Regards,

    Eivind

  5. Brett Says:

    It’s nearly impossible to find experienced people about this topic, but you seem like you know what you’re
    talking about! Thanks


Leave a comment