My last post on how to use Acegi Security while JUnit integration testing drew a fair bit of traffic, so I thought I'd write a small Eclipse project that pulls all the bits together, and updated to run with Spring 2.0 and Acegi 1.0.3.
To build this project I followed the following steps:
- Got petclinic 2.0.2 going (download Spring here) and followed the instructions in the readme.txt files.
- Got acegi 1.0.3 (download Acegi here) and followed the instructions on integrating it with petclinic as described here (including the optional bonus section - "securing the middle tier").
- (optional) Ran the JdbcClinicTests from within Eclipse. This allowed me to step through the code in the debugger.
- Modified JdbcClinicTests so that it used the Acegi post-interceptor.
To get JdbcClinicTests working with Acegi (Step 4) I made the following changes:
- Add
/WEB-INF/applicationContext-acegi-security.xml
- Applied the same changes to /test/../applicationContext-jdbc.xml that were done in step 2 above - i.e. add a "postInterceptor" entry to the clinic bean and create a "methodSecurityInterceptor" bean.
- Add the acegi libraries to the debug profile, and include the war directory (so that it can find users.properties and the acegi context file). Here is a screen grab of my settings: Download acegi-debug-settings.gif
to JdbcClinicTests.getConfigLocations.
If you ran the project at this point you would get a AuthenticationCredentialsNotFoundException for every single test. To prevent this, we need to use a TestingAuthenticationProvider instead. I created a class called AcegiTestIntegration that does the replacement on the fly in its' constructor, and has a setAuthenticationToken method that sets up the security context.
For example, here is how it is initialized in the JdbcClinic.setClinic method:
acegiTestIntegration = new AcegiTestIntegration(getApplicationContext());
acegiTestIntegration.setAuthenticationToken(dianne);
The variable dianne is declared as:
private TestingAuthenticationToken dianne = new TestingAuthenticationToken("dianne", "emu", new GrantedAuthority[] {ROLE_USER});
Now if you ran the project, every test would pass except the last one - testInsertVisit - which would get an AccessDeniedException. Success! You have validated that only users with the supervisor role can invoke the method. Now, to get the unit test to pass, we simply catch the exception, and set up a token that should pass:
try {
this.clinic.storeVisit(visit);
fail("Should of thrown an AccessDeniedException because dianne is not ROLE_SUPERVISOR");
} catch (AccessDeniedException e) {
// expected - dianne is ROLE_USER, not ROLE_SUPERVISOR
}
acegiTestIntegration.setAuthenticationToken(marissa); // ROLE_SUPERVISOR
this.clinic.storeVisit(visit);
That's it.
Choose from one of the following downloads:
- Download petclinic-acegi-junit.zip - The full source tree (including jar files) and eclipse project (12,603 KB).
- Download AcegiTestIntegration.java - AcegiTestIntegration source file (put this under petclinic/test/org etc) (2 KB).
Additional notes:
- An alternative approach you may want to consider is having seperate applicationContext-acegi-security.xml files - one for production and one for testing. The testing one would be configured to use a TestingAuthenticationProvider. The down side to this is having to move different configuration files around in the build script and/or keeping changes in sync between the files.
- AcegiTestIntegration could be configured as a bean and injected into the test class. In this case, make it ApplicationContextAware and move the code that is in the constructor to it's setApplicationContext method, then get rid of the constructor.
Please leave a comment if you find this useful, or have suggestions for improvements. Thanks.
Recent Comments