HISPACTA Hell

Monday, April 24, 2006

Encrypting passwords in a web service environment

If your various GUIs are planning on connecting to an AuthenticationManager over a web service, the decision on where to encrypt passwords becomes non-trivial (I think).

If you've got a straight-forward application without web services, then just add a PasswordEncoder property to the daoAuthenticationProvider bean and you're done.

However, if your daoAuthenticationProvider (or whatever) is running on a remote box that your client accesses through a GUI, then you have the option of either (a) adding the PasswordEncoder to the web service's bean or (b) encrypting the password on the client side, sending it over to the web service, and have the web service's bean do a straight string-to-string comparison.

After mulling it over a little bit, I decided that I'd go with option (b) and apply the Acegi MD5 encryption at each of the client sides. It's a little more work, sure, but it means that the password isn't sent over the network in plaintext (even if the network is secured with SSL), and it means the password doesn't show up in the GET headers that are posted as part of the /j_acegi_security_check that the client depends on.

Thursday, April 20, 2006

Moving Acegi's inMemoryDaoImpl to a jdbcDaoImpl

This was surprisingly easy, with only one thing that caught me up.

To use the standard Acegi implementation of the jdbcDaoImpl, you need to have a schema that will respond to the following SQL queries:

"SELECT username,password,enabled FROM users WHERE username = ?"
"SELECT username,authority FROM authorities WHERE username = ?"

Because our schema doesn't have a "users" or "authorities" table, you need to overwrite the properties in the jdbcDaoImpl class that hold those 2 SQL statements.

But I get ahead of myself...

First of all, change the application-context.xml file that is responsible for providing the AuthenticationManager bean to use the jdbcDaoImpl instead of the inMemoryDaoImpl.

Before

[bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"]
[property name="userDetailsService"][ref local="inMemoryDaoImpl"/][/property]
[/bean]


After

[bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"]
[property name="userDetailsService"][ref local="jdbcDaoImpl"/][/property]
[/bean]


Now define the jdbcDaoImpl like the Acegi documentation spells out:

[bean id="jdbcDaoImpl"
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl"]
[property name="dataSource"][ref bean="EDISDataSource"/][/property]
[/bean]


Note that in that dataSource property I'm using the EDISDataSource which on my project is defined like:
[bean id="EDISDataSource" class="org.apache.commons.dbcp.BasicDataSource"....

The important thing is that the bean implement the DataSource interface.

Now, because my schema doesn't look like the schema Acegi wants, I had to override the two parameters within the JdbcDaoImpl.java class that hold the SQL queries that the implementation uses to fetch credentials by username and authorities by username.

To wit; add the following properties to the jdbcDaoImpl bean definition:

[property name="usersByUsernameQuery"]
[value]
select USER_NAME as username,
PASSWORD,
1 as ENABLED
from CBISOWNER.CBIS_USER
where USER_NAME=?
[/value]
[/property]
[property name="authoritiesByUsernameQuery"]
[value]
select USER_NAME as username,
ROLE_NAME as authority
from CBISOWNER.CBIS_USER,
CBISOWNER.CBIS_ROLE,
CBISOWNER.USER_ROLE
where USER_ROLE.USER_ID=CBIS_USER.USER_ID
and USER_ROLE.ROLE_ID=CBIS_ROLE.ROLE_ID
and CBIS_USER.USER_NAME = ?
[/value]
[/property]


Obviously, your implementation of this facade will depend on whatever your schema looks like, but you get the picture.

One thing to note... in some of the javadocs within the JdbcDaoImpl, there's a reference to adding 'true' as ENABLED. That does not work. It causes a "Fail to convert to internal representation" stack trace. So I found on the forum.springframework.org site an example where someone used "1 as ENABLED" and that works like a champ.

Tuesday, April 18, 2006

Moving the Acegi AuthenticationManager to a web service

Setting up Acegi to secure a local Tapestry application is tough enough...but it doesn't gain you too much. You need to provide a remote, centralized source of authentication for your web apps to be at all consistent.

Note that I haven't even looked at CAS yet as an option, so this is perhaps wasted effort. Nonetheless, here's what I did to pull the Acegi AuthenticationProvider code out of the GUI and in to a remote web service.

Step 1) Go to the application-context.xml of your web service and add the following Acegi configuration information. (Remove this info from the application-context-acegi.xml in your Tapestry app)

[!-- ========================================================= --]
[!-- ACEGI Security (Authentication Provider) --]
[!-- ========================================================= --]
[bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager"]
[property name="providers"]
[list]
[ref local="daoAuthenticationProvider"/]
[ref local="anonymousAuthenticationProvider"/
[/list]
[/property]
[/bean]

[bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"]
[property name="userDetailsService"]
[ref local="inMemoryDaoImpl"/]
[/property]
[/bean]

[bean id="passwordEncoder"
class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/]

[bean id="inMemoryDaoImpl"
class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl"]
[property name="userMap"]
[value]
tom=tvaughan,ROLE_USER,ROLE_INTERNAL,ROLE_SYSTEM_ADMIN
sue=stillery,ROLE_USER,ROLE_INTERNAL,ROLE_SYSTEM_ADMIN
carlos=cfernandez,ROLE_USER,ROLE_INTERNAL,ROLE_USER_ADMIN
joel=jmoeller,ROLE_USER,ROLE_INTERNAL,ROLE_USER_ADMIN
tony=tgiaccone,ROLE_USER,ROLE_INTERNAL,ROLE_SERVICE_LIST_ADMIN
jack=jrodriguez,ROLE_USER,ROLE_INTERNAL,ROLE_INVESTIGATION_ADMIN
walter=wkelly,ROLE_USER,ROLE_INTERNAL,ROLE_INVESTIGATION_MGR
joeexternal=password,ROLE_SUBMITTER
anonymous=anonymous,
[/value]
[/property]
[/bean]

[bean id="anonymousAuthenticationProvider"
class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"]
[property name="key"][value]foobar[/value][/property]
[/bean]


Step 2) Export the Authentication Manager as a web service by editing the remote-servlet.xml and adding these lines:

[bean name="/edis-authentication-manager"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"]
[property name="service" ref="authenticationManager" /]
[property name="serviceInterface"]
[value]org.acegisecurity.AuthenticationManager[/value]
[/property]
[/bean]


Step 3) There's a client context file managed within the web service project that the client service gets as part of the client jar. In the example of this project, the context file that we package in the client jar is called edis3-ws-client-context.xml. Edit that file to make the client know about the web service...this is also important because the client's acegi configuration information will need to reference the bean defined in this client context file.

[bean id="authenticationManager"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"]
[property name="serviceUrl"]
[value]
$WS{PROTOCOL}://$WS{HOST}:$WS{PORT}/edis3-ws/remote/edis-authentication-manager
[/value]
[/property]
[property name="serviceInterface"]
[value]org.acegisecurity.AuthenticationManager[/value]
[/property]
[/bean]


Step 4) Ok...finally on to the client. From step 1, you should have removed all the Acegi configuration information from the application-context-acegi.xml file. Now update that file to point to the beans that are exported and defined via the client-context.xml we updated in step 3.

The only modifications I had to make was this line of the basicProcessingFilter and authenticationProcessingFilter specifications:

OLD

[property name="authenticationManager"]
[ref local="authenticationManager"/]
[/property]


NEW

[property name="authenticationManager"]
[ref bean="authenticationManager"/]
[/property]

Wednesday, April 12, 2006

Spinning up on Acegi

After diddling around for a month or two and getting my sea legs on Eclipse and Java 5, I've found myself as the point person implementing a SSO framework across multiple distributed web applications. Luckily, each web app is essentially the same:
  • Static content: hosted off Apache w/ vhosts to support different dev/pre-prod/prod environments
  • Presentation Layer: Tapestry 3, now 4
  • Biz Logic Layer: POJOs, DAOs, etc.
  • Object Relational Mapping: was iBatis, now Hibernate
  • Web Services: was Hessian, now it's Spring's native web service support
  • Tying it all together: Spring...and luckily we haven't found a need for AspectJ or JMS yet, but it's coming.
To get Acegi up and running on a Tapestry GUI is no small feat if you're relatively new to Spring and the "modern" Java development environment.

Here's a series of steps I wrote up on the Spring forums site for future generations:
http://forum.springframework.org/showthread.php?t=24013

A return to the grind

My first job out of college was all Java, all the time. The good old days of the dot com boom involved lots of Weblogic, Oracle, Dynamo and POJOs wired together with badly-written JSP.

Just as EJBs were becoming all the rage, I was pulled in the direction of content management by a series of successive projects that left me out of the Java (r)evolution of the last 4 years.

Now that I'm back on a project that involves heavy Java programming based on the F/LOSS stack, I thought it'd be a good idea to capture my learning experience along the way. Enjoy.