Spring Security + Spring-Boot Testing Controller - spring

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

Related

Mockito when statement not triggering during Spring Boot REST Controller Test

I've written a typical three layer Spring Boot REST API and am building out the tests for it. The API itself works fine but I am running into issues getting the controller tests to work. The body that's being returned is empty because the object the controller layer is getting back is null. Here are the main dependencies in play.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
I've mocked out the service layer but the when statement in the test doesn't seem to be firing as I'd expect.
Here's the test itself:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureMockMvc
public class VehicleControllerTest {
#MockBean
VehicleServiceImpl vService;
#Mock
HttpServletRequest mockRequest;
#Mock
Principal mockPrincipal;
#Autowired
MockMvc mockMvc;
Vehicle vehicle1;
#BeforeEach
public void setUp() throws ItemNotFoundException {
vehicle1 = new Vehicle();
vehicle1.setVin("5YJ3E1EA5KF328931");
vehicle1.setColor("black");
vehicle1.setDisplayName("Black Car");
vehicle1.setId(1L);
}
#Test
#WithMockUser("USER")
public void findVehicleByIdSuccess() throws Exception {
//Given **I think the problem is here***
when(vService.findVehicleById(any(),any(),any())).thenReturn(vehicle1);
//When
this.mockMvc.perform(get("/vehicles/1")).andDo(print())
//Then
.andExpect(status().isOk());
}
}
Here's the corresponding controller method:
#Secured("ROLE_USER")
public class VehicleController {
#JsonView(VehicleView.summary.class)
#GetMapping("/vehicles/{id}")
public Vehicle findVehicleById(#PathVariable Long id, Principal principal,
HttpServletRequest request) throws ItemNotFoundException {
log.info("In controller " +LogFormat.urlLogFormat(request,principal.getName()));
return vehicleService.findVehicleById(id,principal, request);
}
Here's the MockHTTPServletResponse. It has a status of 200 but the body is empty
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
For reference here's the service method that I'm trying to Mock
#Override
public Vehicle findVehicleById(Long id, Principal principal, HttpServletRequest request) throws ItemNotFoundException {
Optional<Vehicle> vehicle = vehicleRepository.findByIdAndUserId(id,principal.getName());
if (vehicle.isPresent()){
return vehicle.get();
} else {
throw new ItemNotFoundException(id,"vehicle");
}
}
I've tried different versions of Springboot but that hasn't helped. I had started off using 2.2.4 but I figured I would try the 2.1.X train since it has been around longer. I can confirm the correct method in the controller is being called because of the log output I'm getting.
Try replacing the following line:
when(vService.findVehicleById(any(),any(),any())).thenReturn(vehicle1);
with this (explicitity provide the class in any()):
when(vService.findVehicleById(any(Long.class),any(Principal.class),any(HttpServletRequest.class))).thenReturn(vehicle1);
Ignore the following part, as #M. Deinum pointed out that #MockBean does inject the mock object so this is irrelevant.
You did mock your service object but you didn't inject it into your controller.
#InjectMocks
private VehicleController vehicleController = new VehicleController();
Initialize these mock objects using MockitoAnnotations.initMocks(this) and instead of autowiring MockMvc object try to pass your controller in it like this:
#BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(vehicleController).build();

SpringBoot2 + Webflux - WebTestClient always returns “401 UNAUTHORIZED”

I am trying to write some test using WebTestClient under Springboot 2.1.8 and Junit5
It's always returning < 401 UNAUTHORIZED Unauthorized, but actually it didn't go to the controller or service layer at all. It may related to spring security, just my guess.
The project was generated using JHipster. Here is the build.gradle
-----------------UimApiServiceImplTest.java-------------------
...
#ExtendWith(SpringExtension.class)
#WebFluxTest(controllers = UserGuidController.class)
#ContextConfiguration(classes = {UserGuidController.class, UimApiServiceImpl.class})
public class UimApiServiceImplTest {
#Autowired
private WebTestClient webTestClient;
#Test
public void testGetGuidByEmail() {
webTestClient.get()
.uri("/uimapi/getguid/{email}", "someone#xxxxx.com")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk();
}
}
--------------------UserGuidController.java--------------------
...
#RestController
#RequestMapping("/uimapi")
public class UserGuidController {
#Autowired
private UimApiServiceImpl uimApiService;
private static final Logger logger = LoggerFactory.getLogger(UserGuidController.class);
#GetMapping("/getguid/{email}")
public String getUserGuid(#PathVariable String email) {
return uimApiService.getUserGuid(email);
}
}
For webflux, you can disable SecurityAutoconfiguration by excluding the ReactiveSecurityAutoConfiguration class like this:
#WebFluxTest(controllers = YourController.class,excludeAutoConfiguration = {ReactiveSecurityAutoConfiguration.class}))
You've implementation "org.springframework.boot:spring-boot-starter-security" in your gradle dependencies. Spring boot automatically enables security on all endpoints by default when this dependency is found in classpath.
You can choose to disable this default configuration by updating your main class:
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class })
This would disable security on all endpoints.
If you want to have control of which endpoints to remove security from, you can do that using the below code with updates matching your requirements:
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
return http.build();
}
}

Mocking a Keycloak token for testing a Spring controller

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
}

How to test spring controller handler response

I have spring controller handler. I have to test that handle by using Junit test case. I am new to Juint so not able to test Junit. I want to run this in eclipse.
spring handler:
#Controller
#RequestMapping("/core")
public class HelloController {
#RequestMapping(value = "/getEntityType", method = RequestMethod.GET)
public ResponseEntity<List<MyEnum >> getEntityType(HttpServletRequest
request, HttpServletResponse response) {
return new ResponseEntity<List<MyEnum >>(Arrays.asList(MyEnum.values()), HttpStatus.OK);
}
Enum Class:
public enum MyEnum {
FIRST, SECOND, THIRD;
}
TestCase:
#Test
public void testToFindEnumTypes() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "core/getEntityType");
MockHttpServletResponse response = new MockHttpServletResponse();
hello.Controller.getEntityType(request, response);
Assert.assertNotNull(getResponseJSON(response));
}
Please tell me how to Run Junit Test case for that handler. I am new to Junit testing.
From Eclipse, there should be a green run button that allows you to run JUnit tests.
Eclipse Help has this really good article explaining how to:
https://help.eclipse.org/neon/index.jsptopic=%2Forg.eclipse.jdt.doc.user%2FgettingStarted%2Fqs-junit.htm
Also, if running outside your Eclipse, and doing it in a maven build, you need to add junit dependency to your maven pom.xml file:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Then run maven test command as follows:
mvn clean test
Also, there a few syntax errors in your posted code. It should be like something like this:
public class HelloControllerTest {
private HelloController helloController = new HelloController();
#Test
public void testToFindEnumTypes() throws Exception {
// setup
MockHttpServletRequest request = new MockHttpServletRequest("GET", "core/getEntityType");
MockHttpServletResponse response = new MockHttpServletResponse();
// execution
ResponseEntity actualResponse = helloController.getEntityType(request, response);
// verify
assertNotNull(actualResponse);
assertEquals(HttpStatus.OK, actualResponse.getStatusCode());
List<MyEnum> myEnumList = (List<MyEnum>) actualResponse.getBody();
assertTrue(myEnumList.contains(MyEnum.FIRST));
assertTrue(myEnumList.contains(MyEnum.SECOND));
assertTrue(myEnumList.contains(MyEnum.THIRD));
}
Ideally, you should also verify correctly all the return values like i done in the example above.

How to verify web responses when using Spring and test-mvc

A question about how to use test-mvc for unit testing.
I have a simple controller:
#Controller
#RequestMapping("/users")
public class UserController {
private UserService business;
#Autowired
public UserController(UserService bus)
{
business = bus;
}
#RequestMapping(value="{id}", method = RequestMethod.GET)
public #ResponseBody User getUserById(#PathVariable String id) throws ItemNotFoundException{
return business.GetUserById(id);
}
(( My idea is to keep the controllers so thin as possible.))
To test this controller I am trying to do something like this.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:mvc-dispatcher-servlet.xml"})
public class UserControllerTest extends ControllerTestBase {
UserService mockedService;
#Before
public void Setup()
{
MockitoAnnotations.initMocks( this );
mockedService = mock(UserService.class);
}
#Test
public void ReturnUserById() throws Exception{
User user = new User();
user.setName("Lasse");
stub(mockedService.GetUserById("lasse")).toReturn(user);
MockMvcBuilders.standaloneSetup(new UserController(mockedService)).build()
.perform(get("/users/lasse"))
.andExpect(status().isOk())
.andExpect(?????????????????????????????);
}
My intention is to check that proper json code is returned,,,,,,
I am not a pro,,, so I have not found a way to replace ??????????????????????? with code to do verify the returned string but I am certain that there must be a elegant way to do this
Can anyone fill me in?
//lg
content().string(containsString("some part of the string"))
assuming that you have this import:
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
Update: Adding jsonPath also based on your comments:
You can add a dependency to json-path, the 1.0.M1 seems to depend on an much older version of json-path though:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>0.5.5</version>
<scope>test</scope>
</dependency>
With this your test can look like this:
.andExpect(jsonPath("$.persons[0].first").value("firstName"));

Resources