Java and Security, Part 2
Pages: 1, 2, 3, 4, 5, 6, 7, 8
Login Module
We now need to supply a login module, as shown in Example 17-7. This follows the standard JAAS API.
Example 17-7. A login module
package com.oreilly.wlguide.security.provider;
import java.io.IOException;
import java.util.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.LoginModule;
import weblogic.security.principal.WLSGroupImpl;
import weblogic.security.principal.WLSUserImpl;
/**
* This login module will be called by our Authentication Provider.
* It assumes that the option, usermap, will be passed which contains
* the map of users to passwords and groups.
*/
public class MyLoginModuleImpl implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private HashMap userMap;
// Authentication status
private boolean loginSucceeded;
private boolean principalsInSubject;
private Vector principalsBeforeCommit = new Vector( );
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
// Fetch user/password map that should be set by the authenticator
userMap = (HashMap) options.get("usermap");
}
/* Called once after initialize to try and log the person in */
public boolean login( ) throws LoginException {
// First thing we do is create an array of callbacks so that
// we can get the data from the user
Callback[] callbacks;
callbacks = new Callback[2];
callbacks[0] = new NameCallback("username: ");
callbacks[1] = new PasswordCallback("password: ", false);
try {
callbackHandler.handle(callbacks);
} catch (IOException eio) {
throw new LoginException(eio.toString( ));
} catch (UnsupportedCallbackException eu) {
throw new LoginException(eu.toString( ));
}
String username = ((NameCallback) callbacks[0]).getName( );
char [] pw = ((PasswordCallback) callbacks[1]).getPassword( );
String password = new String(pw);
if (username.length( ) > 0) {
if (!userMap.containsKey(username))
throw new FailedLoginException("Authentication Failed: Could not find user:
" + username);
String realPassword = ((MyAuthenticationProviderImpl.MyUserDetails) userMap.
get(username)).getPassword( );
if (realPassword == null || !realPassword.equals(password))
throw new FailedLoginException("Authentication Failed: Password incorrect
for user" + username);
} else {
// No Username, so anonymous access is being attempted
}
loginSucceeded = true;
// We collect some principals that we would like to add to the user
// once this is committed.
// First, we add his username itself
principalsBeforeCommit.add(new WLSUserImpl(username));
// Now we add his group
principalsBeforeCommit.add(new WLSGroupImpl(((MyAuthenticationProviderImpl.
MyUserDetails)userMap.get(username)).getGroup( )));
return loginSucceeded;
}
public boolean commit( ) throws LoginException {
if (loginSucceeded) {
subject.getPrincipals().removeAll(principalsBeforeCommit);
principalsInSubject = true;
return true;
} else {
return false;
}
}
public boolean abort( ) throws LoginException {
if (principalsInSubject) {
subject.getPrincipals( ).removeAll(principalsBeforeCommit);
principalsInSubject = false;
}
return true;
}
public boolean logout( ) throws LoginException {
return true;
}
}
Although long, this is pretty straightforward. Let's go through it. The initialize( )
method has direct access to the options that were configured in the provider. As we
put our database in the options, this method provides the ideal place to extract the
database. The rest of the action occurs in a combination of the login( ), commit( ),
and abort( ) methods. If a login succeeds, and the control flags of the login modules
are such that the entire login is to commit, the commit( ) method will be called—
otherwise, the abort( ) method will be called. These methods simply ensure that the
principals that should be associated with the subject are placed into the subject. The
login( ) method does all of the work. First, it sets up a number of callbacks—we
need the username and password. Note that the actual callback implementation is
going to be handled by WebLogic. For example, WebLogic prompts you for the system
user credentials when you try and boot a WebLogic server or access the Administration
Console. It is the data from these callbacks that eventually will be supplied
to the login method. After handling the callbacks, we extract the username and password
of the user and locate it in our database. If successful, we make a list of principals
that we want to associate with the user, storing these in the variable
principalsBeforeCommit. The principals are added to the subject only if WebLogic
calls the commit( ) method.
Deploying the Provider
Once you have created the authentication provider and login module, you can package these together with the generated stub and MBI files. To do this, execute the following command:
java weblogic.management.commo.WebLogicMBeanMaker -MJF myAuth.jar -files .
You are now ready to deploy your new provider. Copy myAuth.jar to the WL_HOME/server/lib/mbeantypes directory, and then reboot the server. Note that all custom providers
have to be located in this directory. Start up the Administration Console and
navigate to the Security/myrealm/Providers/Authentication node. In the list of available
authenticators and identity asserters, you should find an option for "Configure a
new My Authenticator." Selecting this option and clicking Create will configure the
authenticator. On the following tab, you will notice that you can change the control
flag. If you change this to something such as requisite, make sure that your database
has a user in the Administrators group. If not, you won't even be able to boot the
server! Use the OPTIONAL flag during development to avoid these problems.
Creating an Identity Assertion Provider
Imagine that you have some external system—say, a Java client or perhaps even an external web server—that authenticates a user, and you now want this user to participate in actions involving WebLogic. Furthermore, you don't want WebLogic to reauthenticate the user. Rather, you want to use some token generated by the external system to be used as an automatic WebLogic login. This is fairly typical of many single sign-on scenarios. The key to implementing this is to use an Identity Assertion Provider. Let's look at how you can implement such a scenario.
We are going to take as an example an external Java client that has presumably performed
some user authentication, and who now needs to transfer this identity to
WebLogic in order to access a protected web application. First of all, let's configure
the web application to use identity assertion. Do this by setting the login-config to
use a CLIENT-CERT authorization method. As this is standard J2EE, you will need to
create a web.xml file with something such as the following in it:
<security-constraint>
<!-- web resource collection omitted -->
<auth-constraint>
<description>nyse</description>
<role-name>mysecrole</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>myrealm</realm-name>
</login-config>
<security-role>
<role-name>mysecrole</role-name>
</security-role>>
Now let's imagine we have a client (written in whatever language you wish) that has already performed some user authentication and now needs to access one of the protected web pages—say, http://10.0.10.10:8001/index.jsp. The following client is such an example:
URL url = new URL("http://10.0.10.10:8001/index.jsp");
URLConnection connection = url.openConnection( );
BufferedReader in = new BufferedReader(new InputStreamReader(
connection.getInputStream( )));
// Read the input stream
in.close( );