How to correctly implement CORS in WebFlux? - java

I want to create a simple Spring Boot/Webflux server to serve as REST API. I'm trying to test it currently locally. The Webflux server is running on port 8080 and I have another server serving the html (React.js) running on port 3000. I want to make a CORS request from the website to the server. To that end I created a Java class with a method addCorsMappings and added #Configuration annotation. I was wondering if adding an annotation magically make Spring aware of the annotated class but according to this article I also need to add org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.mypackage.CORSHandler property in spring.factories file which I did. However I still see that addCorsMappings is not even called (I don't see log message). Being new to Spring Boot is there any other configuration I'm missing?
This is my main class:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
public class Server {
private static final Logger log = LogManager.getLogger(Server.class);
public static final String HOST = "localhost";
public static final int PORT = 8080;
public static void main(String[] args) throws Exception {
Server server = new Server();
server.startReactorServer();
System.out.println("Press ENTER to exit.");
System.in.read();
}
public RouterFunction<ServerResponse> routingFunction() {
PersonRepository repository = new DummyPersonRepository();
PersonHandler handler = new PersonHandler(repository);
return nest(path("/person"),
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getPerson)
.andRoute(method(HttpMethod.GET), handler::listPeople)
).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson));
}
public void startReactorServer() {
RouterFunction<ServerResponse> route = routingFunction().filter((request, next) -> {
log.warn(request.path());
if (request.path().contains("person")) {
log.warn("calling next()");
return next.handle(request);
} else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}
}
and this is my CORSHandler class:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
#Configuration
public class CORSHandler implements WebFluxConfigurer {
private static final Logger log = LogManager.getLogger(CORSHandler.class);
#Override
public void addCorsMappings(CorsRegistry registry) {
log.warn("from addCorsMappings!!");
registry.addMapping("*")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("Content-Type", "Access-Control-Allow-Headers", "Authorization",
"X-Requested-With", "mode")
.allowCredentials(true);
}
}

The solution I currently found is that The class which contains configuration methods needs to be given as a parameter to AnnotationConfigApplicationContext context. The context needs to be given as a parameter to WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route)).applicationContext(context). So the main class would look like this:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import reactor.ipc.netty.http.server.HttpServer;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import static org.springframework.web.reactive.function.server.RequestPredicates.method;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
#SpringBootApplication
public class Server {
private static final Logger log = LogManager.getLogger(Server.class);
public static final String HOST = "localhost";
public static final int PORT = 8080;
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(CorsConfiguration.class);
Server server = new Server();
server.startReactorServer(ctx);
System.out.println("Press ENTER to exit.");
System.in.read();
}
public RouterFunction<ServerResponse> routingFunction() {
PersonRepository repository = new DummyPersonRepository();
PersonHandler handler = new PersonHandler(repository);
return nest(path("/person"),
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getPerson)
.andRoute(method(HttpMethod.GET), handler::listPeople)
).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson));
}
public void startReactorServer(AnnotationConfigApplicationContext ctx) {
RouterFunction<ServerResponse> route = this.routingFunction().filter((request, next) -> {
log.warn(request.path());
if (request.path().contains("person")) {
log.warn("calling next()");
return next.handle(request);
} else {
return ServerResponse.status(UNAUTHORIZED).build();
}
});
HttpHandler httpHandler = WebHttpHandlerBuilder.webHandler(RouterFunctions.toWebHandler(route))
.applicationContext(ctx).build();
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}
}
and CorsConfiguration class would look like this:
package com.mypackage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Configuration
#EnableWebFlux
public class CorsConfiguration {
private static final Logger log = LogManager.getLogger(CorsConfiguration.class);
private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN, mode";
private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
private static final String ALLOWED_ORIGIN = "*";
private static final String MAX_AGE = "3600";
#Bean
public WebFilter corsFilter() {
log.warn("from CorsConfiguration!!!");
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
log.warn("after ServerHttpRequest");
if (CorsUtils.isCorsRequest(request)) {
log.warn("inside isCorsRequest");
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.add("Access-Control-Max-Age", MAX_AGE);
headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
In this code corsFilter is called and sets the CORS headers but routing doesn't work however (404 status is returned).

Related

Unit testing - RestTemplate mock keeps retuning null

So Im writing unit test a class that does bankId authentication and the unit test looks like this.
package se.kt.client;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpEntity;
import se.kt.common.vo.PublicApplicationForm;
import se.kt.models.BankIdAuthRequest;
import se.kt.models.BankIdAuthResponse;
import se.kt.models.BankIdCollectResponse;
import static org.mockito.ArgumentMatchers.any;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BankIdClientTest {
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
#InjectMocks
private BankIdClient bankIdClient;
#BeforeEach
public void setUp() {
}
#Test
public void testBankIdAuthentication_success() throws InterruptedException {
PublicApplicationForm form = new PublicApplicationForm();
form.setSsn("123456-7890");
form.setIp_address("123.123.123.123");
BankIdAuthRequest authRequest = bankIdClient.authRequestFromApplicationForm(form, "123");
BankIdAuthResponse authResponse = new BankIdAuthResponse();
authResponse.setOrderRef("123456");
BankIdCollectResponse collectResponse = new BankIdCollectResponse();
collectResponse.setStatus("completed");
Mockito.when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), any()))
.thenReturn(ResponseEntity.ok(authResponse));
Mockito.when(restTemplate.getForEntity(anyString(), any()))
.thenReturn(ResponseEntity.ok(collectResponse));
assertTrue(bankIdClient.bankIdAuthentication(authRequest));
}
}
And the class Im testing looks like this:
package se.kt.client;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import se.kt.common.domain.AbstractApplicationForm;
import se.kt.common.vo.PublicApplicationForm;
import se.kt.models.BankIdAuthRequest;
import se.kt.models.BankIdAuthResponse;
import se.kt.models.BankIdCollectResponse;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.AssertTrue;
import java.util.Objects;
#Component
public class BankIdClient {
private static final Logger log = LoggerFactory.getLogger(BankIdClient.class);
private final RestTemplate customRestTemplate;
private static final String CONTENT_TYPE = "Content-Type";
#Value("${BankId.AuthUrl}")
private String bankIdAuthUrl;
#Value("${BankId.CollectUrl}")
private String bankIdCollectUrl;
#Value("${BankId.SecretKey}")
private String bankIdSecretKey;
public BankIdClient(RestTemplate customRestTemplate) {
this.customRestTemplate = customRestTemplate;
}
public BankIdAuthRequest authRequestFromApplicationForm(PublicApplicationForm form, String jobId) {
BankIdAuthRequest bankIdAuthRequest = new BankIdAuthRequest();
bankIdAuthRequest.setPno(form.getSsn());
bankIdAuthRequest.setIpAddress(form.getIp_address());
bankIdAuthRequest.setRefID(jobId);
bankIdAuthRequest.setSecretKey(bankIdSecretKey);
bankIdAuthRequest.setAvsikt("Kt application");
return bankIdAuthRequest;
}
public boolean bankIdAuthentication(BankIdAuthRequest bankIdAuthRequest) throws InterruptedException {
//Setup header and body for request.
HttpHeaders headers = new HttpHeaders();
headers.add(CONTENT_TYPE, MediaType.APPLICATION_JSON.toString());
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
try {
String bankIdAuthFormJson = ow.writeValueAsString(bankIdAuthRequest);
HttpEntity<String> httpEntity = new HttpEntity<>(bankIdAuthFormJson, headers);
ResponseEntity<BankIdAuthResponse> authResponse = customRestTemplate.postForEntity(bankIdAuthUrl, httpEntity, BankIdAuthResponse.class);
bankIdCollectUrl += Objects.requireNonNull(authResponse.getBody()).getOrderRef();
ResponseEntity<BankIdCollectResponse> collectResponse;
do {
collectResponse = customRestTemplate.getForEntity(bankIdCollectUrl, BankIdCollectResponse.class);
Thread.sleep(1500);
if (Objects.requireNonNull(collectResponse.getBody()).getStatus().equals("completed"))
return true;
if (Objects.requireNonNull(collectResponse.getBody()).getStatus().equals("failed"))
return false;
} while (Objects.requireNonNull(collectResponse.getBody()).getStatus().equals("progress"));
} catch (JsonProcessingException e) {
log.info(e.getMessage());
} catch (NullPointerException e) {
log.info(e.toString());
log.info("BankId API not responding correctly. Check server connection");
}
return false;
}
public void cancelBankIdAuthentication(#Value("${BankId.CancelUrl}") String bankIdCancelUrl) {
customRestTemplate.postForEntity(bankIdCancelUrl, null, String.class);
}
}
Now for some reson this line:
ResponseEntity<BankIdAuthResponse> authResponse = customRestTemplate.postForEntity(bankIdAuthUrl, httpEntity, BankIdAuthResponse.class);
keeps producing the result authResponse = null indicating that
Mockito.when(restTemplate.postForEntity(anyString(), any(HttpEntity.class), any()))
.thenReturn(ResponseEntity.ok(authResponse));
is not working. Now I have been playing around with this for over 3h and i still get the same result. What could i be doing wrong?
#InjectMocks only works with #Mock-annotated fields, not with fields which get assigned a manually created mock. Therefore you need to change:
private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
#InjectMocks
private BankIdClient bankIdClient;
to
#Mock
private RestTemplate restTemplate;
#InjectMocks
private BankIdClient bankIdClient;
I also recommend reading Why is my class not calling my mocked methods in unit test? which provides additional insights and points out some common mistakes when using Mockito or mocks in general.

Configure SpringBoot Kafka streams unit tests

I have a SpringBoot Kafka streams maven app. I use a spring-boot-starter-parent 2.4.4 for my springboot dependencies and kafka-streams 2.7.0.
I am stuck at running tests with
java.lang.NullPointerException
when trying to load my application configuration from either
resources/application.yml or test/resources/application-test.resources or test/resources/application.yml
I have a Config class with this annotations and getters and setters for fields, which are defined with same name as in the application.yml
package com.acme.rtc.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
#ConfigurationProperties(prefix = "topics")
#Component
#Configuration
public class ConfigProps {
private String MATRIXX_ADJ_EVENT_TOPIC;
private String OUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC;
private String OUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC;
private String EVENTS_NO_MVNO;
public void setMATRIXX_ADJ_EVENT_TOPIC(String MATRIXX_ADJ_EVENT_TOPIC) {
this.MATRIXX_ADJ_EVENT_TOPIC = MATRIXX_ADJ_EVENT_TOPIC;
}
public void setOUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC(String OUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC) {
this.OUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC = OUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC;
}
public void setOUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC(String OUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC) {
this.OUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC = OUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC;
}
public String getEVENTS_NO_MVNO() {
return EVENTS_NO_MVNO;
}
public void setEVENTS_NO_MVNO(String EVENTS_NO_MVNO) {
this.EVENTS_NO_MVNO = EVENTS_NO_MVNO;
}
public String getMATRIXX_ADJ_EVENT_TOPIC() {
return MATRIXX_ADJ_EVENT_TOPIC;
}
public String getOUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC() {
return OUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC;
}
public String getOUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC() {
return OUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC;
}
}
I am doing #Autowire of this class in my test and app class,
#Autowired
ConfigProps cp;
and trying to access fields using cp.getBootstrapServerHost() but this resolves to a NullPointer in my test class. But resolves properly in my application class...
My test class looks like this
package distinct;
import com.acme.rtc.configuration.ConfigProps;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.kafka.streams.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.kafka.config.KafkaStreamsConfiguration;
import com.acme.rtc.configuration.KafkaConfiguration;
import com.acme.rtc.configuration.TopologyConfiguration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.List;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
#ContextConfiguration(classes = TopologyConfiguration.class)
#SpringJUnitConfig
public class TestWithTopologyTestDriver {
private TestInputTopic<String, String> inputTopicWrong;
private TestOutputTopic<String, String> outputTopicWrong;
private TestInputTopic<String, String> inputTopicRight;
private TestOutputTopic<String, String> outputTopicRight;
private TopologyTestDriver topologyTestDriver;
#Autowired
ConfigProps configProps;
#BeforeEach
public void setUp() {
KafkaProperties properties = new KafkaProperties();
properties.setBootstrapServers(singletonList("localhost:9092"));
KafkaStreamsConfiguration config = new KafkaConfiguration(properties).getStreamsConfig();
StreamsBuilder sb = new StreamsBuilder();
Topology topology = new TopologyConfiguration().createTopology(sb);
topologyTestDriver = new TopologyTestDriver(topology, config.asProperties());
inputTopicWrong =
topologyTestDriver.createInputTopic(configProps.getMATRIXX_ADJ_EVENT_TOPIC(), new StringSerializer(),
new StringSerializer());
outputTopicWrong =
topologyTestDriver.createOutputTopic(configProps.getOUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC(), new StringDeserializer(),
new StringDeserializer());
inputTopicRight =
topologyTestDriver.createInputTopic(configProps.getMATRIXX_ADJ_EVENT_TOPIC(), new StringSerializer(),
new StringSerializer());
outputTopicRight =
topologyTestDriver.createOutputTopic(configProps.getOUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC(), new StringDeserializer(),
new StringDeserializer());
}
#AfterEach
public void tearDown() {
topologyTestDriver.close();
}
#Test
void wrongDistinctTopology() {
testTopology(inputTopicWrong, outputTopicWrong);
}}
where TopologyConfiguration is my application and that has this signature
package com.acme.rtc.configuration;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.serialization.Serde;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.common.serialization.Serializer;
import org.apache.kafka.common.serialization.Deserializer;
import org.apache.kafka.streams.Topology;
import org.apache.kafka.streams.kstream.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.kafka.support.serializer.JsonSerializer;
import org.springframework.stereotype.Component;
#Configuration
#ConfigurationProperties(prefix = "topics")
#Component
#RequiredArgsConstructor
public class TopologyConfiguration {
#Autowired
Environment env;
#Autowired
ConfigProps configProps;
private void acmeStreamsTopoloy(StreamsBuilder streamsBuilder) {
Deserializer<JsonNode> jsonDeserializer = new JsonDeserializer();
Serializer<JsonNode> jsonSerializer = new JsonSerializer();
Serde<JsonNode> jsonSerde = Serdes.serdeFrom(jsonSerializer, jsonDeserializer);
System.out.println("ConfigProps.getMattrix: "+configProps.getMATRIXX_ADJ_EVENT_TOPIC());
KStream<String, String> inputStream =
streamsBuilder.stream(configProps.getMATRIXX_ADJ_EVENT_TOPIC(), Consumed.with(Serdes.String(), Serdes.String()));
KStream<String, String>[] branches = inputStream.branch(
(key, value)-> value.contains("KGN"),
(key, value)-> value.contains("LEB"),
(key, value)->true);
branches[0].to(configProps.getOUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC());
branches[1].to(configProps.getOUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC());
branches[2].to(configProps.getEVENTS_NO_MVNO());
}
#Bean
public Topology createTopology(StreamsBuilder streamsBuilder) {
acmeStreamsTopoloy(streamsBuilder);
return streamsBuilder.build();
}
}
My KafkaConfiguration class
package com.acme.rtc.configuration;
import lombok.RequiredArgsConstructor;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.errors.LogAndContinueExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.annotation.KafkaStreamsDefaultConfiguration;
import org.springframework.kafka.config.KafkaStreamsConfiguration;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.kafka.support.serializer.JsonDeserializer;
import org.springframework.kafka.support.serializer.JsonSerializer;
import java.util.HashMap;
import java.util.Map;
#Configuration
#RequiredArgsConstructor
public class KafkaConfiguration {
public static final String APP_ID = "acme-stream-rtc";
private final KafkaProperties kafkaProperties;
#Autowired
#Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
public KafkaStreamsConfiguration getStreamsConfig() {
Map<String, Object> props = new HashMap<>();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, APP_ID);
props.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 2);
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaProperties.getBootstrapServers());
props.put(JsonSerializer.ADD_TYPE_INFO_HEADERS,false);
KafkaStreamsConfiguration streamsConfig = new KafkaStreamsConfiguration(props);
return streamsConfig;
}
}
My application.yml has the right syntax etc.
spring:
kafka:
bootstrap-servers: localhost:9092
json:
value:
default:
type: true
kafka:
streams:
properties:
default:
value:
serde: org.springframework.kafka.support.serializer.JsonSerde
admin:
security:
protocol: SSL
ssl:
trust-store-location: ${TRUSTSTORE_LOCATION}
trust-store-password: ${TRUSTSTORE_PASSWORD}
key-store-location: ${KEYSTORE_LOCATION}
key-store-password: ${KEYSTORE_PASSWORD}
key-password: ${KEY_PASSWORD}
topics:
MATRIXX_ADJ_EVENT_TOPIC: input-matrixx-adj-event
OUTPUT_MNVO_KGN_ADJ_EVENT_TOPIC: output-KGN-adj-event
OUTPUT_MNVO_LEB_ADJ_EVENT_TOPIC: output-LEB-adj-event
EVENTS_NO_MVNO: events-no-mvno-spec
My main class
package com.acme.rtc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.EnableKafkaStreams;
#SpringBootApplication
#EnableKafkaStreams
public class StreamProcessing {
public static void main(String[] args) {
SpringApplication.run(StreamProcessing.class, args);
}
}
I am not sure if I am missing any context when Autowiring my ConfigProps class or if I need further annotations on my test class.
For JUnit4 you need #Runwith(SpringJUnit4ClassRunner.class) alongside the #ContextConfiguration.
For JUnit5, use #SpringJUnitConfig.
For proper loading of properties, though, you need #SpringBootTest.
Boot 2.4 uses JUnit5.
And you should not have #ConfigurationProperties on the test.
EDIT
I just tested it with no problems.
#Configuration
public class Config {
#Bean
String str() {
return "str";
}
}
#ConfigurationProperties(prefix = "foo")
#Component
public class MyProps {
String bar;
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#Override
public String toString() {
return "MyProps [bar=" + this.bar + "]";
}
}
#SpringBootApplication
public class So67078244Application {
public static void main(String[] args) {
SpringApplication.run(So67078244Application.class, args);
}
}
#SpringBootTest
class So67078244ApplicationTests {
#Autowired
MyProps props;
#Test
void contextLoads() {
System.out.println(this.props);
}
}
foo.bar=baz
MyProps [bar=baz]

There's any way to load #ImportResources dynamically by checking the active profile that's running?

I have this class below ... it's responsible for start my application , is there any
maneuver to make #ImportResources annotation dynamic?
For my test covering I would like to load another resource. I am trying to avoid to run my unit test with my parallel configuration, because the results are being intermittent.
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportResource;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.client.RestTemplate;
import br.com.bradesco.ciar.srv.agorainv.infrastructure.LoggingClientHttpRequestInterceptor;
#SpringBootApplication
#ImportResource("spring/*.xml")
public class Application {
#Value("${rest.timeout.read}")
private int readTimeout;
#Value("${rest.timeout.connect}")
private int connectTimeout;
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public HttpClient httpClient() {
try {
return HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
#Override
public boolean isTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {
return true;
}
}).build()).build();
}
catch (final Exception e) {
throw new RuntimeException(e);
}
}
#Bean
public HttpComponentsClientHttpRequestFactory httpRequestFactory() {
final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
requestFactory.setReadTimeout(readTimeout);
requestFactory.setConnectTimeout(connectTimeout);
return requestFactory;
}
#Bean
public RestTemplate restTemplate() {
final RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(httpRequestFactory()));
restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
return restTemplate;
}
#Bean
public LocalValidatorFactoryBean restValidator() {
return new LocalValidatorFactoryBean();
}
}
You could try moving the #ImportResource annotation to a separate configuration class annotated with a #Profile annotation, e.g:
#Profile("!Test")
#Configuration
#ImportResource("spring/*.xml")
class ResourceConfiguration {}

Spring thymeleaf template does not work upon adding CORS configuration

I recently added CORS configuration in my springboot app. Below is the code for this configuration.
package com.turtlemint.verticals.commonverticals;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
/**
* #author praveenkamath
**/
#Configuration
#EnableWebFlux
public class CorsConfiguration implements WebFluxConfigurer {
#Value("${cors.allowed.origins}")
private String[] corsSites;
#Value("${cors.enabled}")
private boolean isCorsEnabled;
#Override
public void addCorsMappings(final CorsRegistry registry) {
if(isCorsEnabled) {
registry.addMapping("/api/device/**").allowedOrigins(corsSites).allowedMethods("*");
registry.addMapping("/api/sehat/**").allowedOrigins(corsSites).allowedMethods("*");
registry.addMapping("/api/dukandaar/**").allowedOrigins(corsSites).allowedMethods("*");
return;
} registry.addMapping("/**");
}
}
My application.properties
### CORS ###
cors.enabled=true
cors.allowed.origins=*
### CORS ###
After adding it, I ran into 2 issues:
A PUT request with nested JSON gives below error
Internal Server Error","message":"Type definition error: [simple type, class com.turtlemint.verticals.commonverticals.common.containers.v1.collections.ReviewPayment]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.turtlemint.verticals.commonverticals.common.containers.v1.collections.ReviewPayment` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: com.turtlemint.verticals.commonverticals.dukandaar.containers.dto.DukandaarCheckoutDTO[\"reviewPayment\"])
ReviewPayment Java class:
package com.turtlemint.verticals.commonverticals.common.containers.v1.collections;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.turtlemint.verticals.commonverticals.common.enums.v1.ReviewPaymentStatusEnum;
import lombok.Data;
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ReviewPayment {
private ReviewPaymentStatusEnum status;
private String rejectionReason;
public ReviewPayment(ReviewPaymentStatusEnum status, String rejectionReason) {
this.status = status;
this.rejectionReason = rejectionReason;
}
}
I added a default constructor in above class and it worked.
Getting below error when redirecting to a view from backend
{
"timestamp": 1576568268085,
"path": "/api/dukandaar/v1/payment/callback/HDFC",
"status": 500,
"error": "Internal Server Error",
"message": "Could not resolve view with name 'get_redirect'."
}
My controller:
package com.turtlemint.verticals.commonverticals.common.controllers.v1;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.turtle.enums.VerticalEnum;
import com.turtlemint.verticals.commonverticals.common.constants.v1.Constants;
import com.turtlemint.verticals.commonverticals.common.constants.v1.ThymeConstants;
import com.turtlemint.verticals.commonverticals.common.containers.v1.mappers.PaymentCallbackDTO;
import com.turtlemint.verticals.commonverticals.common.containers.v1.mappers.PaymentRedirectRequestDTO;
import com.turtlemint.verticals.commonverticals.common.containers.v1.mappers.ReviewStatusUpdateRequestDTO;
import com.turtlemint.verticals.commonverticals.common.containers.v1.mappers.ReviewStatusUpdateResponseDTO;
import com.turtlemint.verticals.commonverticals.common.containers.v1.mappers.notification.share.ShareRequestMapper;
import com.turtlemint.verticals.commonverticals.common.enums.v1.ReviewPaymentStatusEnum;
import com.turtlemint.verticals.commonverticals.common.services.CommonServiceFactory;
import com.turtlemint.verticals.commonverticals.common.utils.v1.BrokerUtils;
import com.turtlemint.verticals.commonverticals.common.utils.v1.MapUtils;
import com.turtlemint.verticals.commonverticals.deviceprotection.services.v1.DeviceProtectionServiceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* #author praveenkamath
**/
#Controller
#RequestMapping("/api/{apiVertical}")
public class PaymentController {
private static final Logger LOG = LoggerFactory.getLogger(PaymentController.class);
private static final String REDIRECTION_PARAMS = "redirectionParams";
#Autowired
private CommonServiceFactory commonServiceFactory;
#Autowired
private DeviceProtectionServiceFactory deviceProtectionServiceFactory;
#GetMapping(value = "/v1/payment/callback/{insurer}")
public String processPayment(#PathVariable final String insurer, #PathVariable final String apiVertical, final Model model, final ServerHttpRequest serverRequest){
LOG.info("[processPayment] request received {} for insurer {}",serverRequest.getQueryParams(), insurer);
Map<String,Object> requestMap= MapUtils.convertMultiToRegularMap(serverRequest.getQueryParams());
VerticalEnum verticalEnum = VerticalEnum.getValue(apiVertical);
final String url = commonServiceFactory.getCheckoutService().processPayment(BrokerUtils.determineBroker(serverRequest.getURI().getHost()), requestMap, verticalEnum, insurer);
final Map<String, String> redirectionParams = new HashMap<>();
redirectionParams.put("url", url);
model.addAttribute(REDIRECTION_PARAMS, redirectionParams);
return ThymeConstants.REDIRECT_GET_TEMPLATE;
}
}
public class ThymeConstants {
private ThymeConstants() {}
public static final String REDIRECT_GET_TEMPLATE = "get_redirect";
}
My GET request:
https://<DOMAIN>/api/dukandaar/v1/payment/callback/HDFC?<query_params>
I comment out the CORS configuration and everything works fine.
I am still pulling my hair trying to find out the 2nd issue. Some help would be appreciated.
Finally found the solution for the 2nd issue. Had to override the thymeleaf configuration.
CorsConfiguration now looks like this:
package com.turtlemint.verticals.commonverticals.common.security.cors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
/**
* #author praveenkamath
**/
#Configuration
#EnableWebFlux
public class CorsConfiguration implements ApplicationContextAware, WebFluxConfigurer {
#Value("${cors.allowed.origins}")
private String[] corsSites;
#Value("${cors.enabled}")
private boolean isCorsEnabled;
private ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext context) {
this.ctx = context;
}
#Override
public void addCorsMappings(final CorsRegistry registry) {
if(isCorsEnabled) {
registry.addMapping("/api/device/**").allowedOrigins(corsSites).allowedMethods("PUT", "PATCH", "DELETE", "OPTIONS");
registry.addMapping("/api/sehat/**").allowedOrigins(corsSites).allowedMethods("PUT", "PATCH", "DELETE", "OPTIONS");
registry.addMapping("/api/dukandaar/**").allowedOrigins(corsSites).allowedMethods("PUT", "PATCH", "DELETE", "OPTIONS");
return;
} registry.addMapping("/**");
}
#Bean
SpringResourceTemplateResolver thymeleafTemplateResolver() {
final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(this.ctx);
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false);
resolver.setCheckExistence(false);
return resolver;
}
#Bean
ISpringWebFluxTemplateEngine thymeleafTemplateEngine() {
final SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();
templateEngine.setTemplateResolver(thymeleafTemplateResolver());
return templateEngine;
}
#Bean
ThymeleafReactiveViewResolver thymeleafChunkedAndDataDrivenViewResolver() {
final ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();
viewResolver.setTemplateEngine(thymeleafTemplateEngine());
viewResolver.setResponseMaxChunkSizeBytes(8192); // OUTPUT BUFFER size limit
return viewResolver;
}
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(thymeleafChunkedAndDataDrivenViewResolver());
}
}

spring security + rest doesn't work

I have configured all properties, but my app still loads without spring security as if it does not exist... Please help me, what I am doing wrong.
Here I get my rooms without auth with postman:
Here below are my classes:
SecurityConfiguration:
package com.vidaflo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
#Configuration
#EnableWebSecurity
#ComponentScan("com.vidaflo")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/room/**").hasRole("ADMIN")
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
SecurityInitializer:
package com.vidaflo.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
WebConfiguration:
package com.vidaflo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.vidaflo.controllers")
public class WebConfiguration extends WebMvcConfigurationSupport {
}
Tomcat embedded:
package com.vidaflo.server;
import com.vidaflo.config.ApplicationConfiguration;
import com.vidaflo.config.DatabaseConfiguration;
import com.vidaflo.config.SecurityConfiguration;
import com.vidaflo.config.WebConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
#Slf4j
public class Application {
private static final String APPLICATION_PROPERTIES = System.getProperty("app.properties");
private static final int DEFAULT_PORT = 8080;
private static final String DEFAULT_CONTEXT_PATH = "/app";
private AppProperties appProperties;
private AnnotationConfigWebApplicationContext ctx;
public static void main(String[] args) throws LifecycleException {
Application app = new Application(APPLICATION_PROPERTIES);
Server server = new TomcatServer(new Tomcat());
app.run(server);
}
public Application(String fieldName) {
loadProperties(fieldName);
}
public void run(Server server) {
initApplicationContext();
server.run(getConfig());
}
private void loadProperties(String fieldName) {
appProperties = new AppProperties();
appProperties.load(fieldName);
}
private void initApplicationContext() {
log.info("Initialize application context...");
ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SecurityConfiguration.class);
ctx.register(ApplicationConfiguration.class);
ctx.register(WebConfiguration.class);
ctx.register(DatabaseConfiguration.class);
ctx.getEnvironment()
.getPropertySources()
.addLast(new PropertiesPropertySource("applicationEnvironment", appProperties.getProperties()));
}
private ServerConfig getConfig() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setPort(appProperties.getPort(DEFAULT_PORT));
serverConfig.setContextPath(appProperties.getContextPath(DEFAULT_CONTEXT_PATH));
serverConfig.setServlet(getServlet());
return serverConfig;
}
private DispatcherServlet getServlet() {
return new DispatcherServlet(ctx);
}
}
Rest controller:
package com.vidaflo.controllers;
import com.vidaflo.dto.RoomDto;
import com.vidaflo.model.location.Room;
import com.vidaflo.repositories.LocationRepository;
import com.vidaflo.services.RoomService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
#RestController
public class RoomController {
#Autowired
private RoomService roomService;
#Autowired
private LocationRepository locationService;
#PostMapping("/room/save")
public String save(#RequestParam(name = "name") String name,
#RequestParam(name = "location_id") Long locationId) {
roomService.save(name, locationService.findOne(locationId));
return "room added";
}
#GetMapping("/room/all")
public List<RoomDto> findAll() {
return roomService.findAll().stream()
.map(this::toDto)
.collect(Collectors.toList());
}
private RoomDto toDto(Room room) {
return RoomDto.builder()
.id(room.getId())
.name(room.getName())
.build();
}
}
Please tell me if I should add additional details. I rly need help and I can't understand what I'm doing wrong.
Found an answer, we should manually add filter for spring security in tomcat embedded config like this:
FilterDef filterDef = new FilterDef();
filterDef.setFilterName("springSecurityFilterChain");
filterDef.setFilterClass("org.springframework.web.filter.DelegatingFilterProxy");
container.addFilterDef(filterDef);
FilterMap filterMapping = new FilterMap();
filterMapping.setFilterName("springSecurityFilterChain");
filterMapping.addURLPattern("/*");
container.addFilterMap(filterMapping);
Try to change role "ADMIN" "USER" to "ROLE_ADMIN" "ROLE_USER" in configureGlobalSecurity method and in the enum "Roles", but in configure method don't change.

Categories