Thursday, November 09, 2006

Customize form based authentication in J2EE web container


Sometimes it is just not enough to just collect the user name and password through

the login page. In my work, it has a request to also collect the logon reason at the
same time the user logs in.

Form based authentication is adopted because it provids a logon page and
you have the chance to customize it. Since the default form based authentication
supports only two input parameters, they are j_username and j_password, and the
j_security_check is a container built-in function and you can not hack on it, how
can it be customized and support any extra parameters probably will be defined on
the logon page? I searched it on the Internet and got nothing.

It's a time to do your own invention.

I tried filter. It failed because it happens after the j_security_check. Then I
thought since the session is already there when you see the logon page, and the
logon is only for those protected resources, an unprotected resource might be
helpful to handle the extra information before it access the protected resource.
Tested it, it's through!

Here's how to make it work.

1. Define a jsp that handle the logon request before j_security_check. Let's name it
as preLogin.jsp. Assuming the extra information is called "reason", it has lines
like this.

<%
request.getSession().setAttribute("reason",request.getParameter("reason"));
System.out.println("user is "+request.getParameter("j_username"));
System.out.println("reason is "+request.getParameter("reason"));
System.out.println("j check url is "+request.getParameter("checkUrl"));
//request.getRequestDispatcher(response.encodeURL("j_security_check")).forward(request,response);
response.sendRedirect(response.encodeURL("j_security_check?
j_username="+request.getParameter("j_username")+"&j_password="+request.getParameter
("j_password")));%>

2. modify the logon page to use this preLogin.jsp instead of the standard
j_security_check.

<form action="'preLogin.jsp'" method="post">
<table cellspacing="5" border="0"><tbody>
<tr><th align="right">1Username:</th><td align="left">
<input name="j_username"></td></tr>

<tr><th align="right">Password:</th><td
align="left"><input type="password" value=""
name="j_password"></td></tr>

<tr><th align="right">Reason:</th><td align="left">
<input name="reason"></td></tr>

<tr><td align="right"><input type="submit" value="Log
In"></td><td align="left"><input type="reset"
value="Reset"></td></tr></tbody></table>
</form>

3. Make sure the preLogin.jsp is not protected in the application.

<security-constraint><display-name>Example Security
Constraint</DISPLAY-NAME><web-resource-collection>
<web-resource-name>unProtected Area</WEB-RESOURCE-NAME>
<url-pattern>/security/protected/preLogin.jsp</URL-PATTERN>
<http-method>DELETE</HTTP-METHOD><http-method>GET
</HTTP- METHOD><http-method>POST</HTTP-METHOD>
<http-method>PUT</HTTP-METHOD></WEB-RESOURCE-
COLLECTION></SECURITY-CONSTRAINT>

4. Restart the web container
5. Test it by accessing an protected resource

Note: using AJAX make it more elegant to set extra information in session before going to j_security_check.

Friday, July 14, 2006

Java RMI Service on Multiple Home (IP address) Computer

Synopsis

When a Java RMI service is started on a server that has multiple IP addresses belonging to different network enabled, the service is accessible from only one of the network.

Reason

The reason why it can work on only one of the network is because when the remote object is exported, the default endpoint is accociated to one of the IP addresse. Later when the stub
tries to contact the remote object, it uses the IP defined in the endpoint to make the connection.
If the invoking is initiated from another network that does not has any route to the IP specified in the endpoint, the network can not be established and the invoking will certainly
result in failure.

Solution

The way to solve this problem is to write customized client socket factory to avoid using the default endpoint.

The thoery behind this is because as a TCP/IP server, the RMI service itself is available through all of the IPs by default, and actually the remote stub is retrievable through
every one of the IP. It does not work on some of the IPs is just becuase it will fail at the phase to
invoke the remote method provided by the remote object. it will try to contact a remote object
resides in another network.

In one of my solution, I put all the supported server IP addresses into the client socket factory and "ping" them until one is available before openning a socket from client to the proper server that is represented by the proper IP address.

The ideal way is to use the IP of the server directly. But I'd like to leave somebody else to enhance it.

The Sample Code


public class MSJRMIClientSocketFactory implements RMIClientSocketFactory,
Serializable {
protected ArrayList hostIPs = new ArrayList();
private static final long serialVersionUID = 57282513633491000L;
public MSJRMIClientSocketFactory() throws Exception {
Enumeration nics = null;
try {
nics = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
throw new Exception("There's no network interface available.");
}
while (nics != null && nics.hasMoreElements()) {
java.net.NetworkInterface ni = (java.net.NetworkInterface) (nics
.nextElement());
Enumeration eoip = ni.getInetAddresses();
while (eoip.hasMoreElements()) {
InetAddress ip = (InetAddress) eoip.nextElement();
if (!"127.0.0.1".equals(ip.getHostAddress()))
hostIPs.add(ip);
}
}
}
// isReachable is not the expected method
// the port is used to connect to the remote service that is on the remote
// computer
protected String getFirstLiveIP(int port) {
String theLiveIP = null;
Socket testSocket = null;
for (int i = 0; i < ip =" (InetAddress)" testsocket =" new" theliveip =" ip.getHostAddress();" reachablehost =" null;" reachablehost =" getFirstLiveIP(port);">


Use it in the code

If your remote object is already a type of UnicastRemoteObject, you simply use the constructor of
super(port,ClientSocketfactory,ServerSocketfactory)
to initiate it.
Since the socket returned is still the normal socket, so the server socket factory is not
need to be customized. Simple give it a null instead.

If you are using the "exportObject" method to get a stub, similiar to above, initiate the client
socket factory and use it as one of the parameter.

Thursday, July 13, 2006

Start Blogging?

I'm struggling......

Am I ready to become a blogger? I am not quite sure. I am still kind of in the mood of creating my own personal web space and making every web page by myself with a web page designer.

But, that might be too old fashioned already. I must give it a try to catch up the trend. Articles in a blogging web site are easier to be found anyway.