Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java & NT Authentication


Feb01: Java Q&A

Elisabeth is a graduate student in computer science at the University of Virginia. She can be contacted at [email protected].


Many Internet sites require usernames and passwords for authentication. The site must store a username/ password pair for each user of the site, and users must remember one for each secure site they visit. There's an easier solution with intranet sites, however. Since many organizations run on a Windows NT domain, users must remember an NT username/ password for their workstations. Because only internal users can see intranet sites, there is a ready-made database for authentication on the network.

This is straightforward in languages such as C++ or Visual Basic, which are platform dependent with built-in interfaces to Windows API functions. With Java, however, it is more difficult. Because of Java's platform independence, it makes no sense to include interfaces to Windows functions. Microsoft does this with its version of Java, but to take advantage of it, the code must run on Microsoft's VM. Sun has created the Java Authentication and Authorization Service (JAAS), a standard extension to Java 1.3. Unfortunately, so far the NT module for the JAAS gets the NT permissions of users who are logged on to the machine that calls it, rather than authenticating specific users.

Thus, the only flexible way to authenticate NT users with Java (without getting down to sockets) is to write a wrapper for the Windows method that performs the authentication, and call it from the Java program. In this article, I'll show how to write just such a wrapper and call it using Java Native Interface (JNI). I'll also call that wrapper from another (possibly nonNT) machine using Remote Method Invocation (RMI) and create services, so the object and the RMI registry will run automatically; see Figure 1.

These techniques may also be of interest to anyone trying to use a Windows Foundation Class (WFC) function, in that I'll describe how to write the code to interface between Java and C++, which can be modified easily to make any of those functions available to Java programmers.

The LogonUserA Method

To log on to an NT domain, Windows NT workstations use the LogonUserA method found in Advapi32.dll. I use this method to authenticate the intranet site in the same way NT performs network authentication. The method declaration looks like this:

BOOL LogonUserA(LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR lpszPassword, DWORD dwLogonType, DWORD dwLogonProvider, PHANDLE phToken);

The function returns True if the authentication were successful; otherwise, False. All of the type definitions for the LogonUserA declaration can be found in windows.h. Listing One shows the arguments to the function. A note about permissions: LogonUserA requires a special NT permission, SE_TCB_NAME. This is an operating-system privilege, so you must give OS privileges to any process that calls the method. There are two ways to do this: Make your class into a service, or run it under a user who has operating-system privileges. To give a user OS privileges, first make him a local administrator, then open musrmgr, go to Policies|User Rights, check Show Advanced User Rights, choose Act As Part Of The Operating System from the pull-down menu, and add the user under whom the process will run. This method obviously raises some security concerns and should be used with care.

Calling LogonUserA with JNI

All native code presented here is written in C++. However, you can also implement the techniques in other languages, including C and Visual Basic.

1. Write a Java class that calls the native method. You can give the method any name you want in your class because you will call the Windows function from a C or C++ wrapper, not from Java directly. Declare the method in the class, but that's all — do not define it, just put a semicolon at the end of the declaration. The native keyword will tell the compiler that no definition is needed. My method declaration looked like this:

private static native boolean logonUserA(String userName, String userPass);

2. Compile your .java file, then compile the C++ header file from your .class file. This gives you the declaration for the method that you must implement in the wrapper, telling the C++ code to make it available for export to JNI. This declaration is slightly different from the normal declaration to export a function in a DLL using only C++, which is why you must write a C++ wrapper class. To create the header file, use the javah utility, which can be found in /jdk1.x/bin along with java.exe. Because JDK 1.0 used a different API for native-method interfacing, you must also specify -jni, like this: javah -jni JavaNTLogon. This gives you the file javantlogon.h. If your class is in a package, specify the package when you run javah, and it appears in the name of the header file my_package_javantlogon.h. So, it's easiest if you put your class in the right package to begin with. (If you do change packages, run javah again.) Also, be careful how long the package, class, and method names are — if they are too long, they will be truncated when they are exported, and you will get an error.

Notice that in the method declaration in the header file there are two parameters that you didn't declare. These are present in any function called through JNI, and they are automatically passed, so you don't have to worry about them. The first is an environment interface pointer, which gives access to certain methods for communicating between the two languages (changing types, throwing exceptions, and so on). The second is a jclass pointer, which is the C++ equivalent of the this pointer for the calling Java object.

3. Write the C++ wrapper for the DLL. This class should include the header file output by javah, jni.h, and windows.h. It should also implement the method declared in the header file output by javah. This is where you call the Windows method you're trying to access. Listing Two is the code for this method.

First, use a typedef to tell the compiler what the declaration of the WFC method looks like:

typedef BOOL (CALLBACK* LPFNDLLFUNC1) (LPTSTR, LPTSTR, LPTSTR, DWORD, DWORD, PHANDLE);

Then call LoadLibrary on the DLL the function is in HINSTANCE hDLL = LoadLibrary("Advapi32.dll");. Check to see if hDLL is nonNULL (whether the library was loaded). If it is, get the export address of the function in the DLL: LPFNDLLFUNC1 f1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "LogonUserA");.

Again, ensure this is nonNULL, then use the pointer to the procedure address just like a function, and call LogonUserA as if it were a function in your code:

ReturnVal = f1(lpszUsername, lpszDomain, lpszPassword, dwLogonType, dwLogonProvider, phToken);

  • Parameters. Several types are represented differently in Java and C++. Because you passed the username and password each as a string, I need to convert the Java strings passed in (as type jstring, defined in jni.h) into the character array representation used in C++. jni.h defines a function to do this, GetStringUTFChars. This is a method called through the environment interface pointer (previously mentioned).

    const char * Username = env->GetStringUTFChars(userName, 0);
    const char * Userpass = env->GetStringUTFChars(userPass, 0);

    If the username and password you sent involve Unicode characters, use GetStringChars() and ReleaseStringChars().

    LogonUserA doesn't actually change the arrays, but because the declaration does not declare the strings constant, the compiler gives an error unless I copy the constant array into a nonconstant array.

    //assumes lpszUsername and lpszPassword have sufficient space allocated
    strcpy(lpszUsername, Username);
    strcpy(lpszPassword, Userpass);

  • Exceptions. If your code does not complete successfully, it can attempt to throw the proper exception back to the calling Java program. First, the wrapper must find the Java exception class to throw, then it must throw it:

    jclass exceptionClass = env->FindClass("java/lang/Exception");
    if (exceptionClass == 0) return 0;
      env->ThrowNew(exceptionClass, "couldn't find C function");
      //the second argument will be the
      //exception's message string

    Remember that any exceptions the method throws must be stated in the native method declaration in your Java class.

  • Cleanup. Don't forget that C++ doesn't do garbage collection. You need to deallocate space created through malloc and new, and also LoadLibrary and GetStringUTFChars. Otherwise, you may cause a memory leak. Also, check to ensure that if you throw an exception or return before the end of the program, you do your cleanup first.

    FreeLibrary(hDLL);
    free(lpszUsername);
    free(lpszPassword);
    free(phToken);
    env->ReleaseStringUTFChars(userName, Username);
    env->ReleaseStringUTFChars(userPass, Userpass);

  • UnsatisfiedLinkErrors. When you run the Java program that calls the native code, UnsatisfiedLinkErrors signals any problems the JVM has in finding your DLL or the specific procedure. If the error says that your DLL was not found, make sure it is in your system path, along with cw3230mt.dll, which is also required. If it says that the procedure could not be found, make sure the procedure name in the native code matches your package name in both the method declaration in the header and in the definition (particularly if you have changed the package you put your code in since you used javah.exe). You may want to run javah.exe again, and change your #include statement and the method name accordingly. Also, check to be sure the native method's name is less than 50 characters long; otherwise, it may be truncated on export.

RMI, LogonUserA, and Other Machines

RMI is a way to use your machine to call a method on a remote machine (what a surprise). It has two sides — client and server. Your Java class will register an instance of itself with the server, and the client will ask the server to reference that class's methods; see Figure 1.

1. Write an interface to declare remotely accessible methods. This interface should extend java.rmi.Remote, and must declare any methods you want your calling class to be able to see. In this case, the only method needed is logonUser(String, String). My interface is called LogonUser, so when I get an object from the RMI registry it is of type LogonUser.

2. Implement the remotely accessible methods in your Java class. That is, make logonUser() call the native method logonUserA() — the one you created earlier.

3. Create the stub and skeleton for your Java class. First run javac on the .java source, then run rmic.exe on the .class file. (rmic.exe is found in the bin folder of the JDK, along with java.exe, javac.exe, and the like.) Copy the stub to the client program's machine and set the machine's classpath so that any applications that call your class can access the stub.

4. Ensure that the RMI registry is running. If your object is the only one bound to the registry, you can create the registry in that code via java.rmi.registry.LocateRegistry.createRegistry(). Otherwise, you will probably want to create the registry via rmiregistry.exe, which is found in the same place as rmic.exe. The registry is only active as long as rmiregistry.exe is running.

5. Register an instance of the interface with the RMI registry. I put the following code into a main() method of my class, so that I would be able to run my class as a service. You do not have to put it in the same class, but you must have some way to register your class.

First, create an object of the type of your interface. You do this by calling a constructor for your class LogonUser user = new JavaNTLogon();.

Then you will bind this instance to the RMI registry via java.rmi.Naming.bind() or java.rmi.Naming.rebind():

java.rmi.Naming.bind("http://localhost:1099/LogonUser", user);

Registering your object with the RMI registry gives the registry a handle to the instance of the object you created. Thus, it is valid only while the process that bound it is running.

6. Write the client application for the remote object. First, copy the stub for the remote object to the client machine, and make sure it is in the classpath. Then, create a Java method that will call your object. Listing Three presents this method.

Services, the RMI Registry, and Your Class

Srvany.exe is a utility included in the Windows NT Resource Kit. It can be registered with NT as a service and used to invoke another executable to create a service out of that executable. This way, you avoid doing the extra work of writing your own service.

Srvany works by calling any specified executable, then reporting that it is running as a service. Unfortunately, it has no way of knowing whether the executable was started successfully. Also, if the service is stopped, the executable's process is simply terminated. There is no way to cleanup gracefully. If this is a problem, you can create your own service to keep the registry object bound. There are several tutorials for writing services available on the Web, and Microsoft's Windows API documentation is helpful in writing them. To use Srvany, you first register SRVANY.EXE as a service. This is done by using the instsrv utility, also in the NT Resource Kit. The general syntax is instsrv [servicename] [path]\SRVANY.EXE, where servicename is whatever you choose to name your service, and path is the path where SRVANY .EXE is located. You need to do this for each of the services.

>instsrv rmiregistry c:\NTRESKIT\SRVANY.EXE
>instsrv LogonUser c:\NTRESKIT\SRVANY.EXE

Next, open the system registry using regedt32 .exe. Go to HKEY_LOCAL_MACHINE| SYSTEM|CurrentControlSet|Services. Here you will find a list of all the services currently installed on your machine. Go to the first service you just installed, rmiregistry, and create a Parameters key. In this key, create an Application value with type REG_SZ where the string for the value is [path]\rmiregistry.exe. Do the same for LogonUser, using [path]\java.exe. Then, also in the LogonUser Parameters key, add an AppParameters value of type REG_SZ where the string is the rest of what you would type on the command line; that is, classpath and qualified class name.

Next, you may want to set LogonUser to depend on rmiregistry so that when LogonUser is run, there will be a registry existing with which it can instantiate itself. To do this, use another NT Resource Kit utility, SC.EXE. At a command prompt, simply type >sc config LogonUser depend= rmiregistry

Finally, configure your new services in the Control Panel|Services applet the same way you would configure any other service. If you want it to run whenever the machine is restarted, set Startup to automatic. You will probably want to leave it under the system account; otherwise, ensure that the account you choose has OS privileges.

References

Further information is available on the Web. The NT LogonUserA method documentation is at http://msdn.microsoft.com/library/psdk/winbase/accclsrv_9cfm.htm. Sun has tutorials on JNI and RMI. For JNI, see http://java.sun.com/docs/books/tutorial/native1.1/index.html. For RMI, go to http://java.sun.com/docs/books/tutorial/rmi/index.html.

DDJ

Listing One

LPTSTR lpszUsername // string that specifies the user name
LPTSTR lpszDomain   // string that specifies the domain or server
LPTSTR lpszPassword // string that specifies the password
DWORD dwLogonType   // specifies type of logon operation
    Values include:
    LOGON32_LOGON_INTERACTIVE   //returns impersonation token with user's 
             // credentials. the token can then be used to run other processes
             //as that user.
    LOGON32_LOGON_NETWORK   //fastest, but does not return credentials       
DWORD dwLogonProvider   // specifies the logon provider
   Values include:
    LOGON32_PROVIDER_DEFAULT
    LOGON32_PROVIDER_WINNT40    
PHANDLE phToken         //pointer variable to receive impersonation token 
handle. Remember to allocate space for this--LogonUserA does not it for you.

Back to Article

Listing Two

// You will want to check to ensure you have deallocated memory through 
// every error path. I have left some of this out for readability.

#include "logon_JavaNTLogon.h"  // Header file output by the RMI registry
#include <windows.h>
#include <iostream.h>
#include <string.h>

//tells the compiler the function signature to expect.
typedef BOOL (CALLBACK* LPFNDLLFUNC1)(LPTSTR, LPTSTR, LPTSTR, DWORD, 
                                                         DWORD, PHANDLE);
JNIEXPORT jboolean JNICALL Java_logon_JavaNTLogon_logonUserA
    (JNIEnv * env, jclass obj, jstring userName, jstring userPass)
{
  //declare all these variables we need
  jboolean successful=0;

  HINSTANCE hDLL;         // Handle to DLL
  LPFNDLLFUNC1 f1;        // Function pointer
  BOOL ReturnVal;
  LPTSTR lpszUsername = new char[12];  
  LPTSTR lpszDomain = "BFUSA";         
  LPTSTR lpszPassword = new char[30];  
  DWORD dwLogonType = LOGON32_LOGON_INTERACTIVE; 
  DWORD dwLogonProvider = LOGON32_PROVIDER_DEFAULT;      
  PHANDLE phToken = (PHANDLE)malloc(sizeof(HANDLE)); 
                                                                 
  //convert the java strings to UTF-8 strings so 
  // the dll can deal with them
  const char * Username = env->GetStringUTFChars(userName, 0);
  const char * Userpass = env->GetStringUTFChars(userPass, 0);

  //copy them into non-constant arrays for the function call
  strcpy(lpszUsername, Username);
  strcpy(lpszPassword, Userpass);

hDLL = LoadLibrary("Advapi32.dll");
  if (hDLL != NULL)
  {
   f1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "LogonUserA");
   if (!f1)
   {
        // handle the error
    jclass newExcCls = env->FindClass("java/lang/Exception");
    if (newExcCls == 0) { 
          return 0;
       }
        env->ThrowNew(newExcCls, 
             "couldn't find the Windows LogonUserA function");
   }
   else
   {  // try to call the function
      ReturnVal = f1(lpszUsername, lpszDomain, lpszPassword, dwLogonType, 
         dwLogonProvider, phToken);
      successful = (jboolean)ReturnVal;
   }
  }
  else
  {
    jclass newExcCls = env->FindClass("java/lang/Exception");
       if (newExcCls == 0)
       { 
          return 0;
       }
       env->ThrowNew(newExcCls, "couldn't load Advapi32.dll in native code");
  }
  FreeLibrary(hDLL);
  free(lpszUsername);
  free(lpszPassword);
  free(phToken);
  env->ReleaseStringUTFChars(userName, Username);
  env->ReleaseStringUTFChars(userPass, Userpass);
  return successful;
}

Back to Article

Listing Three

public static boolean logonUser(String userName, String password) 
throws RemoteException
{
    boolean isValid = false;
    // There must be a security manager for certain classes to be
    // served through RMI.  If you are getting security exceptions, you
    // may have to define your own security manager.
    if (System.getSecurityManager() == null) {
        System.setSecurityManager(new java.rmi.RMISecurityManager());
    }
    try {
        // Specify the name of the server the remote logon object is on,
        // the port that the remote RMI registry is listening on, and the
        // name of the remote object we want to access.  Format is just
        // like a URL.  1099 is the default port for RMI.
        String remoteServer = "//myServer:1099/LogonUser";

        // Get a handle on the remote logon object by doing an RMI lookup.
        LogonUser user = (LogonUser) java.rmi.Naming.lookup(remoteServer);

        // Call the NT authentication method on the remote object. True means
        // the username and password was successfully authenticated, False
        // means it wasn't.
        isValid = user.validateUser(userName, password);
       
    } catch (Exception e) {
        e.printStackTrace();
    }
    return isValid;
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.