Spring Security allows to secure access to methods, allowing execution only for user with defined roles.
For example, using annotation to configure the roles required:
public interface MyService
{
@PreAuthorize("hasRole('USER') OR hasRole('ADMIN')")
void hello();
@PreAuthorize("hasRole('ADMIN')")
void bye();
}
In order to call the
bye()
method the user must have the
ADMIN role, for
hello()
method also the the
USER role is enough.
If a secured method is called by a user without the needed roles is raised an exception of type
org.springframework.security.access.AccessDeniedException
.
Method security is active even during tests, so we have developed an annotation and a listener that can be used to setup easily user roles when testing (we use TestNG, probably something like this can be done also with Junit).
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Authenticate {
String username() default "";
String[] roles() default {};
}
public class AuthenticationListener implements IInvokedMethodListener
{
public static final String DEFAULT_USERNAME = "default";
private static final String FAKE_PASSWORD = "fakePassword";
/**
* {@inheritDoc}
*/
@Override
public void beforeInvocation(IInvokedMethod method, ITestResult testResult)
{
if (method.isTestMethod())
{
ITestNGMethod testMethod = method.getTestMethod();
Method javaMethod = testMethod.getMethod();
Authenticate userAnnotation = javaMethod.getAnnotation(Authenticate.class);
if (userAnnotation != null)
{
String username = userAnnotation.username();
if (username == null || username.isEmpty())
{
username = DEFAULT_USERNAME;
}
String[] roles = userAnnotation.roles(); // role may be null/empty
authenticateUser(username, roles);
}
}
}
private void authenticateUser(String username, String... roles)
{
Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
if (roles != null)
{
for (String role : roles)
{
grantedAuthorities.add(new GrantedAuthorityImpl(role));
}
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, FAKE_PASSWORD,
grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(token);
}
/**
* {@inheritDoc}
*/
@Override
public void afterInvocation(IInvokedMethod method, ITestResult testResult)
{
if (method.isTestMethod())
{
SecurityContextHolder.clearContext();
}
}
}
The
Authenticate
annotation allows to set the username and a list of roles that must be used when executing a test method, the
AuthenticationListener
take care to configure the authentication token before test execution and to reset the security context after execution.
This is an example of a test method that uses the
Authenticate
annotation:
@ContextConfiguration("classpath:META-INF/spring/applicationContext*.xml")
@Listeners(AuthenticationListener.class)
public class MyServiceTest extends AbstractTestNGSpringContextTests
{
@Autowired
private MyService myService;
@Test
@Authenticate(username = "pippo", roles = {"ADMIN" })
public void testCallHelloAsAdmin()
{
myService.hello();
}
@Test
@Authenticate(username = "pippo", roles = {"USER" })
public void testCallHelloAsUser()
{
myService.hello();
}
@Test
@Authenticate(username = "pippo", roles = {"ADMIN" })
public void testCallByeAsAdmin()
{
myService.bye();
}
@Test(expectedExceptions = AccessDeniedException.class)
@Authenticate(username = "pippo", roles = {"USER" })
public void testCallByeAsUser()
{
myService.bye();
}
}
See also:
The demo project on GitHub
Spring Security
TestNG Listeners
Annotations