Friday, July 16, 2010

JEE Authentication: Login Errors, Roles, Access Denied, and Logout

In a previous post around LDAP authentication using Jetty, I had some unfinished business. Here I'll deal with login errors, restrict the login to a given role, deal with subsequent access denied scenarios, display the currently logged-in user name, and provide log-out functionality.

The web.xml I'd started with already specified a login-error page, but I was simply pointing it to the same page as the login form. This results in that page simply refreshing without any indication to the user of why that happened. What we want is some kind of message displayed that indicates the given username or password was not valid.

Now, I could simply copy the login.jsp to another JSP, say login-error.jsp, add a message to that page and alter the web.xml to specify that new page on login error:
<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>ldap</realm-name>
    <form-login-config>
        <form-login-page>/faces/login/login.jsp</form-login-page>
        <form-error-page>/faces/login/login-error.jsp</form-error-page>
    </form-login-config>
</login-config>
But now both login.jsp and login-error.jsp contain the same FORM snippet. Now if I next want to specify yet another login page to which the user is directed on an "access denied" error (which we'll deal with momentarily), and I'm still afflicted with copy-paste fever, I'll have the same login form in three places. Let's factor it out instead into a JSP snippet named loginform.jsp:
<form method=post action="j_security_check">
    <label for="j_username" style="font-weight:bold">Username</label>
    <input type="text" name="j_username" id="j_username" style="margin-left:10px"/>
    <label for="j_password" style="font-weight:bold">Password</label>
    <input type="password" name="j_password" id="j_password" style="margin-left:10px"/>
    <input type="submit" value="Log In"/>
</form>
The usual login page now looks like this:
<div style="margin-top:25px; margin-left:25%">
    <h2 style="text-decoration:underline; color:blue;margin-left:-10px">Management App</h2>
    <h3>Please Log In</h3>
    <%@include file="loginform.jsp"%>
</div>
And I'll provide an "access denied" page that looks like this:
<div style="margin-top:25px; margin-left:25%">
    <h2 style="text-decoration:underline; color:blue;margin-left:-10px">Management App</h2>
    <h3>Please Log In</h3>
    <div style="color:red;font-weight:bold;">Authentication failed. User is not in Required Role.</div>
    <%@include file="loginform.jsp"%>
</div>
Configuring HTTP-403 responses (i.e. access denied) to navigate to this page is done like so:
<error-page>
    <error-code>403</error-code>
    <location>/login/accessDenied.jsp</location>
</error-page>
Access denied problems will occur if a given user is not in the expected role. So far, the web.xml has granted authorization to all roles by virtue of the wild-card for the role-name. We can restrict that by naming a role instead:
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Resources</web-resource-name>
        <url-pattern>*.iface</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>admin</role-name>
    </auth-constraint>
    <user-data-constraint>
        <transport-guarantee>
            CONFIDENTIAL
        </transport-guarantee>
    </user-data-constraint>
</security-constraint>
Now, once a user provides his/her credentials at the login form, these are first checked by the LDAP module (configured as a Jetty realm, as per the previous post); if those are valid, the user is next confirmed to be assigned the admin role. If that is the case, all is well and navigation will proceed as configured by the JSF navigation rule (again, please see the previous post). If the credentials are not valid, the user will be redirected to the login-error page, this time with an informative error message about the login problem. If the credentials are good but the user is not assigned the admin role, the user will be redirected to the access-denied page, again with a informative message.

Displaying the current username is a simple matter of leveraging the built-in getRemoteUser(), provided by HttpServletRequest. Since, after logging in, I've transitioned into a JSF application - and because I'm adverse to using JSP scriptlets to accomplish use of that getter once I'm in JSF - I simply provide a getter in one of my JSF managed beans that fetches the HTTP request and returns the user name:
public String getUserName() {
    return getServletRequest().getRemoteUser();
}
...referencing it, as usual, with the JSF expression language:

User: #{svh.userName}

Finally, I'll provide log-out functionality. First, a command link (done with the IceFaces framework):
<ice:commandLink 
    action="logout" immediate="true" value="Logout" 
    style="margin-left:5px;color:blue;font-size:medium"/>
The logout action is mapped with a navigation rule:
<navigation-rule>
    <description>Logout</description>
    <from-view-id>/*</from-view-id>
    <navigation-case>
        <from-outcome>logout</from-outcome>
        <to-view-id>/login/logout.jsp</to-view-id>
        <redirect/>
    </navigation-case>
</navigation-rule>
...taking us to the logout.jsp page, which invalidates the session and invites the user to log back in:
<% session.invalidate(); %>

<div style="margin-top:5px; margin-left:25%">
    <h2 style="text-decoration:underline; color:blue;margin-left:-10px">Management App</h2>
    <h3>Logout Succeeded</h3>
    <p>
        You are now logged out of the Management UI.
    </p>
    <a href="/index.jsp" style="text-decoration:underline">Return to Login page.</a>
</div>
The index.jsp redirects to the application's JSF-based home page:
<html>
    <head>
        <title>Management UI</title>
    </head>
    <body>
    <%
        String redirectURL = "./index.iface";
        response.sendRedirect(redirectURL);
    %>
    </body>
</html>
And, as mentioned in the first post, all iface resources are protected by a security constraint, so this will redirect to the login page.

No comments:

Post a Comment