Tutorial :Spring Security: Authentication returns null



Question:

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.


Solution:1

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.


Solution:2

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;      }  }  


Solution:3

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.


Solution:4

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());      }  }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »