My company has been evaluating Spring MVC to determine if we should use it in one of our next projects. So far I love what I've seen, and right now I'm taking a look at the Spring Security module to determine if it's something we can/should use.
Our security requirements are pretty basic; a user just needs to be able to provide a username and password to be able to access certain parts of the site (such as to get info about their account); and there are a handful of pages on the site (FAQs, Support, etc) where an anonymous user should be given access.
In the prototype I've been creating, I have been storing a "LoginCredentials" object (which just contains username and password) in Session for an authenticated user; some of the controllers check to see if this object is in session to get a reference to the logged-in username, for example. I'm looking to replace this home-grown logic with Spring Security instead, which would have the nice benefit of removing any sort of "how do we track logged in users?" and "how do we authenticate users?" from my controller/business code.
It seems like Spring Security provides a (per-thread) "context" object to be able to access the username/principal info from anywhere in your app...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... which seems very un-Spring like as this object is a (global) singleton, in a way.
My question is this: if this is the standard way to access information about the authenticated user in Spring Security, what is the accepted way to inject an Authentication object into the SecurityContext so that it is available for my unit tests when the unit tests require an authenticated user?
Do I need to wire this up in the initialization method of each test case?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
This seems overly verbose. Is there an easier way?
The SecurityContextHolder
object itself seems very un-Spring-like...
You are quite right to be concerned - static method calls are particularly problematic for unit testing as you cannot easily mock your dependencies. What I am going to show you is how to let the Spring IoC container do the dirty work for you, leaving you with neat, testable code. SecurityContextHolder is a framework class and while it may be ok for your low-level security code to be tied to it, you probably want to expose a neater interface to your UI components (i.e. controllers).
cliff.meyers mentioned one way around it - create your own "principal" type and inject an instance into consumers. The Spring <aop:scoped-proxy/> tag introduced in 2.x combined with a request scope bean definition, and the factory-method support may be the ticket to the most readable code.
It could work like following:
Nothing complicated so far, right? In fact you probably had to do most of this already. Next, in your bean context define a request-scoped bean to hold the principal:
Thanks to the magic of the aop:scoped-proxy tag, the static method getUserDetails will be called every time a new HTTP request comes in and any references to the currentUser property will be resolved correctly. Now unit testing becomes trivial:
Hope this helps!
Authentication is a property of a thread in server environment in the same way as it is a property of a process in OS. Having a bean instance for accessing authentication information would be inconvenient configuration and wiring overhead without any benefit.
Regarding test authentication there are several ways how you can make your life easier. My favourite is to make a custom annotation
@Authenticated
and test execution listener, which manages it. CheckDirtiesContextTestExecutionListener
for inspiration.I asked the same question myself over here, and just posted an answer that I recently found. Short answer is: inject a
SecurityContext
, and refer toSecurityContextHolder
only in your Spring config to obtain theSecurityContext
Just do it the usual way and then insert it using
SecurityContextHolder.setContext()
in your test class, for example:Controller:
Test:
General
In the meantime (since version 3.2, in the year 2013, thanks to SEC-2298) the authentication can be injected into MVC methods using the annotation @AuthenticationPrincipal:
Tests
In your unit test you can obviously call this Method directly. In integration tests using
org.springframework.test.web.servlet.MockMvc
you can useorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()
to inject the user like this:This will however just directly fill the SecurityContext. If you want to make sure that the user is loaded from a session in your test, you can use this: