Welcome to HashDot.com
Search  


Contact Us

Earn Money
Earn money online, For lifetime Hashdot membership and for Advertisement details..
Click Here

Login




 


 Log in Problems?
 New User? Sign Up!

  

High Speed Authentication Tips w/Source Code

(4401 total words in this text)
(1028 Reads)  Printer-friendly page
<div align="justify">

Many sites charge for access to their content, or want to limit viewing of information to select groups. Other sites just want users to register before allowing access to their sites in order to build a knowledge base about users. Customized site behavior can be provided for the site user, which again requires authentication to access user specific preferences. No matter why you want to authenticate, the need for authentication places a burden on your web site's resources, and proper programming can significantly reduce the load, increasing site performance.

Authentication implies the presence of a database to provide persistent data to be used as a part of the verification process. Database access must be kept to a minimum so that the request/response process remains fast and uninhibited by database overhead.

Sample source code is provided at the end of this article showing one approach to authentication. It is provided as a starting point and illustration of how you might approach building authentication for your web site. Included in the source code is a method that is just a shell for the database access ( dbCheckValidUser( String user, String pwd ) ) you will have to make to validate the user. Regardless of which database tool you choose to use for this purpose the programming issues remain the same.

ZCSentry, in addition to providing load balancing, and has a plug-in feature that allows you to intercept a request prior to execution of the request. This feature makes intercepting a new user to your web site much easier, and cleaner than most solutions.

The traditional approach is to make the home page dynamic, usually with a cgi style process. This can be a pain, because the content returned is authentication dependent, and the page returned needs to reflect the current session state of the user. Every single page, and sometimes every page element, or specific content type, needs to be authenticated. This can mean an excessive cgi overhead on every request, resulting in poor site performance. A big advantage to using ZCSentry is the ability to define what content you want to authenticate, reducing programming effort. ZCSentry also provides web caching of static content.

The plug-in is a Java class you can write to customize site behavior. The basic outline of the class looks like this.

Now that you can see the basic layout of the plug-in, you can see that the request arrives at your custom class with a great deal of information you can use to write your request handler.

private String[] requestArray = null; private String ipAddr = null; private Hashtable globalHash = null; private Vector outHeaderAdd = null; private Vector customLog = null; private String newGroupName = null; private byte[] postData = null; private byte[] retData = null; private String hostName = null;

As you would expect, there is more information available than you might need for our discussion, but it's useful to see the resources available to the plug-in. We have the complete request in string array form in requestArray, and requestArray[0] contains a string that forms the file request.

These objects are bi-directional. If you modify requestArray[0] to point to a different file, the request will then be modified and the new file will be requested. If you load retData with a byte array of data you would like to return to the caller, that data will be returned without accessing any other server as if a file had been accessed. This is why ZCSentry is such a great tool for managing complex site issues. You have direct access at the very point of entry into your web site environment.

Cookie value pairs are in different array positions within requestArray.

A reference to a Vector is passed in from ZCSentry which we have named outHeaderAdd. Any string you want to add to the reply header for this request can be added to the Vector. This is really nice for adding/setting cookie information, and you can see how this is done in the code sample below.

Since ZCSentry is multi-threaded, a separate instance of MyAuthenticator is loaded by each ZCSentry service thread. This means that each class can run without concern about threading issues since each class is operating independently of any other threads. If you want to share information with other threads this is accomplished by defining a class and adding the class object to the globalHash Hashtable. You can define all the classes you want and place them in this global container. You use naming conventions to define the key for the class you need, get the class you want and cast it for local for use.

Now that we've laid the groundwork for our authentication system, let's build a usable authenticator.

Authentication And Speed Tips

Authentication can be a post with username and password, it can be verification of a cookie, validation of an IP address, or checking a request for the presence of a key. There are many different schemes or combinations of schemes in use on the web. For the sake of simplicity we'll use a cookie which is the most popular method of authentication in use on the web. Not using cookies involves high load processing like dynamically rewriting content so that links include a session id for use in authentication. It can and is done, and ZCSentry can be used to do this, but I don't advise using this approach unless you must.

The key thing to keep in mind is minimizing data access. If you can keep data in memory for a while, repeated accesses can be kept fast and clean. In order to do this we need to store and periodically clean-up no longer needed data from memory. Java's Hashtable allows a convenient and fast way to store and retrieve authentication information.

The best time to check for expired sessions is during the authentication process. To keep performance high, we keep a time object in the globalHash called "NEXTCHECK". We check that time, at each authentication, to see if expired sessions need to be removed. If sufficient time has passed to justify a recheck, we then examine current sessions and remove those that have expired.

Code optimization rules can be applied that will increase performance. These are general guidelines, and are probably well understood by the majority of developers, but I will list a few here that might be helpful:

In the case of ZCSentry, every thread has it's own instance of the class, so do not use the synchronized keyword.
If you are defining a class that will reside in the globalHash and be referenced by all of the threads then you must use the synchronized keyword in order to make your class thread-safe.
Heavy-weight object creations (i.e. SimpleDateFormat) should only occur once if possible, during your authentication class's initialization process.
Use StringBuffer.append() and not String concatenation. This results in smaller byte code.
Use object methods that empty an existing object rather than recreating the object.
String arrays are an exception
Don't create an object within a looping structure if you can help it.
As database table sizes increase, performance decreases. If you have a large site, or performance is critical, consider using ZCache to speed database access.

Sample Source Code

This code is provided as an illustration of the ideas we are discussing here. It can be used in whole or in part by anyone. Further optimization is possible, since it is provided just for reference and illustration of the ideas presented here. I have purposely tried to be verbose for ease of understanding.

import java.lang.*;import java.io.*;import java.net.*;import java.awt.*;import java.util.*;import java.text.*;public class MyAuthenticator{ private Object[] o = null; private String[] requestArray = null; // the request and all the header information, including browser information private String ipAddr = null; // the apparent ip address of the requestor private Hashtable globalHash = null; // A global place to store object information private Vector outHeaderAdd = null; // Where you can put strings to add to the header of the reply, like cookies private Vector customLog = null; // Where you can store custom logging information in "mylog.log|"+message form private String newGroupName = null; // By filling in a group name you can force the request to be handled by a specific server group private byte[] postData = null; // Extra data from a POST request private byte[] retData = null; // A byte array for dynamic responses you might want to send in answer to a request private String hostName = null; // A string that identifies the target domain /// private long incrNum = 0; // variable used by this example code private String inUserID = null; // variable used by this example code private String inUserPWD = null; // variable used by this example code /// private SimpleDateFormat df2 = null; public MyAuthenticator() { df2 = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss"); df2.setTimeZone( TimeZone.getTimeZone("GMT") ); } public void execute( Object[] o ) { this.o = o; requestArray = (String[])o[0]; ipAddr = (String)o[1]; globalHash = (Hashtable)o[2]; outHeaderAdd = (Vector)o[3]; customLog = (Vector)o[4]; newGroupName = (String)o[5]; postData = (byte[])o[6]; retData = (byte[])o[7]; hostName = (String)o[8]; // place your code here... { String s = getSessionIdFromRequest(); sessionValid(s); // if session is invalid, refers to login page } } private String getSessionIdFromRequest() { String s = null; int beg = 0; int end = 0; for(int i=1; i < requestArray.length && requestArray[i] != null; i++) // first position (0) is never the Cookie: { // Now get the session ID. if( requestArray[i].startsWith("Cookie:") ) { beg = requestArray[i].indexOf("MY_SESSION_COOKIE_NAME="); if( beg == -1 ) return null; // cookie entity (name) did not exist, don't look at any more just return. beg = beg+7; end = requestArray[i].indexOf(";", beg); if(end == -1) end = requestArray[i].length(); s = new String(requestArray[i].substring(beg, end)); return s; // returning the cookie value, which is the session id, don't look at any more just return. } } return null; } private boolean sessionValid(String sessionId) { Long tmpLong = null; long tim = System.currentTimeMillis(); long sessionExpiration = 0; if( sessionId != null ) tmpLong = (Long)globalHash.get(sessionId); // check to see if this request is an attempt to authenticate if( isAuthCheckIn() ) { // if so, validate the request. name and password have been collected. // if okay, return true, else false if( dbCheckValidUser( inUserID, inUserPWD ) ) { createNewSession(); return true; } else return false; } else { if( tmpLong == null ) { setRequest("login.html"); return false; } else { sessionExpiration = tmpLong.longValue(); if( sessionExpiration > tim ) { setRequest("login.html"); return false; } else return true; } } } private boolean dbCheckValidUser( String user, String pwd ) { boolean validUser = true; /* This code is dependent upon your specific database and chosen methodology. I'll leave this code up to you, and it's easy to change. If speed is critical I suggest you check out ZCache as a part of your solution. Just look for the user in your database, compare the password, and if you want to you can also compare ipAddr to see if the users ip address is valid, or within a valid range of ip addresses. */ if( validUser ) return true; else return false; } private String createNewSession() { String s = null; String cookieExpTime = null; Date date3 = null long curTime = System.currentTimeMillis(); s = getNewSessionId(); date3 = new Date( curTime ); globalHash.put(s, new Long(curTime)); cookieExpTime = df2.format(date3) + " GMT"; setCookie(s, cookieExpTime); return s; } private String getNewSessionId() { String s = new String( Thread.currentThread().getName()+ ":" + (++incrNum) ); return s; } boolean isAuthCheckIn() { String s = null; String tmpStr = null; int ind2 = 0; int ind3 = 0; int ind4 = 0; if(requestArray[0].startsWith("POST /AuthCheck.htm")) { deleteExpiredHashSessions(); // remove old sessions // This is the request; "POST /AuthCheck.htm HTTP/1.1" data is "username=sample&password=pwd" s = new String(postData); ind2 = s.indexOf("username") + 9; if( ind2 != -1 ) { ind3 = s.indexOf("&password"); if( ind3 != -1 ) { inUserID = new String(s.substring(ind2,ind3)); tmpStr = s.substring(ind3+10); //Get to where the password starts. inUserPWD = new String(s.substring(ind3+12)); return true; } } } return false; } private void setCookie(String cookieValue, String expires) { outHeaderAdd.addElement("Set-Cookie: MY_SESSION_COOKIE_NAME=" + cookieValue + "; path=/; expires=" + expires); } private void setRequest(String request) { requestArray[0] = new String("GET /" + request + " HTTP/1.1"); } private void deleteExpiredHashSessions() { long tim = System.currentTimeMillis(); Long tmpLong = null; long nextCheck = 0; tmpLong = (Long)globalHash.get("NEXTCHECK"); if( tmpLong != null ) { nextCheck = tmpLong.longValue(); if( nextCheck != 0 && tim < nextCheck ) // a 30 minute delay before checking return; } { // this code isn't executed unless it needs to be.. int comValue = 0; long storeTime = 0; String key = null; Enumeration elms = globalHash.keys(); while(elms.hasMoreElements()) { key = (String)elms.nextElement(); if( !key.equals("NEXTCHECK") ) { tmpLong = (Long)globalHash.get(key); // get the time the session started if( tmpLong != null ) { storeTime = tmpLong.longValue(); if( tim > storeTime+3600000 ) // if older than an hour, remove. globalHash.remove(key); } } } tmpLong = new Long(tim+180000); globalHash.put("NEXTCHECK", (tmpLong); // update the value } }}

Plug-in Program Structure

/* MyAuthenticator.java - Sample code*/import java.awt.*;import java.util.*;import java.text.*;import java.net.*;import java.io.*;public class MyAuthenticator{ private Object[] o = null; private String[] requestArray = null; private String ipAddr = null; private Hashtable globalHash = null; private Vector outHeaderAdd = null; private Vector customLog = null; private String newGroupName = null; private byte[] postData = null; private byte[] retData = null; private String hostName = null; private SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); public MyAuthenticator() { } public void execute( Object[] o ) { if( this.o == null ) { this.o = o; requestArray = (String[])o[0]; ipAddr = (String)o[1]; globalHash = (Hashtable)o[2]; outHeaderAdd = (Vector)o[3]; customLog = (Vector)o[4]; newGroupName = (String)o[5]; postData = (byte[])o[6]; retData = (byte[])o[7]; hostName = (String)o[8]; } // place your code here... { } }}

</div>

Useful Links :

http://www.ittoolbox.com http://www.javaworld.com/javaworld/jw-04-1997/jw-04-optimize.html http://www.protomatter.com/nate/java-optimization/ http://www.forums.hashdot.com


Web site powered by PostNuke ADODB database library PHP Language

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest (c) 2008 by me
This web site was made with PostNuke, a web portal system written in PHP. PostNuke is Free Software released under the GNU/GPL license.

You can syndicate our news using the file backend.php