I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.
I have a method in my controller that goes something like this:
#GetMapping
public ResponseEntity<User> getUser(#AuthenticationPrincipal User activeUser){
String userEmail = activeUser.getEmail();
return userService.getUser(userEmail);
}
I get a 500 error with this.
Another variation for the controller I've tried is, and this is working on Postman/Curl :
#GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
return userService.getUser(userEmail);
}
My service looks like :
public ResponseEntity<User> getUser(String email) {
return userRepository.findByEmail(email)
.map(record -> ResponseEntity.ok().body(record))
.orElse(ResponseEntity.notFound().build());
}
In my unit test case for this controller method, I have:
#Test
#WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}
I am getting the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)
How should I go about passing or mocking a user with the current authentication? Thanks.
#WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.
At least three solutions here:
Inject an OAuth2Authentication mock or instance in the security context
change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
use some existing tooling to apply solution 1. for you, like in this libs I wrote
Sample usage with solution 1
#Test
public void test() throws Exception {
final var storedRequest = mock(OAuth2Request);
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var userAuthentication = mock(Authentication.class);
when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
when(userAuthentication.getPrincipal()).thenReturn(principal);
final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);
SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
Sample usage with solution 3
#Test
#WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:
Try Mocking OAuth2Authentication in some setUp method.
OR
If you are using Spring 4.0+, the best solution is to annotate the test method with #WithMockUser
#Test
#WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
//Your test scenario
}
Related
I am trying to test the security configuration of some of my endpoints which are secured with #PreAuthorize(#oauth2.hasScope('scope'). When accessing such an endpoint via Postman with a access token that does not have the required scope, the following is returned with HTTP status code 403 (forbidden):
{
"error": "insufficient_scope",
"error_description": "Insufficient scope for this resource",
"scope": "scope"
}
Which is the expected behaviour that I want.
When trying to test this configuration, Springs NestedServletException interferes with my test case before it can complete with my expected result.
This is a simplified version of the controller I want to test:
#RestController
#RequestMapping(value = "/api")
public class OauthTestingResource {
#PreAuthorize(#oauth2.hasScope('scope'))
#RequestMapping(value = "/scope", method = RequestMethod.GET)
public void endpoint() {
// ...
}
}
And this is the corresponding test case:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
#WebAppConfiguration
public class AuthorizationTest {
#Autowired
protected WebApplicationContext webApplicationContext;
protected SecurityContext securityContext = Mockito.mock(SecurityContext.class);
#Before
public void setup() throws Exception {
this.mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
SecurityContextHolder.setContext(securityContext);
}
protected Authentication createMockAuth(Client client) {
final List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
final Authentication pwAuth = new UsernamePasswordAuthenticationToken("testuser", "testpw", authorities);
final TokenRequest request = new TokenRequest(new HashMap<>(), client.getClientId(), client.getScopes(), "password");
final OAuthClient oauthClient = new OAuthClient(client, GrantType.PASSWORD);
return new OAuth2Authentication(request.createOAuth2Request(oauthClient), pwAuth);
}
#Test
public void testAppScope() throws Exception {
final Client client = new Client("id1", "secret1");
client.setScope("scope");
Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
// this test passes
mvc.perform(get("/api/scope")).andExpect(status().isOk());
client.setScope("other_scope");
Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
// NestedServletException thrown here
mvc.perform(get("/api/scope")).andExpect(status().isForbidden());
}
}
The exception that is thrown is the following (which is expected):
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
org.springframework.security.access.AccessDeniedException:
Insufficient scope for this resource
My question is how can I prevent this exception from interfering with my test case?
I did spring security test cases by following this link. Things worked fine except this issue of nesting original exception in NestedServletException.
I did not find any direct way to figure this out but AspectJ helped me in handling this in a cleaner way.
We can use the static assertThatThrownBy() method of the Assertions class. This method returns an AbstractThrowableAssert object that we can use to write assertions for the thrown exception.
The code that captures an exception thrown by the methodThatThrowsException() method looks as follows:
assertThatThrownBy(() -> methodThatThrowsException())
.isExactlyInstanceOf(DuplicateEmailException.class);
Thanks to this excellent blog where you can find additional details.
The way in which I handled this in my test case would be (by taking your test case codeline):
org.assertj.core.api.Assertions.assertThatThrownBy(() -> mvc.perform(get("/api/scope")).andExpect(status().isOk())).hasCause(new AccessDeniedException("Access is denied"));
That way your test case would be able to assert actual AccessDeniedException that is nested in NestedServletException.
I had a similar case and suppressed the NestedServletException by using
#Test(expected = NestedServletException.class) and then I was able to get hold of the MvcResult and do further asserts on it as in other tests like:
// then
MvcResult result = resultActions.andExpect(status().isServiceUnavailable()).andReturn();
String message = result.getResponse().getContentAsString();
assertThat(message).contains("ABC");
assertThat(result.getResolvedException().getClass()).isEqualTo(XYZ.class);
It seemed to work.
I fixed it by adding an #ExceptionHandler for that exception. Seems like if MockMvc throws an actual exception it means you don't "handle" this case which is not ideal.
If you are using a centralised class to do your error handling via AOP (using #ControllerAdvice annotation), then include this ControllerAdvice in your test setup.
#BeforeEach
public void setup() {
this.mockMvc = standaloneSetup(myController).setControllerAdvice(MyErrorHandlingAdvice.class).build();
}
I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason SecurityContextHolder.getContext().getAuthentication() always returns null, even when using #WithMockUser on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.
Class
#Override
public ResponseEntity<EmployeeDTO> meGet() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
logger.debug("Endpoint called: me({})", principal);
EmployeeDTO result;
// Get user email from security context
String email = principal.getName(); // NPE here
// ...
}
Test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"eureka.client.enabled:false"})
#WithMockUser
#ActiveProfiles(value = "test")
public class MeControllerTest extends IntegrationSpringBootTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private SecurityContext securityContext;
#MockBean
private Authentication authentication;
#MockBean
private EmployeeRepository employeeRepository;
#BeforeClass
public static void setUp() {
}
#Before
#Override
public void resetMocks() {
reset(employeeRepository);
}
#Test
public void meGet() throws Exception {
when(securityContext.getAuthentication()).thenReturn(authentication);
securityContext.setAuthentication(authentication);
when(authentication.getPrincipal()).thenReturn(mockEmployee());
SecurityContextHolder.setContext(securityContext);
when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee());
ResponseEntity<EmployeeDTO> employeeDTOResponseEntity =
this.restTemplate.getForEntity("/me", EmployeeDTO.class);
// ...
}
If I return a mock Principal instead of mockEmployee() the test cannot even start because this happens:
org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'
Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the #ActiveProfiles annotation gives us a 401 Unauthorized error against the endpoint request.
I could use PowerMock but I would like to avoid it if possible.
Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it.
You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.
AccessToken mockAccessToken = mock(AccessToken.class);
Authentication authentication = mock(Authentication.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);
I ended up using MockMvc despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext calls into another service, but before doing that I could assert that the #WithMockUser annotation was working properly.
What's key for this to work is using these snippets at class level:
#WebMvcTest(MeController.class)
#Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
Using #WebMvcTest facilitates not having to initialize a SecurityContext in the first place. You don't even have to call springSecurity(). You can just just the mockMvc.perform() operations as usual, and any calls to the SecurityContext will return whatever mocked user you specify, either with #WithMockUser or mocking the service that handles such a call.
This sample code is working for me. This code is using JUnit 5.
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {
// mockMvc is not #Autowired because I am customizing it #BeforeEach
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Mock
DefaultOidcUser principal;
#BeforeEach
public void beforeEach() {
Authentication authentication = mock(OAuth2AuthenticationToken.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(principal);
SecurityContextHolder.setContext(securityContext);
// setting mockMvc with custom securityContext
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {
final String loginName = "admin";
// given
// manipulate the principal as needed
when(principal.getAttribute("unique_name")).thenReturn(loginName);
// #formatter:off
// when
this.mockMvc.perform(get("/login"))
.andDo(print())
//then
.andExpect(status().isFound())
.andExpect(redirectedUrl("/logout"));
// #formatter:off
}
}
I have the following controller and I want to make a Junit Test on it,
#RequestMapping(value = "/path", method = RequestMethod.Get)
public String getMyPath(HttpServletRequest request, Model model) {
Principal principal = request.getUserPrincipal();
if (principal != null) {
model.addAttribute("username", principal.getName());
}
return "view";
}
The JUnit Method looks as follows:
#Test
public void testGetMyPath() throws Exception {
when(principalMock.getName()).thenReturn("someName");
this.mockMvc.perform(get("/path")).andExpect(status().isOk());
}
principalMock is declared like this:
#Mock
private Principal principalMock;
The problem is that I get NullPointerException at this line on calling getName() method on principal.
model.addAttribute("username", principal.getName());
Your mocking of Principal has absolutely no effect, since it cannot come into play anywhere (the controller is not using any injected dependency to produce the principal, but it uses the HttpServletRequest).
You need to change your test to something like the following:
this.mockMvc.perform(get("/path").principal(principalMock)).andExpect(status().isOk());
This will work because the mock principal will be passed to the MockHttpServletRequest that will actually be passed to the controller method
I'm using Spring 3.1.0.RELEASE with Spring Security 3.1. I want to inject my Spring user (i.e. the user who is currently logged in) into a controller. I want to do this as opposed to using
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
because it allows me to test the controller more easily with JUnit. However, I'm having a problem with my current setup. My question is, what is the correct way to inject my user (per request) into my controller? In my application context file, I have ...
<bean id="userDetails" class="com.myco.eventmaven.security.SecurityHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy />
</bean>
where I define my factory class as ...
public class SecurityHolder {
#Autowired
private static UserService userService;
public static MyUserDetails getUserDetails() {
final Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
final MyUserDetails reg = (MyUserDetails) a.getPrincipal();
final int userId = reg.getId();
final MyUserDetails foundUser = userService.findUserById(userId);
return foundUser;
} // if
} // getUserDetails
}
but the factory class repeatedly dies because "userService" fails to get autowired (the value is always null). I'm looking for a better way to do all this that can easily also integrate into my JUnit test. Any ideas?
Edit: Here's the JUnit test I'm looking to work with ...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "file:src/test/resources/testApplicationContext.xml" })
public class UserEventFeedsControllerTest extends AbstractTransactionalJUnit4SpringContextTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
...
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() {
...
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
...
#Test
public void testSubmitUserEventFeedsForm() throws Exception {
request.setRequestURI("/eventfeeds.jsp");
request.setMethod("POST");
final List<EventFeed> allEventFeeds = getAllEventFeeds();
request.setParameter("userEventFeeds", allEventFeeds.get(0).getId().toString());
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "user/eventfeeds");
}
You cannot autowire static fields. There are some workarounds, but I don't want to show them to you...
There are plenty of ways to access current user in an easier and more elegant matter:
Inject Principal to your controller (see When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?):
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
UserDetails ud = ((Authentication)principal).getPrincipal()
Develop your custom facade over SecurityContext
Replace built-in contextHolderStrategy in SecurityContextHolder for the purpose of testing
See also
How to get active user's UserDetails
Spring 3 MVC Controller integration test - inject Principal into method
As part of Spring 3 MVC it is possible to inject the currently logged in user (Principle) object into a controller method.
E.g.
#Controller
public class MyController {
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(ModelMap model, Principal principal) {
String name = principal.getName();
... the rest here
}
}
This is documented as part of the Spring 3 documentation here:
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments.
This works in the production code. However I don't know how to test this.
When I create an integration test (having set up spring security context as well)
and call the controller handle method then the Principal is always null!
public class FareTypeControllerIntegrationTest extends SpringTestBase {
#Autowired
private MyController controller;
#Autowired
private AnnotationMethodHandlerAdapter handlerAdapter;
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
#Test
public void testUpdate() throws Exception {
request.setRequestURI("/update");
request.setMethod(HttpMethod.POST.name());
... setup rest of request
ModelAndView mav = handlerAdapter.handle(request, response, controller);
.. rest of assertions
}
The tests are running correctly and everything except the Principal is null.
Any ideas?
TIA
Ayub
After a quick look into Spring sources this should work:
request.setUserPrincipal(somePrincipal);
I've tried to do this some time ago, here is the method i used to set up authentication.
protected void setSecurityContext(String login){
userDetailsTest = userManager.loadUserByUsername(login);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userDetailsTest, userDetailsTest.getAuthorities());
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication((Authentication) testingAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
}
Then i just call it in the #Before method of the test.
Hope it helps.
I do something like this in my tests prior to calling code using Spring Security (such as the Principal parameter resolver you are testing):
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("wiseau", "Love is blind"));