This tutorial extends on the baseline laid by Spring Security Tutorial maven archetype.
I’ve modified the spring security config file and will be unit testing a simple ProfileService (CRUD) on a Profile entity (User object). The tests include login/logout and spring services security unit testing.
application-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<global-method-security pre-post-annotations="enabled"/>
<http use-expressions="true">
<intercept-url pattern="/secure/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
<intercept-url pattern="/secure/**" access="isAuthenticated()" />
<intercept-url pattern="/**" access="permitAll" />
<form-login />
<logout />
<remember-me />
<!-- Uncomment to limit the number of sessions a user can have -->
<session-management invalid-session-url="/timeout.jsp">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>
<authentication-manager>
<authentication-provider>
<!-- <password-encoder hash="md5"/> -->
<user-service>
<user name="admin" password="admin" authorities="ROLE_ADMIN, ROLE_USER" />
<user name="user" password="user" authorities="ROLE_USER" />
<user name="reports" password="reports" authorities="ROLE_REPORTS" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Base DB Unit Test with utils for Spring Security
import java.io.FileInputStream;
import java.sql.Connection;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.dbunit.database.*;
import org.dbunit.operation.DatabaseOperation;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.transaction.*;
/**
* Base Test that wraps a DbUnit style Test case by inserting a sample testdata
* on startup. Provides necessary functions to login/logout, add roles, etc
*/
public abstract class BaseSecurityTest {
private static final Logger logger = Logger
.getLogger(BaseSecurityTest.class);
Connection con;
IDatabaseConnection dbUnitCon;
IDataSet dataSet;
@Inject
private DataSource dataSource;
protected abstract String getTestClassname();
@BeforeTransaction
public void beforeTx() throws Exception {
logger.debug("Opening Db Connection ... ");
con = DataSourceUtils.getConnection(dataSource);
dbUnitCon = new DatabaseConnection(con);
dataSet = getDataSet(getTestClassname());
DatabaseOperation.CLEAN_INSERT.execute(dbUnitCon, dataSet);
}
@AfterTransaction
public void afterTx() throws Exception {
logger.debug("Releasing Db Connection ...");
DataSourceUtils.releaseConnection(con, dataSource);
}
protected IDataSet getDataSet(String name) throws Exception {
return new FlatXmlDataSetBuilder().build(new FileInputStream(
"src/test/resources/" + name + ".xml"));
}
protected void clearContext() {
SecurityContextHolder.clearContext();
}
protected void login(String username, String password) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(username, password));
logger.debug("User:" + username + " logged in");
}
protected String getLoginDetails() {
Object principal = SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
return ((UserDetails)principal).getUsername();
}
else {
return principal.toString();
}
}
}
ProfileServiceTest
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.inject.Inject;
import org.apache.log4j.Logger;
import xxx.Profile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:spring/applicationContext-core.xml",
"classpath*:spring/applicationContext-mail.xml",
"classpath*:spring/applicationContext-security.xml" })
@TransactionConfiguration
@Transactional
public class ProfileServiceTest extends BaseSecurityTest {
private static final Logger logger = Logger
.getLogger(ProfileServiceTest.class);
@Inject
private ProfileService profileService;
@Override
protected String getTestClassname() {
return "ProfileServiceTest";
}
@Test
public void testUserLogin() {
login("admin", "admin");
String username = getLoginDetails();
assertEquals(username, "admin");
login("user", "user");
String username2 = getLoginDetails();
assertEquals(username2, "user");
login("reports", "reports");
String username3 = getLoginDetails();
assertEquals(username3, "reports");
}
@Test
public void testRead() {
// Test with role that has access to the service
login("user", "user");
profileService.read("1");
login("reports", "reports");
profileService.read("1");
// Test with role that doesn't have access to the service
login("admin", "admin");
try {
profileService.read("1");
}
catch (AccessDeniedException e) {
// Expected
assert(true);
return;
}
// it should not reach here
assert(false);
}
@Test
public void testFindProfiles() {
List<Profile> list = profileService.FindProfiles();
assertEquals(4, list.size());
}
@Test
public void testFindProfileById() {
List<Profile> list = profileService.findProfileById("123");
assertEquals(1, list.size());
}
@Test
public void testSave() {
// Test with role that has access to the service
login("admin", "admin");
Profile dummy = new Profile();
profileService.save(dummy);
// Test with role that doesn't have access to the service
login("reports", "reports");
try {
profileService.save(dummy);
}
catch (AccessDeniedException e) {
// Expected
assert(true);
return;
}
// it should not reach here
assert(false);
}
}
The Profile Service
public interface ProfileService {
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_REPORTS')")
Profile read(String id);
List<Profile> findProfiles();
List<Profile> findProfileById(String Id);
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')")
Profile save(Profile account);
@PreAuthorize("hasRole('ROLE_ADMIN')")
void delete();
}