This blog is mainly about Java...

Friday, February 13, 2009

Hash user password in Seam 2.1.1 manually without using IdentityManager

In the Seam Documentation, they give a minimal schema example of how you can create Users and Roles here: http://docs.jboss.com/seam/2.1.1.GA/reference/en-US/html_single/#d0e9178

As it is explained in the documentation, you can annotate your password with

@UserPassword(hash = "md5")
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
You could also use
hash= "none"
which doesnt hash the password which is what I did at first. The reason why I didnt start using md5 at once is because you needed a default username and password to be able to log into your seam application. And since I was using an import.sql script that created a default user for me, I had no way of knowing how to hash the password.

So what I needed to do is manually somehow hash a password which I could put in my import.sql script, so that I could enable hashing and correctly log in to my seam application. However this was easier said then done.
I tried changing the @UserPassword(hash="md5") and try to log in, but for obvious reason the login failed, because it was expecting a hashed password.

So then I got the idea to call a method that doesn't require login, which creates a user for us, that is called from a button in the login page. However when I persisted the user, the password was saved as clear text. This baffled me, because I thought that seam would handle that automatically. But if you actually look at the user entity and the setHashPassword method, it is not annotated, and it only sets a string. So it was obvious it only persisted the password as string.
In Seam you can also use IdentityManager to create a user for you. This will perform the hashing for you. So I tried to call
identityManager.createUser("username","password","firstname","lastname");

However, identityManager requires that you are correctly Authenticated, and it threw an exception
org.jboss.seam.security.AuthorizationException: Authorization check failed for permission seam.user,create

So apparently IdentityManager requires the correct permissions, which must be set in your drools setting in security.drl, but I didn't want to do that, since that is not how I wanted to configure my seam application.

So what I did, was look in the seam source and IdentityManager class and find the exact code that performs the hashing for us, and do that manually. And this worked great. Here is the source.

public String saveProcessUser() {
// Check if a new password has been entered
if (currentUser.getPasswordHash() != null && !"".equals(currentUser.getPasswordHash())) {
if (!currentUser.getPasswordHash().equals(passwordVerify)) {
StatusMessages.instance().addFromResourceBundle("admin.wrongPassword");
return "failure";
}
}

// If passwordVerify is not empty, it generally means its a new password
if (!passwordVerify.equals("")) {
String hashPass = generatePasswordHash(currentUser.getPasswordHash(), currentUser.getUsername());
log.debug("Setting new hash password: " + hashPass);
currentUser.setPasswordHash(hashPass);
}

if (getCurrentUser().getId() != null) {
entityManager.flush();
} else {
entityManager.persist(currentUser);
}

FacesMessages.instance().addToControlFromResourceBundle("successMessage", "admin.user.saved");
return "success";
}

/**
* This method will generate a hash password
*
* @param password - The password in cleartext
* @param salt - The username is used as salt
* @return - hash password based on password and username
*/
private String generatePasswordHash(String password, String salt) {
AnnotatedBeanProperty<UserPassword> userPasswordProperty = new AnnotatedBeanProperty<UserPassword>(ProcessUser.class, UserPassword.class);
// Will get the hash value from annotation UserPassword in ProcessUser.class
String algorithm = userPasswordProperty.getAnnotation().hash();
return PasswordHash.instance().generateSaltedHash(password, salt, algorithm);
}


Note that the method saveProcessUser() is called from the user administration form, and the currentUser object is my User entity. Seam will inject all the methods for us, so we dont have to do that. So we check if the verifyPassword is set and correct and then we hash the password. If the @UserPassword is set to something other than none, then it will correctly save a user and you can finally copy the hashed password in your import.sql script.

PS: You first should have the @UserPassword(hash="none") and then go to your user registration and then just print out what the password is, and then copy that to your import.sql script. When the authentication correctly works with either md5 or sha, then you can safely add the user to the database.

Wednesday, February 4, 2009

How to create and use a WebService with Axis 2 and Seam 2.x in JBoss 4.x

In this example, I will show how you can create a Webservice using Axis 2.
First of all, download the latest version of Axix 2 from http://ws.apache.org/axis2/

To create a WebService in Java EE 5 you can use the annotation @WebService.
We also annotate this class as a seam component so that we can incorporate it in our existing business logic.

This is our WebService:

package somepackage.webservice;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.persistence.EntityManager;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;

@Name("fooService")
@Stateless
@WebService(name = "FooService", serviceName = "FooService")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class FooService implements FooServiceLocal {

@In EntityManager entityManager;

@In Credentials credentials;

private boolean login(String username, String password) {
credentials.setUsername(username);
credentials.setPassword(password);
Identity.instance().login();
return Identity.instance().isLoggedIn();
}

private boolean logout() {
Identity.instance().logout();
return !Identity.instance().isLoggedIn();
}

@WebMethod
public List<FooCanonical> getFoo(@WebParam(name = "username")
String username, @WebParam(name = "password")
String password, @WebParam(name = "orgnumber")
String orgnumber) {
// orgnumber can be null!
if (username == null || password == null) {
return null;
}
//First thing we do is to login to ensure that the user has the correct username/password
//We are using basic seam login method
boolean isLoggedIn = login(username, password);

if (isLoggedIn) {

List<FooCanonical> returnList = new ArrayList<FooCanonical>();
//Do some stuff with the list
//Remember to log out
logout();
return returnList;
} else {
// Probably wrong username password
return null;
}
}

}


Next what we need to do, is create a way for this webservice to interact with JBoss through our SOAP definition. We do that by creating a xml file called
standard-jaxws-endpoint-config.xml

<jaxws-config xmlns="urn:jboss:jaxws-config:2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="urn:jboss:jaxws-config:2.0 jaxws-config_2.1.xsd">
<endpoint-config>
<config-name>Seam WebService Endpoint</config-name>
<pre-handler-chains>
<javaee:handler-chain>
<javaee:protocol-bindings>##SOAP11_HTTP</javaee:protocol-bindings>
<javaee:handler>
<javaee:handler-name>SOAP Request Handler</javaee:handler-name>
<javaee:handler-class>org.jboss.seam.webservice.SOAPRequestHandler</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</pre-handler-chains>
</endpoint-config>
</jaxws-config>

And place this file in the $JBOSS_HOME/resources/META-INF directory.
Now you are done! Deploy your application and look in
http://localhost:8080/jbossws/services 
and see if your WebService is correctly deployed and the wsdl available.
This should look something like this:

Endpoint Namejboss.ws:context=foo-foo,endpoint=FooService
Endpoint Addresshttp://localhost:8080/foo-foo/FooService?wsdl

Next, we will use the Axis2 framework to create client stubs by using axis2-1.4.1 and the script wsdl2java. Navigate to $AXIS_HOME/bin and type in the following command:
./wsdl2java.sh -uri http://127.0.0.1:8080/foo_foo/FooService?wsdl -o build/client

This command will create an ant script under the directly build/client.
Now go to build/client and type ant after setting $AXIS_HOME. This will generate FooService-test-client.jar which we now can use to retrieve data from the WebService in the client. I recommend changing the name to something more appropriate.

In your client, you can call the getFoo WebMethod like this:

FooServiceStub stub;
GetFoo getFoo;

stub = new FooServiceStub();
getFoo = new FooServiceStub.GetFoo();
getFoo.setUsername("username");
getFoo.setPassword("password");
getFoo.setOrgnumber("1234");

FooServiceStub.GetFooE fooImpl = new FooServiceStub.GetFooE();
fooImpl.setGetFoo(getFoo);

//Retrieve the List as an array
FooCanonical[] get_return = stub.getFoo(fooImpl).getGetFooResponse().get_return();
//Do what you want with the array

Note that even if you return a List from the WebService, you will get it as an array. But it is quite easy to put it in a List in the client afterwards. Also remember that the username and password is sendt in clear text, so you might want to send it through https, so it is encrypted.

Labels