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.
Related
I'm at a loss. I upgraded an application from Spring Boot 2.1 to 2.6 and from Wicket 8.0 to 9.6. I had two issues with circular-references that I fixed but now I get an infinite loop if I want to start the application with H2 database because of authenticate. And I'm not sure what is happening there.
So this is the part of the StackTrace that keeps repeating. IntelliJ cuts off the beginning, not sure what to do about that:
at com.xyz.ufa.app.TestUFSession.<init>(TestUFSession.java:15)
at jdk.internal.reflect.GeneratedConstructorAccessor102.newInstance(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at org.apache.wicket.authroles.authentication.AuthenticatedWebApplication.newSession(AuthenticatedWebApplication.java:108)
at org.apache.wicket.Application.fetchCreateAndSetSession(Application.java:1527)
at org.apache.wicket.Session.get(Session.java:194)
at org.apache.wicket.protocol.http.WebSession.get(WebSession.java:41)
at com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.security.SecureWebSession.authenticate(SecureWebSession.java:48)
at org.apache.wicket.authroles.authentication.AuthenticatedWebSession.signIn(AuthenticatedWebSession.java:66)
at com.xyz.ufa.app.TestUFSession.<init>(TestUFSession.java:15)
Here is the TestUFSession.class
import com.xyz.ufa.frontend.config.UFSession;
import org.apache.wicket.request.Request;
/**
* Helper session to login autmatically.
*
*/
public class TestUFSession extends UFSession {
public TestUFSession(Request request) {
super(request);
signIn("admin", "admin"); // this calls authenticate
}
}
And here the UFSession class
import com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.security.SecureWebSession;
import java.util.Locale;
import lombok.Getter;
import org.apache.wicket.request.Request;
#Getter
public class UFSession extends SecureWebSession {
private String username;
private Locale locale;
public UFSession(Request request) {
super(request);
locale = request.getLocale();
}
#Override
public void signOut() {
username = null;
super.signOut();
}
}
And here the WebSecurityConfiguration class
import com.xyz.uf.common.ApplicationProfile;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapUserDetailsService;
#Slf4j
#Configuration
#Import(LdapProperties.class)
#ComponentScan(basePackages = {"com.xyz.ufa.security.userinfo"})
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final String AUTHENTICATION_PROVIDER_BEAN_NAME = "authenticationProvider";
#Autowired
private LdapProperties ldapProperties;
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws AuthenticationServiceException {
try {
return super.authenticationManagerBean();
} catch (Exception e) {
log.error("Error in authenticationManagerBean", e);
throw new AuthenticationServiceException(e.getMessage(), e);
}
}
#Override
protected void configure(HttpSecurity httpSecurity) throws AuthenticationServiceException {
try {
httpSecurity
.csrf().disable()
.authorizeRequests()
.antMatchers("/*").permitAll()
.antMatchers("/restservice/**").hasAuthority(UFARole.TECHNICAL_ADMIN)
.and().httpBasic()
.and().logout().permitAll();
httpSecurity.headers().frameOptions().disable();
} catch (Exception e) {
throw new AuthenticationServiceException(String.format("Could not configure %s with csrf disabled and matching Pattern /*.", httpSecurity), e);
}
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
try {
AbstractLdapAuthenticationProvider authentProvider = (AbstractLdapAuthenticationProvider) getApplicationContext().getBean(AUTHENTICATION_PROVIDER_BEAN_NAME);
authentProvider.setAuthoritiesMapper(authoritiesMapper());
DaoAuthenticationConfigurer<AuthenticationManagerBuilder, LdapUserDetailsService> configurer = authenticationManagerBuilder
.authenticationProvider(authentProvider)
.userDetailsService(ldapUserDetailsService());
passwordEncoder.ifPresent(configurer::passwordEncoder);
} catch (Exception e) {
throw new AuthenticationServiceException("Could not configure authentication manager ", e);
}
}
#Bean
public LdapUserDetailsService ldapUserDetailsService() {
LdapUserDetailsService userDetailsService = new LdapUserDetailsService(userSearch(), ldapAuthoritiesPopulator());
return userDetailsService;
}
#Bean
public LdapUserSearch userSearch() {
return new FilterBasedLdapUserSearch(ldapProperties.getUserSearchBase(), ldapProperties.getUserSearchFilter(), contextSource());
}
#Bean
public GrantedAuthoritiesMapper authoritiesMapper() {
return new GrantAuthoritiesMapperWithEnvTag(ldapProperties.getEnv());
}
#Bean
public LdapContextSource contextSource() {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl(ldapProperties.getServerUrl());
ldapContextSource.setAnonymousReadOnly(true);
return ldapContextSource;
}
#Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
DefaultLdapAuthoritiesPopulator ldapAuthoritiesPopulator = new DefaultLdapAuthoritiesPopulator(contextSource(), ldapProperties.getGroupSearchBase());
ldapAuthoritiesPopulator.setGroupSearchFilter(ldapProperties.getGroupSearchFilter());
ldapAuthoritiesPopulator.setGroupRoleAttribute(ldapProperties.getGroupRoleAttribute());
ldapAuthoritiesPopulator.setRolePrefix("");
ldapAuthoritiesPopulator.setConvertToUpperCase(false);
return ldapAuthoritiesPopulator;
}
#Bean(name = AUTHENTICATION_PROVIDER_BEAN_NAME)
#Profile(value = { ApplicationProfile.Values.TEST, ApplicationProfile.Values.PROD })
public AuthenticationProvider activeDirectory() {
ActiveDirectoryLdapAuthenticationProvider authenticationProvider = new ActiveDirectoryLdapAuthenticationProvider("HRE.LOC", ldapProperties.getServerUrl());
authenticationProvider.setSearchFilter(ldapProperties.getUserSearchFilter());
return authenticationProvider;
}
#Bean(name = AUTHENTICATION_PROVIDER_BEAN_NAME)
#Profile(value = { ApplicationProfile.Values.DEFAULT, ApplicationProfile.Values.DEV })
public AuthenticationProvider defaultAuthenticationProvider() {
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(contextSource());
authenticator.setPasswordAttributeName("userPassword");
passwordEncoder.ifPresent(authenticator::setPasswordEncoder);
authenticator.setUserSearch(userSearch());
LdapAuthenticationProvider authenticationProvider = new LdapAuthenticationProvider(authenticator, ldapAuthoritiesPopulator());
return authenticationProvider;
}
/**
* This bean is optional and not available for some profiles. Password encoder is only required for embedded LDAP, for productive Active Directory it is not used
*/
#Bean("passwordEncoder")
#Profile(value = { ApplicationProfile.Values.DEFAULT, ApplicationProfile.Values.DEV })
public static PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Autowired(required = false)
#Qualifier("passwordEncoder")
private Optional<PasswordEncoder> passwordEncoder;
}
Profile used for test is default.
Anyone any ideas?
Edit: So Martin helped me to understand the problem, but I'm too dumb to fix it. TestUFApplication is instantiated here via ReflectionTestUtils
ยดยดยดยด
#SpringBootApplication
public class TestApplicationWithH2Database {
#Autowired
private UFyWicketWebApplication webApplication;
#Value("${test.autologin.active}")
private boolean testAutologinActive;
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(Application.class)
.run(args);
}
#PostConstruct
public void PostConstruct() {
if (testAutologinActive) {
ReflectionTestUtils.setField(webApplication, "sessionClass", TestUFSession.class);
}
}
}
I tried to make signIn() a method in TestUFSession and call it like that
TestUFSession testUFSession = (TestUFSession) ReflectionTestUtils.getField(webApplication, "sessionClass");
testUFSession.signIn();
but got a ClassCastException
You will need to call signIn("admin", "admin"); after instantiating the TestUFSession.
From the stacktrace we can see that com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.security.SecureWebSession.authenticate(SecureWebSession.java:48) tries to lookup the WebSession via its static #get() method and that leads to the infinite loop.
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]
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());
}
}
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).
I'm quite new to Spring and I have created a small webservice with Spring-boot, Hibernate and Swagger. Here is my HomeController:
package io.swagger.configuration;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Home redirection to swagger api documentation
*/
#Controller
public class HomeController {
#RequestMapping(value = "/")
public String index() {
System.out.println("swagger-ui.html");
return "redirect:swagger-ui.html";
}
}
Everything is working well, excepting I don't understand why when I have an Internal Server Error I get a status 200 with this body for example:
{
"timestamp": "2017-12-12T23:52:02.306+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "javax.persistence.PersistenceException",
"message": "org.hibernate.exception.ConstraintViolationException: could not execute statement",
"path": "/example/members/1031/subscriptions"
}
And there is the launcher :
package io.swagger;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#SpringBootApplication
#EnableSwagger2
#ComponentScan(basePackages = "io.swagger")
public class Swagger2SpringBoot extends SpringBootServletInitializer implements CommandLineRunner {
#Override
public void run(String... arg0) throws Exception {
if (arg0.length > 0 && arg0[0].equals("exitcode")) {
throw new ExitException();
}
}
public static void main(String[] args) throws Exception {
LogFactory.getLog(Swagger2SpringBoot.class).warn("test");
new SpringApplication(Swagger2SpringBoot.class).run(args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Swagger2SpringBoot.class);
}
class ExitException extends RuntimeException implements ExitCodeGenerator {
private static final long serialVersionUID = 1L;
#Override
public int getExitCode() {
return 10;
}
}
}
with the configuration :
package io.swagger;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
/**
* Make sure dates are serialised in
* ISO-8601 format instead as timestamps
*/
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonMessageConverter = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = jsonMessageConverter.getObjectMapper();
objectMapper.disable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
);
break;
}
}
}
}
Another one for Swagger:
package io.swagger.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
#Configuration
public class SwaggerDocumentationConfig {
ApiInfo apiInfo() {
return new ApiInfoBuilder().title("**** API").description("No description").license("").licenseUrl("")
.termsOfServiceUrl("www.****.com").version("1.0.0")
.contact(new Contact("", "", "****#****.com")).build();
}
#Bean
public Docket customImplementation() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("io.swagger.api")).build().apiInfo(apiInfo());
}
}
So why do I have this 200 status if I got a server error? At least I'd like to generate a status 500, 200 means everything was fine, and obviously it's not. Any idea ?
It appears I had implemented a custom error controller generating code from my swagger file, I deleted it and now everything is ok.