Mocking OAuth2 client with WebTestClient for servlet applications results in null httpHandlerBuilder - spring

My Spring Boot application acts as an OAuth2 client by using the spring-boot-starter-oauth2-client dependency.
Now I'd like to write an integration test (#SpringBootTest) to verify the behavior of a REST endpoint secured by OAuth2. The Testing OAuth 2.0 Clients documentation describes that it is possible to use mutateWith(mockOAuth2Client()) to mock a login via OAuth2.
public class UserIT {
#Autowired
private WebTestClient webTestClient;
#Test
void test() {
webTestClient
.mutateWith(mockOAuth2Client("keycloak"))
.get()
.uri("/api/user/1345")
.exchange()
.expectStatus().isOk();
}
}
However, the test fails with the following message:
java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filters(java.util.function.Consumer)" because "httpHandlerBuilder" is null
at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$OAuth2ClientMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:1113)
at org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder.apply(DefaultWebTestClientBuilder.java:265)
at org.springframework.test.web.reactive.server.DefaultWebTestClient.mutateWith(DefaultWebTestClient.java:167)
As far as I have understood it, this WebTestClient setup is only suitable for "Reactive Applications" whereas my application is a "Servlet Application". Unfortunately, I cannot find the necessary information how to mock this OAuth2 client for a servlet application.

I was able to run your exact #Autowired and #Test code successfully with the following test configuration:
#Configuration
#ComponentScan
public class TestConfig {
#Bean
public WebTestClient webTestClient(ApplicationContext applicationContext) {
return WebTestClient.bindToApplicationContext(applicationContext).build();
}
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
return http.build();
}
}

Related

How to correctly setup Spring Boot 2.2 REST full integration test with JUnit5

I have simple controller, facade, set of services and repository for my H2 DB for Customer entity. It works fine but I need an E2E integration test with whole application context. And with JUnit5.
I've tried many setups, many times got errors like found multiple declarations of #BootstrapWith for test. Finally I made it work, but there is an issue with Spring JPA CustomerRepository which is an interface. I've never tried to use it like this so I have no experiance with it. But simply speaking, it won't create any bean of this type since it's just an interface. As the spring boot application, it works this way, but in the integration test it won't.
The error is still the same: Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [xxx.CustomerRepository].
This is my test setup:
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc
#ContextConfiguration(classes = {
CustomerController.class,
CustomerFacade.class,
CustomerTerminationService.class,
CustomerRepository.class
})
#WebMvcTest(CustomerController.class)
#Import(CustomerRepository.class)
class CustomerFullIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Test
void getCustomer() throws Exception {
mockMvc.perform(MockMvcRequestBuilders
.get("/customer/1")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.lastName").exists())
.andExpect(jsonPath("$.products").isNotEmpty());
}
}
This is my repository:
public interface CustomerRepository extends JpaRepository<Customer, Long> {}
This is my Application:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ProfApplication.class, args);
}
}
Controller, facade, services are irrelevant.
Can you tell me, what exactly am I doing wrong?

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

How to include custom security interceptor in spring boot test

I want to do some end-to-end test for spring boot rest-api application. To achieve this im using spring mock mvc. But i can't get the 200 response because the rest api is using custom security interceptor to validate the token in request. Instead i keep getting 401 as a response. How to include this token validation in my test?
I've tried several configuration by including #ContextConfiguration(classes = {WebMvcConfig.class}) in my test class. WebMvcConfig is configuration class to register the interceptor.
This is my test file
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#SpringBootTest(classes = VeripalServiceApplication.class)
#TestPropertySource(locations="classpath:test.properties")
#Transactional
public class VeripalTextConfigurationTest {
#Autowired
private MockMvc mockMvc;
#Test
public void happpyPath_thenReturns200() throws Exception {
String jsonBody = "some json body";
String endPoint = "/end_point_to_my_api";
HttpHeaders headers = new HttpHeaders();
headers.add("token", "this_is_my_token");
headers.setContentType(aplication/json);
/** Hit the API */
mockMvc.perform(post(endPoint)
.headers(httpHeaders)
.content(jsonBody)
)
.andExpect(status().isOk()).andDo(print());
}
}
And this is the #Configuration
#Configuration
#EnableScheduling
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private ConsumerService consumerService;
#Autowired
private EndpointService endpointService;
#Autowired
private ConsumerConfigurationService consumerConfigurationService;
#Autowired
private AccessLimitService accessLimitService;
#Autowired
private ConfigurationHistoryService configurationHistoryService;
#Autowired
private LimitCarryOverService limitCarryOverService;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Interceptor(consumerService, endpointService, consumerConfigurationService, accessLimitService, configurationHistoryService, limitCarryOverService));
}
}
And this is my Interceptor class
public class Interceptor implements HandlerInterceptor {
// some code here ...
}
you need to have a clear picture of request life-cycle in Servlet API and Spring Security framework.
This article might help you to understand this flow http://blog.florian-hopf.de/2017/08/spring-security.html
So, I'm pretty sure, you have an issue in authentication filters, thus you can resolve it in couple ways:
Disable security, for example by using #AutoConfigureMockMvc(secure = false)
Or you need to mock some places (AuthenticationProvider, UserDetailsService, etc) where you can provide Authentication object
Or, it also might help, try to play with #WithMockUser
.
Related posts:
Spring Test & Security: How to mock authentication?
V2: use IoC + Mockito, e.g. stub it for unit tests. I don't see how your code are written, so I believe a snippet below might help you.
// #Import({MyAuthCustomInterceptor.class}) // eq to #Component/#Service to create a bean
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
MyAuthCustomInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor);
}
}
public class VeripalTextConfigurationTest {
#MockBean
MyAuthCustomInterceptor interceptor;
#SetUp
public void setup(){
Mockito.when(interceptor.preHandle(...)).thenReturn(true);
}
}

Spring Boot 2, Spring Security 5 and #WithMockUser

Since I migrated to Spring Boot 2.0.5 from 1.x, with no mean to disable security, I can't get test roles to work on mock MVC tests :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ApplicationsControllerShould {
...
#Autowired
private MockMvc mockMvc;
private ObjectMapper mapper = new ObjectMapper();
#Test
#WithMockUser(roles = "ADMIN")
public void handle_CRUD_for_applications() throws Exception {
Application app = Application.builder()
.code(APP_CODE).name(APP_NAME)
.build();
mockMvc.perform(post("/applications")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(app)))
.andExpect(authenticated())
.andExpect(status().isOk()); // failure 403!
...
My controller endpoint isn't even protected!
#RestController
#RequestMapping("/applications")
public class ApplicationsController {
...
#PostMapping
public Application addApplication(#RequestBody Application application) {
Assert.isTrue(!applicationsDao.existsById(application.getCode()), "Application code already exists: " + application.getCode());
return applicationsDao.save(application);
}
}
So I have in the test a session (#authenticated fails when #WithMockUser is commented out) and a role by the way (ROLE_ADMIN is visible in traces) but my request is being rejected and I don't understand what I did wrong.
Thx for any idea!
Ok... the good old CSRF stuff, then...
logging.level.org.springframework.security=DEBUG
2018-10-02 10:11:41.285 DEBUG 12992 --- [ main] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost/applications/foo
Application app = Application.builder()
.code(APP_CODE).name(APP_NAME)
.build();
mockMvc.perform(post("/applications").with(csrf()) // oups...
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(app)))
.andExpect(authenticated())
.andExpect(status().isOk()); // there we go!

#WebMvcTest for SOAP?

Is the Spring Boot annotation #WebMvcTest only intended for sliced RestController tests or should SOAP Endpoints be testable with it too?
When I setup my test and run it, I only get a 404 response as if the endpoint wasn't there so I assume it isn't part of the WebMvc slice.
#RunWith(SpringRunner.class)
#WebMvcTest(value = IdServerPortTypeV10.class)
#Import({SecurityConfig.class, ModelMapperConfig.class, WebServiceConfig.class, ControllerTestBeans.class})
public class AccountEndpointTests {
#Autowired
IdServerPortTypeV10 soapEndpoint;
...
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity())
.build();
}
#Test
#WithMockUser(roles = VALID_ROLE)
public void getAccountTest_Success() throws Exception {
mockMvc.perform(
post("/soap/idserver/1.0")
.accept(MediaType.TEXT_XML_VALUE)
.headers(SoapTestUtility.getHeader(SERVICE.getNamespaceURI(), "getAccount"))
.content(SoapTestUtility.getAccountXml())
).andDo(print())
.andExpect(status().isOk());
}
}
The endpoint is enabled in WebServiceConfig.class in which #EnableWs is set.
#WebMvcTest is, as the name implies, only for Spring MVC related tests.
Spring's SOAP support is from the Spring Web Services project which has its own integration testing support.

Resources