views:

6247

answers:

6

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...

+1  A: 

I would take a look at Spring's abstract test classes and mock objects which are talked about here. They provide a powerful way of auto-wiring your Spring managed objects making unit and integration testing easier.

digitalsanctum
While those test classes are helpful, I'm not sure if they apply here. My tests have no concept of the ApplicationContext - they don't need one. All I need is to make sure that the SecurityContext is populated before the test method runs - it just feels dirty to have to set it in a ThreadLocal first
matt b
+3  A: 

The problem is that Spring Security does not make the Authentication object available as a bean in the container, so there is no way to easily inject or autowire it out of the box.

Before we started to use Spring Security, we would create a session-scoped bean in the container to store the Principal, inject this into an "AuthenticationService" (singleton) and then inject this bean into other services that needed knowledge of the current Principal.

If you are implementing your own authentication service, you could basically do the same thing: create a session-scoped bean with a "principal" property, inject this into your authentication service, have the auth service set the property on successful auth, and then make the auth service available to other beans as you need it.

I wouldn't feel too bad about using SecurityContextHolder. though. I know that it's a static / Singleton and that Spring discourages using such things but their implementation takes care to behave appropriately depending on the environment: session-scoped in a Servlet container, thread-scoped in a JUnit test, etc. The real limiting factor of a Singleton is when it provides an implementation that is inflexible to different environments.

cliff.meyers
Thanks, this is useful advice. What I've done so far is basically to proceed with calling SecurityContextHolder.getContext() (thru a few wrapper methods of my own, so at least it's only called from one class).
matt b
Although just one note - I don't think ServletContextHolder has any concept of HttpSession or a way of knowing if it's operating in a web server environment - it uses ThreadLocal unless you configure it to use something else (the only other two builtin modes are InheritableThreadLocal and Global)
matt b
The only drawback to using session/request-scoped beans in Spring is that they will fail in a JUnit test. What you can do is implement a custom scope that will use session/request if available and fall back to thread is necessary. My guess is that Spring Security is doing something similar...
cliff.meyers
+7  A: 

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 Sprin 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).

brd6644 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 so:

public class MyUserDetails implements UserDetails {
    // this is your custom UserDetails implementation to serve as a principal
    // implement the Spring methods and add your own methods as appropriate
}

public class MyUserHolder {
    public static MyUserDetails getUserDetails() {
        Authentication a = SecurityContextHolder.getContext().getAuthentication();
        if (a == null) {
            return null;
        } else {
            return (MyUserDetails) a.getPrincipal();
        }
    }
}

public class MyUserAwareController {        
    MyUserDetails currentUser;

    public void setCurrentUser(MyUserDetails currentUser) { 
        this.currentUser = currentUser;
    }

    // controller code
}

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:

<bean id="userDetails" class="MyUserHolder" factory-method="getUserDetails" scope="request">
    <aop:scoped-proxy/>
</bean>

<bean id="controller" class="MyUserAwareController">
    <property name="currentUser" ref="userDetails"/>
    <!-- other props -->
</bean>

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:

protected void setUp() {
    // existing init code

    MyUserDetails user = new MyUserDetails();
    // set up user as you wish
    controller.setCurrentUser(user);
}

Hope this helps!

Pavel
This is the best suggestion I've seen to tackle this issue, I can't believe Spring hasn't built something like this in.
Scott Bale
A: 

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 to SecurityContextHolder only in your Spring config to obtain the SecurityContext

Scott Bale
A: 

wait; i'm confused. why not a session-scoped bean to hold the principal?

john j
Ok, so, there are some types of security like Basic that are request specific and have no concept of session.
john j
+1  A: 

Using a static in this case is the best way to write secure code.

Yes, statics are generally bad - generally, but in this case, the static is what you want. Since the security context associates a Principal with the currently running thread, the most secure code would access the static from the thread as directly as possible. Hiding the access behind a wrapper class that is injected provides an attacker with more points to attack. They wouldn't need access to the code (which they would have a hard time changing if the jar was signed), they just need a way to override the configuration, which can be done at runtime or slipping some XML onto the classpath. Even using annotation injection would be overridable with external XML. Such XML could inject the running system with a rogue principal.

Michael Bushe