This post is part of a series on building a custom worklist for BPM/SOA 11g.
In the previous posts we have created our model and skeleton view. Now, we want to set up security. One of our goals was to integrate with WebLogic security so that we don’t need to worry about security in our application. It also makes our application more secure, as we will not be collecting or storing any credentials.
Using WebLogic security is pretty straight forward. We will use a FORM login page, as opposed to BASIC HTTP authentication or some other method. This way we can have control over how our login page looks. So we will need to create the login page and the form.
We also need to set up some rules in our deployment descriptors to tell WebLogic what content can be accessed by anonymous users and which require authentication.
Since we are using Spring Web MVC, we will be accessing our login page through a controller. Our anonymous login page will be accessed through the com.oracle.ateam.HomeController, which will use the view src/main/webapp/WEB-INF/jsps/Home.jsp.
In our Spring configuration file, src/main/webapp/WEB-INF/worklist-servlet.xml, we map the URL /home to the controller. All of our other URLs end with ‘.do‘, e.g. /tasklist.do. We will use the absence of the ‘.do‘ in our security rules to make the login page available to anonymous users.
Here are our URL mappings:
/home=homeController /tasklist.do=tasklistController /taskdetail.do=taskdetailController /login.do=loginController /logout.do=logoutController /createtasks.do=createTestTasksController /processtask.do=processTaskController /addcomment.do=addCommentController /initiatelist.do=initiateListController /initiatetask.do=initiateTaskController /error.do=errorController
Let’s take a look at the login form. Here is the essential code for the login form:
<form action="j_security_check" method="post"> <div>Username:</div> <input name="j_username" size="20" type="text"> <div>Password:</div> <input name="j_password" size="20" type="password"> <input type="submit" value="Login"> </form>
The actual code (available in Subversion) has a fair bit more HTML in there to make it look nice 🙂 but the snippet above has everything we need to understand how it works.
The important part is the FORM action and the names of the input fields. The action needs to be j_security_check. This is how we invokeWebLogic authentication. The user name needs to be in an input field called j_username and the password in a password field called j_password. The form must use the POST method.
When the user completes this form and submits it, WebLogic will attempt to authenticate them using the provided information. They can be authenticated against any of the WebLogic Authentication Providers, just like any other WebLogic application.
The other ingredient is to set some rules in the deployment descriptor. In the Java EE standard web application deployment descriptor – src/main/webapp/WEB-INF/web.xml – we need to set the ‘welcome file,’ some ‘security constraints’ and a ‘login config.’ Let’s take a look at these now. Here is the welcome file:
<welcome-file-list> <welcome-file>/login.do</welcome-file> </welcome-file-list>
Notice that this points to /login.do, the first page we want users to see after they have authenticated. When a user tries to access our application, they will be directed to this page, but WebLogic will notice that they are not logged in (due to the configuration entries we are about to make) and redirect them to the login page.
Here is the section that defines the security constraints:
<security-constraint> <web-resource-collection> <web-resource-name>worklist</web-resource-name> <url-pattern>/*do</url-pattern> <http-method>GET</http-method> </web-resource-collection> </security-constraint>
Here we are saying that any URL matching /*do will only be served to authenticated users. So given our mappings (above) this means that everything except for the home (login) page will require the user to login first.
Here is our login config:
<login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/home</form-login-page> <form-error-page>/home</form-error-page> </form-login-config> </login-config>
This is telling WebLogic that we are using FORM based authentication and that our login form is accessed by sending the user to the URL /home, which maps to our HomeController and our Home.jsp login form view.
We also set the error page to the same URL, so that the user will be sent back to the login page if they do not login successfully. Our login controller will add a message to tell the user about the failure to authenticate. We will see this shortly.
That completes the configuration of the deployment descriptor. Now we need to implement a login controller. Since we are delegating the actual authentication to WebLogic, our login controller does not actually need to do much. We will just check if the user is logged in successfully, validate they can access the workflow engine, and if not, we will send a message to tell them to try again. Here is the code:
package com.oracle.ateam; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.oracle.ateam.util.MLog; import com.oracle.ateam.domain.MTaskList; public class LoginController extends SimpleSuccessFailureController { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { MLog.log("LoginController", "Entering handleInternalRequest()"); if (request.getRemoteUser() == null) { // user has not tried to login to weblogic yet // this will happen on the first visit to the webapp request.getSession(true).setAttribute("message", ""); return new ModelAndView(getFailureView()); } if(MTaskList.login(request)) { request.getSession(true).setAttribute("message", ""); } else { request.getSession(true).setAttribute("message", "Error: Login details incorrect."); return new ModelAndView(getFailureView()); } return (new TaskListController()).handleRequest(request, response); } }
Again, the source code in Subversion has more comments.
If the user has logged in to WebLogic successfully, their HttpServletSession will return the username when we call getRemoteUser(). Otherwise, it will return null. So first, we check this to see if the user is logged on. We need to check because we don’t want to give the user an error message when they first arrive at the login page, before they have tried to login.
Next, we use the MTaskList.login(HttpServletRequest) method to check if the user has access to the workflow engine. If this method returns true then we are happy and we can send the user to the first page of the application. These users will arrive at the return (new TaskListController()).handleRequest(request, response) statement. This shows how we can chain together Spring controllers. Since the user is logged in, we just pass them along to the TaskListController which will get a list of tasks, put that in the model and pass it to its view. We will come to all of this in a later post! For now, just notice how we can easily chain together the controllers to redirect the user to another page.
If the user did authenticate to WebLogic, but does not have permission to access the workflow engine, we consider this a failed login attempt and send them an error message. The error message is sent by adding it to the session and returning the failure view.
Now, a login is not much good without a logout, so let’s take a look at the com.oracle.ateam.LogoutController that handles the user logout. Again, this is a fairly simple class. Here is the code (more comments in Subversion):
package com.oracle.ateam; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.oracle.ateam.util.MLog; import com.oracle.ateam.domain.ContextCache; public class LogoutController extends SimpleSuccessFailureController { @Override protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { MLog.log("LogoutController", "Entering handleInternalRequest()"); // get rid of the context ContextCache.getContextCache().remove(request.getRemoteUser()); // invalidate the session request.getSession().invalidate(); return super.handleRequestInternal(request, response); } }
There are two things we need to do to cleanly logout the user. First, we remove their context from the ContextCache, then we invalidate their session. We also do this in the com.oracle.ateam.ErrorController which we will see later on.
That completes our security work! (Wasn’t too bad, was it?) Now, let’s move on to the next post, where we will create that Task List Controller and View.