I am using Spring Boot 1.4.2 and I am new with Spring Boot. I have an Authentication Filter to set the current user information when the user log in. In an advice for the controller, I made the call to get the current userId as below:
public static String getCurrentUserToken(){
return ((AuthenticatedUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
}
Here is my custom AuthenticatedUser:
public class AuthenticatedUser implements Serializable {
private final String userName;
private final String userId;
private final String sessionId;
public AuthenticatedUser(String userName, String userId, String sessionId) {
super();
this.userName = userName;
this.userId = userId;
this.sessionId = sessionId;
}
public String getUserName() {
return userName;
}
public String getUserId() {
return userId;
}
public String getSessionId() {
return sessionId;
}
}
Everything works fine. However, the filter does not work in integration testing and I need to mock a current user. I searched a lot on how to mock a user but none of them help me. I finally found out this guide which is likely close to what I wanted: https://aggarwalarpit.wordpress.com/2017/05/17/mocking-spring-security-context-for-unit-testing/ Below is my testing class following that guideline:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class PersonalLoanPreApprovalTest {
@Before
public void initDB() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testRequestPersonalLoanPreApproval_Me() {
AuthenticatedUser applicationUser = new
AuthenticatedUser("[email protected]", "2d1b5ae3", "123");
UsernamePasswordAuthenticationToken authentication = new ApiKeyAuthentication(applicationUser);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
// error at this line
when(securityContext.getAuthentication().getPrincipal()) .thenReturn(applicationUser);
// The controller for this api has the advice to get the userId
MyResponse response = restTemplate.getForObject(url.toString(), MyResponse.class);
}
}
I got this error:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
AuthenticatedUser cannot be returned by getAuthentication()
getAuthentication() should return Authentication
I spent couple days on this already, tried a lot of suggestions I found but still failed. I also tried to remove the line that cause error, but when the error gone, I still could not get the current user information in my controller advice.
Any suggestion is highly appreciated.
UPDATE1: Just wanted to have my results with some modification in the code.
With @glitch suggestion, I changed the code to mock the authentication and user in my test method as below:
@Test
public void testRequestPersonalLoanPreApproval_Me() {
AuthenticatedUser applicationUser = new AuthenticatedUser("[email protected]", "2d1b5ae3-cf04-44f5-9493-f0518cab4554", "123");
Authentication authentication = Mockito.mock(Authentication.class);
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
Mockito.when(authentication.getPrincipal()).thenReturn(applicationUser);
// The controller for this api has the advice to get the userId
MyResponse response = restTemplate.getForObject(url.toString(), MyResponse.class);
}
}
I now can get rid of the error in the testing class. I debugged into the code, see that the securityContext has value when I am in the testing class. But when I jump to the code inside the controller advice, the below get returns null:
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
There a Spring test annotation (org.springframework.security.test.context.support.WithMockUser
) which does this for you...
@Test
@WithMockUser(username = "myUser", roles = { "myAuthority" })
public void aTest(){
// any usage of `Authentication` in this test invocation will get an instance with the user name "myUser" and a granted authority "myAuthority"
// ...
}
Alternatively, you can continue with your current approach by mocking Spring's Authentication
. For example, in your test case:
Authentication authentication = Mockito.mock(Authentication.class);
Then tell Spring's SecurityContextHolder
to store this Authentication
instance:
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication()).thenReturn(auth);
SecurityContextHolder.setContext(securityContext);
Now, if your code needs Authentication
to return something (the user name perhaps) you just set some expectations on the mocked Authentication
instance in the usual way e.g.
Mockito.when(authentication.getName()).thenReturn("aName");
This is quite close to what you are already doing but you have just mocked the wrong type.
Update 1: in response to this update to the OP:
I now can get rid of the error in the testing class. I debugged into the code, see that the securityContext has value when I am in the testing class. But when I jump to the code inside the controller advice, the below get returns null:
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
You just need to set an expectation on the mocked Authentication
, for example:
UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken("aUserName", "aPassword");
Mockito.when(authentication.getPrincipal()).thenReturn(principal);
With the above code this line ...
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... will return that UsernamePasswordAuthenticationToken
.
Since you use a custom type (ApiKeyAuthentication
, I think?) you should just let authentication.getPrincipal()
return that type instead of a UsernamePasswordAuthenticationToken
.
In addition to glytching's answer I had to add the following line when mocking:
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);
That is because the spring boot test code doesn't run in the same thread as the spring boot application you're testing, and the default strategy is ThreadLocal. With MODE_GLOBAL you ensure that the mocked SecurityContext object is actually returned in your application.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With