$Author: bastafidli $
$Date: 2007/03/11 06:30:45 $
$Revision: 1.13 $
$RCSfile: tutorial_security.html,v $
The application is almost complete. All the code required to browse, create, modify and delete data is in place. The last remaining item on our list is to secure the application. The requirement is that only the authorized users can modify the data while all the others can still read it.
Security of web applications is critical business requirement, because the application can be easily accessed by anybody who has a network connection to a given server. One of the common security requirements is to perform authentication of the users so that only the approved users can access certain data or execute certain operations. Open Core provides framework to implement authentication easily.
Note: OpenSubsystems provides Open Security, subsystem that addresses all aspects of application security including authentication, authorization, session tracking, user and role management and domain partitioning. By integrating Open Security into your application you can avoid the design and coding effort we will demonstrate in this section of the tutorial and you will achieve much more robust and reliable solution. Since this step of the tutorial focuses on Open Core only, we will demonstrate basic authentication that can be easily achieved just using Open Core.
We will design and implement user interface, that will allow user to login and logout. We will also implement the web tier support for this user interface. At last, we will override and implement the handlers that Open Core provides to authenticate user. We will also decide how to handle situation when the application doesn't allow access to users that are not logged in.
The user interface that allows user to login and logout is implemented the same way as we have discussed earlier. It consists of only two pages.
login.jsp
logout.jsp
Access to these two pages is provided using link generated
by the layout template blog.jsp
.
The code snippet in the template displays the correct link
depending on, if the user is already logged in or not
<bean:define id="loggedin" name="loggedin" scope="request" type="java.lang.Boolean"/>
...
<logic:equal name="loggedin" value="false">
<a href="<%=contextpath%>/<%=BlogNavigator.LOGIN_WEB_PAGE%>">Login</a>
</logic:equal>
<%-- If user is logged in, he can modify the blog data --%>
<logic:equal name="loggedin" value="true">
<a href="<%=contextpath%>/<%=BlogNavigator.LOGOUT_WEB_PAGE%>">Logout</a>
</logic:equal>
The user can this way login or logout from any location in the application and the individual pages don't have to be concerned how does the user access the login and logout page.
The doGet method of
BlogEditServlet
is responsible for displaying these two new pages to user.
protected void doGet(
HttpServletRequest hsrqRequest,
HttpServletResponse hsrpResponse
) throws ServletException,
IOException
{
if (isLoginPage(hsrqRequest))
{
processNewLoginForm(hsrqRequest, hsrpResponse);
}
else if (isLogoutPage(hsrqRequest))
{
processNewLogoutForm(hsrqRequest, hsrpResponse);
}
else
{
// Now let the browser servlet process the request
super.doGet(hsrqRequest, hsrpResponse);
}
}
The requests submitted from these pages are handled by
doPost method as described earlier. The
implementation detects what action the user wants to execute
and dispatches the call to a correct method.
protected void doPost(
HttpServletRequest hsrqRequest,
HttpServletResponse hsrpResponse
) throws ServletException, IOException
{
switch(getFormToProcess(hsrqRequest))
{
case FORM_LOGIN_ID :
{
processLoginForm(hsrqRequest, hsrpResponse);
break;
}
case FORM_LOGOUT_ID :
{
processLogoutForm(hsrqRequest, hsrpResponse);
break;
}
...
}
Before we take a look at the implementation of these methods,
lets discuss the framework that Open Core provides to handle
authentication. Every request sent to the server is passed to
verifyLogin method defined in
WebSessionServlet.
Its purpose is to verify identity of the user that submitted
the request. We will override this method and in our case the
implementation will detect two situations: if the user is
already logged in or if the user is just trying to login. If
neither one of these two situations is detected, it just
simply returns null because it doesn't know the user's identity.
protected Principal verifyLogin(
HttpSession hsSession,
HttpServletRequest hsrqRequest,
HttpServletResponse hsrpResponse
) throws ServletException,
IOException
{
Principal currentUser = null;
if (getFormToProcess(hsrqRequest) == FORM_LOGIN_ID)
{
// User is trying to log in, so try to log him/her in
final String strLogin;
String strPassword;
strLogin = hsrqRequest.getParameter("LOGIN");
strPassword = hsrqRequest.getParameter("PASSWORD");
// Verify the user name and password and if it is valid, let the
// user proceed otherwise return an error message
if ((m_strLogin.equals(strLogin))
&& (m_strPassword.equals(strPassword)))
{
currentUser = new Principal()
{
public String getName()
{
return strLogin;
}
};
}
else
{
CallContext.getInstance().getMessages().addErrorMessage(
"The user name or password is not valid.");
// User have tried to login again so reset the info from previous
// login since it is no longer valid
WebSessionUtils.resetSessionAndUserInfo(hsSession);
}
}
else
{
// User is not trying to log in so see if he is already logged in
if (WebSessionUtils.isLoggedIn(hsSession))
{
currentUser = WebSessionUtils.getLoggedInUserInfo(hsSession);
}
}
return currentUser;
}
Once we know that before each request is forwarded for processing, the identity of a user has been already verified, it is easy to implement login a logout. During login we just create an internal session identifier for this user and set it up so that we can detect it during subsequent requests. As a last step, we forward the user either to a URL he tried to access before login or to the main page.
protected void processLoginForm(
HttpServletRequest hsrqRequest,
HttpServletResponse hsrpResponse
) throws IOException,
ServletException
{
s_logger.entering(this.getClass().getName(), "processLoginForm");
try
{
HttpSession hsSession;
Principal currentUser;
String strSessionId;
String strURL;
// We do not want to create new session if it doesn't exist since that
// is the responsibility of the parent classes. We should have session
// at this point
hsSession = hsrqRequest.getSession(false);
currentUser = CallContext.getInstance().getCurrentUser();
if (GlobalConstants.ERROR_CHECKING)
{
assert hsSession != null : "Session must exist at this point";
}
if (currentUser != null)
{
// If users tries to login again using the same session (by directly
// typing login URL) while it is already logged in,
// the session tracking object in the application server wouldn't be
// destroyed, just for that case go throught the session object and
// remove all values in it, this will cause logout if it was previously
// logged in
WebSessionUtils.resetSessionAndUserInfo(hsSession);
// This needs to by synchronized so that we generate the unique id for
// each session
synchronized (FORM_LOGIN_NAME)
{
strSessionId = Integer.toString(++s_iSessionCounter);
}
WebSessionUtils.setSessionAndUserInfo(hsSession, strSessionId, currentUser);
// Check if there is any URL specified in the session object
strURL = getLoginRedirect(hsSession, hsrqRequest);
// We are doing the redirect so reset the stored value
resetLoginRedirect(hsSession);
if ((strURL == null) || (strURL.length() == 0))
{
strURL = getNavigator(hsrqRequest).getRootURL();
}
hsrpResponse.sendRedirect(strURL);
}
else
{
// The login was incorrect so display the login page again
hsrqRequest.setAttribute("blognavigator", getNavigator(hsrqRequest));
displayUI(BLOGEDIT_LOGIN_PAGE, hsrqRequest, hsrpResponse);
}
}
catch (Exception eExc)
{
s_logger.log(Level.WARNING,
"An error has occured while processing login page.", eExc);
messageBoxPage(hsrqRequest, hsrpResponse, "Error", eExc.getMessage(),
getNavigator(hsrqRequest).getRootURL(),
eExc.getCause());
}
finally
{
s_logger.exiting(this.getClass().getName(), "processLoginForm");
}
}
Notice how we have retrieved the identity of the user on behalf of who we are processing the request using call
Principal currentUser = CallContext.getInstance().getCurrentUser()
This call can be used anywhere in the code whenever the application needs to know who is executing that portion of the code.
The verifyLogin method is using
WebSessionUtils.getLoggedInUserInfo(hsSession);
to detect whether the user is already logged in. Knowing this, we just need to ensure that during logout we reset the session information for example in a following way
protected void processLogoutForm(
HttpServletRequest hsrqRequest,
HttpServletResponse hsrpResponse
) throws IOException,
ServletException
{
s_logger.entering(this.getClass().getName(), "processLogoutForm");
try
{
HttpSession hsSession;
// We do not want to create new session if it doesn't exist since that
// is the responsibility of the parent claees. We should have session
// at this point
hsSession = hsrqRequest.getSession(false);
if (GlobalConstants.ERROR_CHECKING)
{
assert hsSession != null : "Session must exist at this point";
}
WebSessionUtils.resetSessionAndUserInfo(hsSession);
// Redirect user to the page displaying all blogs
hsrpResponse.sendRedirect(getNavigator(hsrqRequest).getRootURL());
}
catch (Exception eExc)
{
s_logger.log(Level.WARNING,
"An error has occured while processing logout page.", eExc);
messageBoxPage(hsrqRequest, hsrpResponse, "Error", eExc.getMessage(),
getNavigator(hsrqRequest).getRootURL(),
eExc.getCause());
}
finally
{
s_logger.exiting(this.getClass().getName(), "processLogoutForm");
}
}
From our discussion you can see that Open Core makes easy
to ensure that every request is authenticated and find out
anywhere in the code who the current user is. In addition, by
changing a single
configuration setting Open Core allows to secure the
entire application and let only the authenticated users to
access any portion of the application. To do this, Open Core
needs to know if there is any specific handling required to
display the login page. In case the application needs to do
anything special, it can do so by overriding the
redirectToLogin method. In our case we just
ensure that if the user already requested the login page, we
display it without any further delays.
protected void redirectToLogin(
HttpServletRequest hsrqRequest,
HttpServletResponse hsrpResponse
) throws ServletException,
IOException
{
BlogNavigator navigator = getNavigator(hsrqRequest);
if (navigator.isLoginPage())
{
// User is already trying to display login page, just show it
processNewLoginForm(hsrqRequest, hsrpResponse);
}
else
{
// We were asked to display login page so just let client go to the
// the login page to get correct URL to the address bar and we will
// most likely come back through the if above
super.redirectToLogin(hsrqRequest, hsrpResponse);
}
}
This concludes the coding effort. Next step is to correctly configure the application so that it can be easily deployed.
Next:
Configuration, packaging and deployment
Previous:
Constructing the web application