SpringBoot2 + Webflux - WebTestClient always returns “401 UNAUTHORIZED” - spring

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

Related

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

Testcontainers and Spring Boot 1.5

We are still using Spring Boot 1.5.x and we want to start using TestContainers. However, all examples are with Spring boot 2.x which is using TestPropertyValues class only available in 2.x. Is it even possible to apply new property values to the configurable context in 1.5.x?
This is the code working in 2.x:
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.class})
public class UserRepositoryTCIntegrationTest extends UserRepositoryCommonIntegrationTests {
#ClassRule
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11.1")
.withDatabaseName("integration-tests-db")
.withUsername("sa")
.withPassword("sa");
static class Initializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(),
"spring.datasource.username=" + postgreSQLContainer.getUsername(),
"spring.datasource.password=" + postgreSQLContainer.getPassword()
).applyTo(configurableApplicationContext.getEnvironment());
}
}
}
good question :). You have different options to setup your testcontext with Spring Boot 1.5 + TestContainers. Instead of using an indirect way by setting the datasource-properties with dynamic values (like in your example code), you can use the following option:
Provide DataSource Bean via #TestConfiguration
#RunWith(SpringRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class YourRepositoryIntTest {
#Autowired
private YourRepository sut;
#Test
public void testMethod() {
// Given
String expectedId = "SOMEID";
// When
Entity entity = sut.testMethod();
// Then
Assertions.assertThat(entity.getId()).isEqualTo(expectedId);
}
#TestConfiguration
public static class Config {
#Bean
public MySQLContainer testContainer() {
MySQLContainer container = new MySQLContainer();
container.start();
return container;
}
#Bean
#Primary
public DataSource dataSource(MySQLContainer container) {
return DataSourceBuilder.create()
.url(container.getJdbcUrl())
.username(container.getUsername())
.password(container.getPassword())
.driverClassName(container.getDriverClassName())
.build();
}
}
}
Database containers can be launched by simply using a JDBC URL scheme:
application.properties
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.url=jdbc:tc:postgresql:11://localhost/test
Note:
Testcontainers needs to be on your application's classpath at runtime for this to work

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

Configure SpringSecurity 4 with application.properties

I'm trying to add HTTP basic authentication into my springboot microservice.
When I use the "code" way described in Spring doc it works perfectly:
#RestController
public class Ping
{
#GetMapping("/ping")
public String ping()
{
return "pong";
}
}
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception
{
auth.inMemoryAuthentication()
.withUser("user").password("password")
.roles("USER");
}
}
But, if I want to use the properties way it seems that it's not possible or I'm doing something wrong:
security.basic.enabled = true
security.basic.path = /**
security.basic.realm = Spring
security.user.name = user
security.user.password = xxx
security.user.role = USER
security.sessions = always
Using this solution, only the resource http://localhost/env is secured, and /ping is not. It is possible to use only application.properties to configure a "simple" basic authentication to all the resources?

Spring session with redis - how to mock it in integration tests?

So, Im using spring session in my project.
How to write integration tests in project using it? Should I mock something for spring session internals? Or any reasonable way to use embedded redis?
I saw there was some #EnableEmbeddedRedis annotation in past, but seems it was removed: https://github.com/spring-projects/spring-session/issues/248
//edit
Ive tried to pass MockHttpSession to
mockMvc.perform(post("/register").session(mockHttpSession)
but spring tries and fails to connect to redis anyway.
You can create your own connectionfactory and the redisserializer. Then spring boot won't create their default beans.
Example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class ApplicationTest
{
#Test
public void contextLoads()
{
}
#EnableRedisHttpSession
#Configuration
static class Config
{
#Bean
#SuppressWarnings("unchecked")
public RedisSerializer<Object> defaultRedisSerializer()
{
return Mockito.mock(RedisSerializer.class);
}
#Bean
public RedisConnectionFactory connectionFactory()
{
RedisConnectionFactory factory = Mockito.mock(RedisConnectionFactory.class);
RedisConnection connection = Mockito.mock(RedisConnection.class);
Mockito.when(factory.getConnection()).thenReturn(connection);
return factory;
}
}
}
I would not suggest to use mockHttpSession, as it will bypass the integration with spring-session library in the tests. I would
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ExampleControllerV2SpringSessionTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private SessionRepository sessionRepository;
#Autowired
private SessionRepositoryFilter sessionRepositoryFilter;
//this is needed to test spring-session specific features
private MockMvc mockMvcWithSpringSession;
#Before
public void setup() throws URISyntaxException {
this.mockMvcWithSpringSession = MockMvcBuilders
.webAppContextSetup(wac)
.addFilter(sessionRepositoryFilter)
.build();
}
}
Knowing that it's hard to always have redis instance ready during the test, I would suggest you to use this property spring.session.store-type=hash_map for your test cases.
ok, ive just disabled redis by using profiles in my integration tests
#ActiveProfiles("integrationtests")
class RegisterControllerTest extends Specification {...
and extracting #EnableRedisHttpSession to its own class:
#Configuration
#EnableRedisHttpSession
#Profile("!integrationtests")
public class RedisConfig {
}
Its more like workaround than solution, but I dont need to test anything inside session anyway.
This work:
In your HttpSessionConfig define profiles where the configuration is active:
#EnableRedisHttpSession
#Profile("prod")
class HttpSessionConfig {
}
In application-prod.properties add:
#REDIS SERVER
spring.redis.host=xxxxxxxx
And then in your application.properties of test add:
#REDIS SERVER
spring.data.redis.repositories.enabled=false
spring.session.store-type=none

Resources