Reputation:
I have configured a Spring bean as follows to return a SecurityContext:
<bean id="securityContext" class="org.springframework.security.context.SecurityContextHolder"
factory-method="getContext">
</bean>
When I use this bean the Authentication object returns null.
Authentication authentication = securityContext.getAuthentication();
GrantedAuthority[] authorities = authentication.getAuthorities();
The second line above causes an NPE. Which seems odd to me, as the following code returns the authorities as expected:
GrantedAuthority[] authorities =
SecurityContextHolder.getContext().getAuthentication().getAuthorities();
Basically I'm trying to eliminate the static call to SecurityContextHolder.getContext() to make my code more testable.
Any thoughts on how to remedy this? Why is the SecurityContext returned by Spring not able to return the authorities while a static call from within my own code can?
FYI I am executing the code from within a Struts 2 Action.
Upvotes: 1
Views: 13009
Reputation: 990
Use a supplier that you could inject with a default one perhaps.
public class UserDetailsArgumentResolver implements HandlerMethodArgumentResolver {
private final Supplier<SecurityContext> contextSupplier;
public UserDetailsArgumentResolver(Supplier<SecurityContext> contextSupplier) {
this.contextSupplier = contextSupplier;
}
public UserDetailsArgumentResolver() {
this.contextSupplier = SecurityContextHolder::getContext;
}
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return UserDetails.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
Authentication authentication = contextSupplier.get().getAuthentication();
return new UserDetails(authentication.getName(), authentication.getAuthorities());
}
}
Upvotes: 0
Reputation: 1527
It happens since the bean you created does not define scope- so it is basically a singleton. To make it work as you wish you need to make it request/session scoped.
Upvotes: 0
Reputation: 5328
You can make your code testable using the static approach. You simply need to create your own implementation of org.springframework.security.Authentication
So in your JUnit Test...
//Assuming you've loaded the user, create your stub
Authentication authentication = new TestAuthentication(userDetails);
//Update the context
SecurityContextHolder.getContext().setAuthentication(authentication);
In the example above 'userDetails' is the class which implements 'UserDetails' and typically wraps your domain User object.
My TestAuthentication class - hope this helps
public class TestAuthentication implements Authentication {
private UserDetails userDetails;
private boolean authentication = true;
public TestAuthentication(UserDetails userDetails){
NullArgumentException.assertNotNull(userDetails, "userDetails");
this.userDetails = userDetails;
}
public TestAuthentication(UserDetails userDetails, boolean authentication){
NullArgumentException.assertNotNull(userDetails, "userDetails");
this.userDetails = userDetails;
this.authentication = authentication;
}
public GrantedAuthority[] getAuthorities() {
return userDetails.getAuthorities();
}
public Object getCredentials() {
return null;
}
public Object getDetails() {
return null;
}
public Object getPrincipal() {
return this.userDetails;
}
public boolean isAuthenticated() {
return authentication;
}
public void setAuthenticated(boolean arg0)
throws IllegalArgumentException {
this.authentication = arg0;
}
public String getName() {
return null;
}
}
Upvotes: 0
Reputation: 104178
SecurityContextHolder.getContext() returns the context associated with the current thread. In bean instantiation the context stored in your bean is different than the context you need when your application is running. I don't think that it is possible to store the context in a bean and use this all the time.
Upvotes: 3