Mocking a Keycloak token for testing a Spring controller - spring

I want to write unit tests for my spring controller. I'm using keycloak's openid flow to secure my endpoints.
In my tests I'm using the #WithMockUser annotation to mock an authenticated user. My problem is that I'm reading the userId from the token of the principal. My unit test now fails because the userId I read from the token is null;
if (principal instanceof KeycloakAuthenticationToken) {
KeycloakAuthenticationToken authenticationToken = (KeycloakAuthenticationToken) principal;
SimpleKeycloakAccount account = (SimpleKeycloakAccount) authenticationToken.getDetails();
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
AccessToken token = keycloakSecurityContext.getToken();
Map<String, Object> otherClaims = token.getOtherClaims();
userId = otherClaims.get("userId").toString();
}
Is there anything to easily mock the KeycloakAuthenticationToken?

#WithmockUser configures the security-context with a UsernamePasswordAuthenticationToken. This can be just fine for most use-cases but when your app relies on another Authentication implementation (like your code does), you have to build or mock an instance of the right type and put it in the test security-context: SecurityContextHolder.getContext().setAuthentication(authentication);
Of course, you'll soon want to automate this, building your own annotation or RequestPostProcessor
... or ...
take one "off the shelf", like in this lib of mine, which is available from maven-central:
<dependency>
<!-- just enough for #WithMockKeycloackAuth -->
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-addons</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<!-- required only for WebMvc "fluent" API -->
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
You can use it either with #WithMockKeycloackAuth annotations:
#RunWith(SpringRunner.class)
#WebMvcTest(GreetingController.class)
#ContextConfiguration(classes = GreetingApp.class)
#ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTests extends ServletUnitTestingSupport {
#MockBean
MessageService messageService;
#Test
#WithMockKeycloackAuth("TESTER")
public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretRouteIsNotAccessible() throws Exception {
mockMvc().get("/secured-route").andExpect(status().isForbidden());
}
#Test
#WithMockKeycloackAuth("AUTHORIZED_PERSONNEL")
public void whenUserIsGrantedWithAuthorizedPersonelThenSecretRouteIsAccessible() throws Exception {
mockMvc().get("/secured-route").andExpect(content().string(is("secret route")));
}
#Test
#WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
claims = #OpenIdClaims(
sub = "42",
email = "ch4mp#c4-soft.com",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy",
otherClaims = #Claims(stringClaims = #StringClaim(name = "foo", value = "bar"))))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
mockMvc().get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")));
}
Or MockMvc fluent API (RequestPostProcessor):
#RunWith(SpringRunner.class)
#WebMvcTest(GreetingController.class)
#ContextConfiguration(classes = GreetingApp.class)
#ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTest extends ServletKeycloakAuthUnitTestingSupport {
#MockBean
MessageService messageService;
#Test
public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretMethodIsNotAccessible() throws Exception {
mockMvc().with(authentication().roles("TESTER")).get("/secured-method").andExpect(status().isForbidden());
}
#Test
public void whenUserIsGrantedWithAuthorizedPersonelThenSecretMethodIsAccessible() throws Exception {
mockMvc().with(authentication().roles("AUTHORIZED_PERSONNEL")).get("/secured-method")
.andExpect(content().string(is("secret method")));
}
}

I don't like to add extra dependency, a specially when it related only for test case scenario. Also adding dependency in some project is a big process with security checks and needs to be approved by many Managers, seniors etc. So this is my solution that allows to mock Keycloak security context without instance of keycloak and other extra dependecies. This is copied form my project so adjustments are required. Hope it help.
#Test
void shouldFooOnProtectedEndpoint() throws Exception {
//given
AccessToken token = new AccessToken();
// by username i was differentiate is it allowed
token.setPreferredUsername(SUBMITTER_USERNAME);
KeycloakSecurityContext keycloakSecurityContext = mock(KeycloakSecurityContext.class);
given(keycloakSecurityContext.getToken()).willReturn(token);
KeycloakPrincipal principal = mock(KeycloakPrincipal.class);
given(principal.getKeycloakSecurityContext()).willReturn(keycloakSecurityContext);
Authentication auth = mock(Authentication.class);
given(auth.getPrincipal()).willReturn(principal);
SecurityContextHolder.getContext().setAuthentication(auth);
... test logic
}

Related

Spring Security and MockMvc - Need to mock authentication or principal

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
}

Spring Security + Spring-Boot Testing Controller

I'm trying to test the home controller
#RequestMapping("/")
#ResponseBody
String home() {
return "Hello World!";
}
I'm using spring security using as username "user" and test as password by default but #PreAuthorize is not working
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#PreAuthorize("hasRole('ADMIN')")
public class HomeControllerTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
#WithMockUser(username = "user", password = "test", roles = "ADMIN")
public void home() throws Exception {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World!");
}
}
The result
Expected result:
<"[Hello World!]">
Actual result:
<"{"timestamp":1501100448216,"status":401,"error":"Unauthorized","message":"Full
authentication is required to access this resource","path":"/"}]">
Am I missing something?
Try to add the following to your test class:
#TestExecutionListeners(mergeMode = MergeMode.MERGE_WITH_DEFAULTS, listeners = {
WithSecurityContextTestExecutionListener.class
})
And the following dependency if you don't have it:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Spring security require an extra listener that is not present in tests by default so you need to tell spring to add it by specifing the #TestExecutionListeners annotation in merge mode so it will merge the current listed listeners with the listeners you want to add - in this case WithSecurityContextTestExecutionListener

Mock SecurityContextHolder / Authentication always returning null

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

How to Mock the security context in Spring MVC for testing

I need to test some protected urls, therefore I need to set up a mock security context in my tests (junit).
In particular I need perform some gets and post against my web application, using an authenticated user.
Below there is my code, I am able to create a such security context but I need to inject it in the 'MockMvc' object.
I set the authentication object in the security context and it works, the output result of 'SecurityContextHolder.getContext().getAuthentication().getPrincipal()' is chanelle.evans#616747.com but when I call the GET on /profile I have an assertion error because I am redirected to my login page, and not to /profile.
#WebAppConfiguration
#ContextConfiguration(locations = {"classpath:spring/security.xml", "classpath:spring/view.xml"})
#ActiveProfiles("default")
#RunWith(SpringJUnit4ClassRunner.class)
public class AuthenticationTest {
#Autowired
WebApplicationContext ctx;
private MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#BeforeClass
public static void setUpBeforeClass() throws Exception {
}
#AfterClass
public static void tearDownAfterClass() throws Exception {
}
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(ctx).addFilters(springSecurityFilterChain).build();
//#formatter:off
UserDetailsLogic userDetailsLogic = null;
userDetailsLogic = ctx.getBean(UserDetailsLogic.class);
final UserDetailsImp userDetailsImp = new UserDetailsImp();
userDetailsImp.setAccountId(1001);
userDetailsImp.setUserId(8001);
userDetailsImp.setPassword("a378c92df7531df6fdf351f7ae1713f91f2dd2d45b9c6e1a8b02736ee3afec6595ff60465e9cb8da");
userDetailsImp.setUsername("chanelle.evans#616747.com");
userDetailsImp.setEmail("chanelle.evans#616747.com");
final Collection<GrantedAuthorityImplementation> authorities= new ArrayList<GrantedAuthorityImplementation>();
authorities.add(new GrantedAuthorityImplementation("ROLE_USER"));
userDetailsImp.setAuthorities(authorities);
userDetailsImp.setAccountNonExpired(true);
userDetailsImp.setAccountNonLocked(true);
userDetailsImp.setCredentialsNonExpired(true);
userDetailsImp.setEnabled(true);
final Authentication authToken = new UsernamePasswordAuthenticationToken (userDetailsImp.getUsername(), userDetailsImp.getPassword(), userDetailsImp.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
System.out.println("principal:"+SecurityContextHolder.getContext().getAuthentication().getPrincipal());
mockMvc.perform(get("/profile").principal(authToken)
.contentType(MediaType.TEXT_HTML)
.accept(MediaType.TEXT_HTML))
.andDo(print())
.andExpect(status().isOk())
.andExpect(redirectedUrl(null))
.andExpect(forwardedUrl(null));
//#formatter:on
}
I guess that I should put my authentication object inside the MockMvc object, but I do not know how
Does anyone have any idea?
This is something I wrote few days ago. I think that could be helpful (I tested the same thing against the login form, using the session for the second request) see loginUser1Ok(..))
See MvcTest.java in
m4nuv/easy-bank.
Add in pom.xml
<repository>
<id>spring-snaspho</id>
<url>http://repo.springsource.org/libs-milestone/</url>
</repository>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.0.M1</version>
</dependency>
and use org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors for authorization request. See the sample usage here, and Spring.io jira ticket SEC-2592.

Spring Test & Security: How to mock authentication?

I was trying to figure out how to unit test if my the URLs of my controllers are properly secured. Just in case someone changes things around and accidentally removes security settings.
My controller method looks like this:
#RequestMapping("/api/v1/resource/test")
#Secured("ROLE_USER")
public #ResonseBody String test() {
return "test";
}
I set up a WebTestEnvironment like so:
import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/security.xml",
"file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
"file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
#Resource
private FilterChainProxy springSecurityFilterChain;
#Autowired
#Qualifier("databaseUserService")
protected UserDetailsService userDetailsService;
#Autowired
private WebApplicationContext wac;
#Autowired
protected DataSource dataSource;
protected MockMvc mockMvc;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
UserDetails user = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities());
return authentication;
}
#Before
public void setupMockMvc() throws NamingException {
// setup mock MVC
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain)
.build();
}
}
In my actual test I tried to do something like this:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class CopyOfClaimTest extends WebappTestEnvironment {
#Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
SecurityContextHolder.getContext().setAuthentication(principal);
super.mockMvc
.perform(
get("/api/v1/resource/test")
// .principal(principal)
.session(session))
.andExpect(status().isOk());
}
}
I picked this up here:
http://java.dzone.com/articles/spring-test-mvc-junit-testing here:
http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller or here:
How to JUnit tests a #PreAuthorize annotation and its spring EL specified by a spring MVC Controller?
Yet if one looks closely this only helps when not sending actual requests to URLs, but only when testing services on a function level. In my case an "access denied" exception was thrown:
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
...
The following two log messages are noteworthy basically saying that no user was authenticated indicating that setting the Principal did not work, or that it was overwritten.
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Seaching for answer I couldn't find any to be easy and flexible at the same time, then I found the Spring Security Reference and I realized there are near to perfect solutions. AOP solutions often are the greatest ones for testing, and Spring provides it with #WithMockUser, #WithUserDetails and #WithSecurityContext, in this artifact:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
In most cases, #WithUserDetails gathers the flexibility and power I need.
How #WithUserDetails works?
Basically you just need to create a custom UserDetailsService with all the possible users profiles you want to test. E.g
#TestConfiguration
public class SpringSecurityWebAuxTestConfig {
#Bean
#Primary
public UserDetailsService userDetailsService() {
User basicUser = new UserImpl("Basic User", "user#company.com", "password");
UserActive basicActiveUser = new UserActive(basicUser, Arrays.asList(
new SimpleGrantedAuthority("ROLE_USER"),
new SimpleGrantedAuthority("PERM_FOO_READ")
));
User managerUser = new UserImpl("Manager User", "manager#company.com", "password");
UserActive managerActiveUser = new UserActive(managerUser, Arrays.asList(
new SimpleGrantedAuthority("ROLE_MANAGER"),
new SimpleGrantedAuthority("PERM_FOO_READ"),
new SimpleGrantedAuthority("PERM_FOO_WRITE"),
new SimpleGrantedAuthority("PERM_FOO_MANAGE")
));
return new InMemoryUserDetailsManager(Arrays.asList(
basicActiveUser, managerActiveUser
));
}
}
Now we have our users ready, so imagine we want to test the access control to this controller function:
#RestController
#RequestMapping("/foo")
public class FooController {
#Secured("ROLE_MANAGER")
#GetMapping("/salute")
public String saluteYourManager(#AuthenticationPrincipal User activeUser)
{
return String.format("Hi %s. Foo salutes you!", activeUser.getUsername());
}
}
Here we have a get mapped function to the route /foo/salute and we are testing a role based security with the #Secured annotation, although you can test #PreAuthorize and #PostAuthorize as well.
Let's create two tests, one to check if a valid user can see this salute response and the other to check if it's actually forbidden.
#RunWith(SpringRunner.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = SpringSecurityWebAuxTestConfig.class
)
#AutoConfigureMockMvc
public class WebApplicationSecurityTest {
#Autowired
private MockMvc mockMvc;
#Test
#WithUserDetails("manager#company.com")
public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
.accept(MediaType.ALL))
.andExpect(status().isOk())
.andExpect(content().string(containsString("manager#company.com")));
}
#Test
#WithUserDetails("user#company.com")
public void givenBasicUser_whenGetFooSalute_thenForbidden() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
.accept(MediaType.ALL))
.andExpect(status().isForbidden());
}
}
As you see we imported SpringSecurityWebAuxTestConfig to provide our users for testing. Each one used on its corresponding test case just by using a straightforward annotation, reducing code and complexity.
Better use #WithMockUser for simpler Role Based Security
As you see #WithUserDetails has all the flexibility you need for most of your applications. It allows you to use custom users with any GrantedAuthority, like roles or permissions. But if you are just working with roles, testing can be even easier and you could avoid constructing a custom UserDetailsService. In such cases, specify a simple combination of user, password and roles with #WithMockUser.
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
#WithSecurityContext(
factory = WithMockUserSecurityContextFactory.class
)
public #interface WithMockUser {
String value() default "user";
String username() default "";
String[] roles() default {"USER"};
String password() default "password";
}
The annotation defines default values for a very basic user. As in our case the route we are testing just requires that the authenticated user be a manager, we can quit using SpringSecurityWebAuxTestConfig and do this.
#Test
#WithMockUser(roles = "MANAGER")
public void givenManagerUser_whenGetFooSalute_thenOk() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.get("/foo/salute")
.accept(MediaType.ALL))
.andExpect(status().isOk())
.andExpect(content().string(containsString("user")));
}
Notice that now instead of the user manager#company.com we are getting the default provided by #WithMockUser: user; yet it won't matter because what we really care about is his role: ROLE_MANAGER.
Conclusions
As you see with annotations like #WithUserDetails and #WithMockUser we can switch between different authenticated users scenarios without building classes alienated from our architecture just for making simple tests. Its also recommended you to see how #WithSecurityContext works for even more flexibility.
Since 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 {
mockMvc.perform(get("/someApi"))
.andExpect(status().isOk());
}
Remember to add the following dependency to your project
'org.springframework.security:spring-security-test:4.2.3.RELEASE'
It turned out that the SecurityContextPersistenceFilter, which is part of the Spring Security filter chain, always resets my SecurityContext, which I set calling SecurityContextHolder.getContext().setAuthentication(principal) (or by using the .principal(principal) method). This filter sets the SecurityContext in the SecurityContextHolder with a SecurityContext from a SecurityContextRepository OVERWRITING the one I set earlier. The repository is a HttpSessionSecurityContextRepository by default. The HttpSessionSecurityContextRepository inspects the given HttpRequest and tries to access the corresponding HttpSession. If it exists, it will try to read the SecurityContext from the HttpSession. If this fails, the repository generates an empty SecurityContext.
Thus, my solution is to pass a HttpSession along with the request, which holds the SecurityContext:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class Test extends WebappTestEnvironment {
public static class MockSecurityContext implements SecurityContext {
private static final long serialVersionUID = -1386535243513362694L;
private Authentication authentication;
public MockSecurityContext(Authentication authentication) {
this.authentication = authentication;
}
#Override
public Authentication getAuthentication() {
return this.authentication;
}
#Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}
#Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
MockHttpSession session = new MockHttpSession();
session.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
new MockSecurityContext(principal));
super.mockMvc
.perform(
get("/api/v1/resource/test")
.session(session))
.andExpect(status().isOk());
}
}
Add in pom.xml:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.0.RC2</version>
</dependency>
and use org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors for authorization request.
See the sample usage at https://github.com/rwinch/spring-security-test-blog
(https://jira.spring.io/browse/SEC-2592).
Update:
4.0.0.RC2 works for spring-security 3.x.
For spring-security 4 spring-security-test become part of spring-security (http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test, version is the same).
Setting Up is changed: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
Sample for basic-authentication: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication.
Here is an example for those who want to Test Spring MockMvc Security Config using Base64 basic authentication.
String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("<username>:<password>").getBytes()));
this.mockMvc.perform(get("</get/url>").header("Authorization", basicDigestHeaderValue).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk());
Maven Dependency
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.3</version>
</dependency>
Short answer:
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private Filter springSecurityFilterChain;
#Before
public void setUp() throws Exception {
final MockHttpServletRequestBuilder defaultRequestBuilder = get("/dummy-path");
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.defaultRequest(defaultRequestBuilder)
.alwaysDo(result -> setSessionBackOnRequestBuilder(defaultRequestBuilder, result.getRequest()))
.apply(springSecurity(springSecurityFilterChain))
.build();
}
private MockHttpServletRequest setSessionBackOnRequestBuilder(final MockHttpServletRequestBuilder requestBuilder,
final MockHttpServletRequest request) {
requestBuilder.session((MockHttpSession) request.getSession());
return request;
}
After perform formLogin from spring security test each of your requests will be automatically called as logged in user.
Long answer:
Check this solution (the answer is for spring 4): How to login a user with spring 3.2 new mvc testing
Options to avoid using SecurityContextHolder in tests:
Option 1: use mocks - I mean mock SecurityContextHolder using some mock library - EasyMock for example
Option 2: wrap call SecurityContextHolder.get... in your code in some service - for example in SecurityServiceImpl with method getCurrentPrincipal that implements SecurityService interface and then in your tests you can simply create mock implementation of this interface that returns the desired principal without access to SecurityContextHolder.
Pretty Late answer though. But This has worked for me , and could be useful.
While Using Spring Security ans mockMvc, all you need to is use #WithMockUser annotation like others are mentioned.
Spring security also provides another annotation called #WithAnonymousUser for testing unauthenticated requests. However you should be careful here. You would be expecting 401, but I got 403 Forbidden Error by default. In actual scenarios, when you are running actual service, It is redirected and you end up getting the correct 401 response code.Use this annotation for anonymous requests.
You may also think of ommitting the annotaions and simply keep it unauthorized. But this usually raises the correct exceptions(like AuthenticationException), but you will get correct status code if it is handled correctly(If you are using custom handler). I used to get 500 for this. So look for the exceptions raised in the debugger, and check if it is handled rightly and returns the correct status code.
Create a class TestUserDetailsImpl on your test package:
#Service
#Primary
#Profile("test")
public class TestUserDetailsImpl implements UserDetailsService {
public static final String API_USER = "apiuser#example.com";
private User getAdminUser() {
User user = new User();
user.setUsername(API_USER);
SimpleGrantedAuthority role = new SimpleGrantedAuthority("ROLE_API_USER");
user.setAuthorities(Collections.singletonList(role));
return user;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
if (Objects.equals(username, ADMIN_USERNAME))
return getAdminUser();
throw new UsernameNotFoundException(username);
}
}
Rest endpoint:
#GetMapping("/invoice")
#Secured("ROLE_API_USER")
public Page<InvoiceDTO> getInvoices(){
...
}
Test endpoint:
#Test
#WithUserDetails("apiuser#example.com")
public void testApi() throws Exception {
...
}
When using MockMvcBuilders.webAppContextSetup(wac).addFilters(...) than springSecurityFilterChain (more specifically SecurityContextPersistenceFilter) will take over and will remove the SecurityContext prepared by #WithMockUser (pretty silly); this happens because SecurityContextPersistenceFilter tries to "restore" the SecurityContext from the HttpSession where finds none. Well, use this simple AutoStoreSecurityContextHttpFilter defined below which will take care of putting #WithMockUser's preppared SecurityContext into the HttpSession such that later SecurityContextPersistenceFilter will be able to find it.
#ContextConfiguration(...) // the issue doesn't occur when using #SpringBootTest
public class SomeTest {
#Autowired
private Filter springSecurityFilterChain;
private MockMvc mockMvc;
#BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.addFilters(new AutoStoreSecurityContextHttpFilter(), springSecurityFilterChain).build();
}
#WithMockUser
#Test
void allowAccessToAuthenticated() {
...
}
}
// don't use this Filter in production because it's only intended for tests, to solve the
// #WithMockUser & springSecurityFilterChain (more specifically SecurityContextPersistenceFilter) "misunderstandings"
public class AutoStoreSecurityContextHttpFilter extends HttpFilter {
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
req.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
super.doFilter(req, res, chain);
}
}

Resources