Friday, April 11, 2008

Interoperable WSS using JDBC realm in Glassfish

Web Service Security is a very useful mechanism, but can be a little tricky if you want to ensure it's interoperability. Also, in an enterprise application, user authentication should be done against a database. So in this blog I want to show that this is actually an easy thing, especially when using NetBeans and Glassfish tandem.

I used a development version of NB 6.1, the one from april 6, then RC1 as soon as it appeared. I was a little unhappy with the Beta release because it had a numerous of bugs. RC1 is much better and I can see a HUGE improvement on my home computer, an old Pentium M@1.6Mhz. The autocomplete is much faster, startup also. It starts faster then Eclipse:).
What I would like to see in the future is a better Seam plugin and a visualization of a normal JSP page, not a Visual Web JSF. If it would have this, it would be the perfect IDE for me. Also, in Eclipse, I like how I can deploy my application quickly and modifications done on a JSP do not require a redeploy of the application. Something like this should also be in NetBeans&Glassfish.
In terms of Desktop Applications and Web Services, NetBeans rules. Swing Application Framework is very good and Web Service support is amazing: restful, stateful, secure, interoperable ... everything. Nice ...


Ok, so let's begin. The first thing you should do is create a database. You need to have a user and role table, as seen in the image below(the fields marked in blue are necessary)

Now, we need to configure a JDBC realm in Glassfish. To do this, we start the server and select "View admin console". We login to the admin console. First, create a JDBC resource under Resources. Then, navigate to Configuration -> Security -> Realms -> new. Under classname, we select the JDBC realm. The configuration should look like this:



Under Digest Algorithm, you can optionally specify a hash algorithm. Only the hash of the password should be kept in the database; when an user authenticates itself, the system will make a hash of the password using this algorithm and compare it to the database. When inserting a new user, you can use the fallowing code to make a hash from a password:

private static final char[] HEXADECIMAL = { '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public static String hashPassword(String password) {
try {
MessageDigest md = MessageDigest.getInstance("SHA");
md.reset();

byte[] bytes = md.digest(password.getBytes());
StringBuilder sb = new StringBuilder(2 * bytes.length);
for (int i = 0; i < low =" (int)" high =" (int)">> 4);
sb.append(HEXADECIMAL[high]);
sb.append(HEXADECIMAL[low]);
}
return sb.toString();
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(UsersFacadeBean.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}

Now, we can create the application. In NetBeans, create a new Enterprise application. First, let's configure the authentification. Add a Glassfish Deployment Descriptor. This will create a sun-application.xml. Here, map the groups from the database to the roles in the application and the realm to use, like this:

<sun-application>
<security-role-mapping>
<role-name>superuser</role-name>
<group-name>superuser</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>user</role-name>
<group-name>user</group-namev
</security-role-mapping>
<security-role-mapping>
<role-name>admin</role-name>
<group-name>admin</group-name>
</security-role-mapping>

<realm>realm</realm>
</sun-application>
Next, create a web service in the EJB module, for example. You can also create one in the web project, but you can't use @RolesAllowed in a servlet web service, so for now let's stick to EJB web service.
After creating a web service, righ-click and select Web Service -> Add Operation. Configure the name and parameters. You might ask why use the 'Add Operation' instead of manually writing the method. The answer is that by doing this, the IDE also configures the message parts and headers to be signed/encrypted if using WSS.
Now add @RolesAllowed({"admin", "superuser"}) for the method. This will allow only users in the specified role to call this method. Now, add the action attribute at the @WebMethod with the same value as the operationName. This is to make sure that the .NET client will correctly generate the service proxy.

To configure security, go to Web Services from the project tree and rightclick Edit Web Service Attributes. Under Quality of Service, select "Secure Service" with "Development Defaults" and "Username authentification with symmetric key".

If at runtime you want to determine the user who called the method, do the fallowing:
1. inject a WebServiceContext with @Resource
2. Use the fallowing code:

javax.security.auth.Subject subject = (javax.security.auth.Subject)wscontext.getMessageContext().get("CLIENT_SUBJECT");
Object[] principals = subject.getPrincipals().toArray();
String username = ((Principal)principals[0]).getName();

return username;

And this is all, we have all set up. Now for the client.
A Java client is easy, just generate a web service client and configure security using NetBeans the same way you did on the EJB. If you want to configure the username and passwor dynamicly, you can do like this:

YourService service = new YourService();
Your port = service.getYourPort();

((BindingProvider)port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "insert_username");
((BindingProvider)port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "insert_password");


Now for a .NET client. A little bit tricky if not done properly.
First, you have to create a certificate from the keystore.key. You cand find instructions on how to create a certificate from keystore here. Install this certificate as a Personal certificate. You can use the 'certmgr.msc' tool to make sure you've done it correctly.

I have used Visual Studio 2008 and .NET framework 3.5, but it should work with .NET 3.0 also. Using Visual Studio, generate a proxy for the Java web service. There are some modifications that have to be done in app.config for the generated stub. You have to add a
<identity>
<dns value="xwssecurityserver">
</dns>
</identity>
in the endpoint tag . This will specify the truststore alias to be used.


This is the code you have to add to securely call the webservice:

YourServiceClient port = new YourServiceClient();
port.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser,
System.Security.Cryptography.X509Certificates.StoreName.My,
System.Security.Cryptography.X509Certificates.X509FindType.FindByIssuerName, "SUNCA");
port.ClientCredentials.UserName.UserName = "insert_username";
port.ClientCredentials.UserName.Password = "insert_password";
Now you can call any method of the web service.
And there you have it, all done! Secure interoperable web service.

NOTE: I have used the default keystore that comes with Glassfish. In a real application, you should create your own keystore.