Last active
August 29, 2015 14:09
-
-
Save eeichinger/e54b488f5aba73cf6942 to your computer and use it in GitHub Desktop.
CORS Request Filter for debugging/testing purposes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.web; | |
import com.google.common.base.Strings; | |
import com.google.common.net.HttpHeaders; | |
import org.springframework.web.filter.OncePerRequestFilter; | |
import javax.servlet.FilterChain; | |
import javax.servlet.ServletException; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import java.io.IOException; | |
/** | |
* Follows the protocol as described in https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Simple_requests | |
* in order to allow CORS requests during development | |
*/ | |
public class AllowCORSRequestFilter extends OncePerRequestFilter { | |
@Override | |
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | |
if (!Strings.isNullOrEmpty( request.getHeader( "Origin" ) )) { | |
response.addHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) ); | |
response.addHeader( "Access-Control-Allow-Credentials", "true" ); // allow cookies | |
} | |
if ("OPTIONS".equalsIgnoreCase( request.getMethod() )) { | |
boolean isPreFlightRequest = false; | |
final String requestedHeaders = request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS ); | |
if (!Strings.isNullOrEmpty( requestedHeaders )) { | |
response.addHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestedHeaders ); | |
isPreFlightRequest = true; | |
} | |
final String requestedMethods = request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD ); | |
if (!Strings.isNullOrEmpty( requestedMethods )) { | |
response.addHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestedMethods ); | |
isPreFlightRequest = true; | |
} | |
// abort further execution | |
if (isPreFlightRequest) { | |
return; | |
} | |
} | |
filterChain.doFilter( request, response ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Global properties file | |
application.modulename = auth | |
spring.profiles.active = production, auth-stub, ignore-web-security |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?><configuration debug="true"> | |
<appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT"> | |
<encoder> | |
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | |
</encoder> | |
</appender> | |
<logger name="org.springframework" level="DEBUG" /> | |
<logger name="de.porsche" level="ALL" /> | |
<root level="INFO"> | |
<appender-ref ref="STDOUT"/> | |
</root> | |
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example; | |
import com.fasterxml.jackson.core.JsonParser; | |
import com.fasterxml.jackson.databind.DeserializationFeature; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.fasterxml.jackson.databind.SerializationFeature; | |
import com.fasterxml.jackson.datatype.joda.JodaModule; | |
import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer; | |
import com.fasterxml.jackson.datatype.joda.ser.JacksonJodaFormat; | |
import lombok.extern.slf4j.Slf4j; | |
import org.joda.time.DateTime; | |
import org.joda.time.format.DateTimeFormatter; | |
import org.joda.time.format.ISODateTimeFormat; | |
import org.springframework.beans.factory.BeanFactoryUtils; | |
import org.springframework.beans.factory.ListableBeanFactory; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.ComponentScan; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.annotation.Profile; | |
import org.springframework.core.OrderComparator; | |
import org.springframework.core.Ordered; | |
import org.springframework.core.annotation.AnnotationAwareOrderComparator; | |
import org.springframework.core.annotation.Order; | |
import org.springframework.http.converter.HttpMessageConverter; | |
import org.springframework.http.converter.StringHttpMessageConverter; | |
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; | |
import org.springframework.web.context.WebApplicationContext; | |
import org.springframework.web.filter.CompositeFilter; | |
import org.springframework.web.filter.RequestContextFilter; | |
import org.springframework.web.servlet.HandlerInterceptor; | |
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; | |
import org.springframework.web.servlet.config.annotation.EnableWebMvc; | |
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | |
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; | |
import javax.servlet.Filter; | |
import java.util.*; | |
/** | |
* @author Erich Eichinger | |
* @since 26/08/2013 | |
*/ | |
@Configuration | |
@ComponentScan(basePackageClasses = {MyController.class}) | |
@EnableWebMvc | |
@Slf4j | |
public class AuthWebConfiguration extends WebMvcConfigurerAdapter { | |
@Autowired | |
WebApplicationContext context; | |
@Override | |
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { | |
configurer.enable(); | |
} | |
@Bean | |
@Profile("ignore-web-security") | |
AllowCORSRequestFilter allowCORSRequestFilter() { | |
@Order(Ordered.HIGHEST_PRECEDENCE + 1) | |
class OrderedAllowCORSRequestFilter extends AllowCORSRequestFilter {} | |
return new OrderedAllowCORSRequestFilter(); | |
} | |
@Bean | |
RequestContextFilter requestContextFilter() { | |
@Order(Ordered.HIGHEST_PRECEDENCE + 2) | |
class OrderedRequestContextFilter extends RequestContextFilter {} | |
return new OrderedRequestContextFilter(); | |
} | |
@Bean | |
Filter servletFilterChain() { | |
final List<Filter> filters = findBeansOfTypeOrdered( context, Filter.class ); | |
for(Filter filter:filters) { | |
log.info("Loaded filter {}", filter.getClass()); | |
} | |
return new CompositeFilter() {{ | |
setFilters( filters ); | |
}}; | |
} | |
@Override | |
public void addInterceptors(InterceptorRegistry registry) { | |
// better use an annotation, see e.g. @ControllerAdvice and | |
// org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans | |
List<HandlerInterceptor> interceptors = findBeansOfTypeOrdered( context, HandlerInterceptor.class ); | |
for (HandlerInterceptor hi : interceptors) { | |
registry.addInterceptor( hi ); | |
} | |
} | |
static <TResult> List<TResult> findBeansOfTypeOrdered(ListableBeanFactory context, Class<TResult> clazz) { | |
Map<String, TResult> matchingBeans = | |
BeanFactoryUtils.beansOfTypeIncludingAncestors( context, clazz, false, false ); | |
List<TResult> interceptors = new ArrayList<>(); | |
if (!matchingBeans.isEmpty()) { | |
interceptors.addAll( matchingBeans.values() ); | |
AnnotationAwareOrderComparator.sort( interceptors ); | |
} | |
//noinspection unchecked | |
return interceptors; | |
} | |
/** | |
* Create a new & configured {@link com.fasterxml.jackson.databind.ObjectMapper} instance | |
* <p/> | |
* This configuration ensures that both jul's {@link java.util.Date} and Joda's {@link org.joda.time.DateTime} | |
* are rendered as ISO8601 without milliseconds ("yyyy-mm-ddThh:mm:ssZ") | |
* <p/> | |
* <b>Note: </b>This factory method is intentionally exposed as static public so that tests can reuse the same | |
* ObjectMapper configuration as it is used in prod | |
* | |
* @return a configured & ready-to-use {@link com.fasterxml.jackson.databind.ObjectMapper} instance | |
*/ | |
public static ObjectMapper createObjectMapper() { | |
ObjectMapper om = new ObjectMapper(); | |
JodaModule module = new JodaModule(); | |
om.registerModule( module ); | |
om.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS ); | |
// this config is necessary so that java.util.Date and | |
final DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTimeNoMillis().withZoneUTC(); | |
module.addSerializer( DateTime.class, new DateTimeSerializer().withFormat( new JacksonJodaFormat( dateTimeFormatter ) ) ); | |
om.setDateFormat( new com.fasterxml.jackson.databind.util.ISO8601DateFormat() ); | |
// TODO: make configurable - only needed for development | |
om.enable( SerializationFeature.INDENT_OUTPUT ); | |
om.disable( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES ); | |
// invalid json, but makes json test data embedded in tests so much easier to read and write | |
om.configure( JsonParser.Feature.ALLOW_SINGLE_QUOTES, true ); | |
om.configure( JsonParser.Feature.ALLOW_COMMENTS, true ); | |
return om; | |
} | |
private MappingJackson2HttpMessageConverter jsonConverter() { | |
// prefix every json response to prevent security vulnerabilities (see https://docs.angularjs.org/api/ng/service/$http#json-vulnerability-protection) | |
return new MappingJackson2HttpMessageConverter( createObjectMapper() ) {{ | |
// TODO: need to customise Spring's jsonPath result matcher to strip the prefix before evaluating | |
// setJsonPrefix( ")]}',\n" ); | |
}}; | |
} | |
@Override | |
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { | |
converters.add( jsonConverter() ); | |
converters.add( new StringHttpMessageConverter() ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example; | |
import lombok.Getter; | |
import lombok.SneakyThrows; | |
import lombok.extern.slf4j.Slf4j; | |
import org.springframework.context.ApplicationContextInitializer; | |
import org.springframework.context.ConfigurableApplicationContext; | |
import org.springframework.core.env.ConfigurableEnvironment; | |
import org.springframework.core.env.PropertiesPropertySource; | |
import org.springframework.core.io.support.ResourcePropertySource; | |
import org.springframework.util.Assert; | |
import java.io.IOException; | |
import java.util.Properties; | |
@Slf4j | |
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { | |
public static final String ENVIRONMENT_DEFAULT = "environment.default"; | |
public static final String ENVIRONMENT = "environment"; | |
@Getter | |
Properties propertyOverrides = new Properties( ); | |
@Override | |
@SneakyThrows | |
public void initialize(ConfigurableApplicationContext applicationContext) { | |
addApplicationPropertySourcesToEnvironment( applicationContext.getEnvironment() ); | |
} | |
public void addApplicationPropertySourcesToEnvironment(ConfigurableEnvironment environment) throws IOException { | |
if (!propertyOverrides.isEmpty()) { | |
environment.getPropertySources().addFirst( new PropertiesPropertySource( "propertyOverrides", propertyOverrides ) ); | |
} | |
String defaultPropertiesLocation = "classpath:/settings/application-default.properties"; | |
String defaultEnvironmentName = environment.getProperty( ENVIRONMENT_DEFAULT ); | |
String environmentName = environment.getProperty( ENVIRONMENT, defaultEnvironmentName ); | |
Assert.hasText( environmentName, "neither environment nor environment.default variables are set" ); | |
final String environmentPropertiesLocation = "classpath:/settings/"+environmentName+"/application.properties"; | |
try { | |
ResourcePropertySource localPropertySource = new ResourcePropertySource( environmentPropertiesLocation ); | |
environment.getPropertySources().addLast( localPropertySource ); | |
log.info( "environment propertysource added {}", environmentPropertiesLocation ); | |
} catch (IOException e) { | |
log.error( "environment propertysource couldn't be loaded: {}", e.getMessage()); | |
} | |
environment.getPropertySources().addLast( new ResourcePropertySource( defaultPropertiesLocation ) ); | |
log.info( "default propertysource added {}", defaultPropertiesLocation ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<web-app xmlns="http://java.sun.com/xml/ns/javaee" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee | |
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" | |
version="3.0"> | |
<filter> | |
<filter-name>delegatingFilterProxy</filter-name> | |
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> | |
<init-param> | |
<param-name>targetBeanName</param-name> | |
<param-value>servletFilterChain</param-value> | |
</init-param> | |
</filter> | |
<filter-mapping> | |
<filter-name>delegatingFilterProxy</filter-name> | |
<servlet-name>dispatcher</servlet-name> | |
</filter-mapping> | |
<listener> | |
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> | |
</listener> | |
<context-param> | |
<param-name>contextClass</param-name> | |
<param-value> | |
org.springframework.web.context.support.AnnotationConfigWebApplicationContext | |
</param-value> | |
</context-param> | |
<context-param> | |
<param-name>contextConfigLocation</param-name> | |
<param-value> | |
com.example.MyServicesWebConfiguration,com.example.MyServicesBackendConfiguration | |
</param-value> | |
</context-param> | |
<context-param> | |
<param-name>contextInitializerClasses</param-name> | |
<param-value> | |
com.example.MyApplicationContextInitializer | |
</param-value> | |
</context-param> | |
<servlet> | |
<servlet-name>dispatcher</servlet-name> | |
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> | |
<init-param> | |
<param-name>contextClass</param-name> | |
<param-value> | |
org.springframework.web.context.support.AnnotationConfigWebApplicationContext | |
</param-value> | |
</init-param> | |
<!-- | |
<!– config locations must consist of one or more comma- or space-delimited | |
and fully-qualified @Configuration classes –> | |
<init-param> | |
<param-name>contextConfigLocation</param-name> | |
<param-value> | |
com.example.MyServicesWebConfiguration,com.example.MyServicesBackendConfiguration | |
</param-value> | |
</init-param> | |
<init-param> | |
<param-name>contextInitializerClasses</param-name> | |
<param-value> | |
com.example.MyApplicationContextInitializer | |
</param-value> | |
</init-param> | |
<init-param> | |
<param-name>dispatchOptionsRequest</param-name> | |
<param-value>true</param-value> | |
</init-param> | |
--> | |
<load-on-startup>1</load-on-startup> | |
</servlet> | |
<servlet-mapping> | |
<servlet-name>dispatcher</servlet-name> | |
<url-pattern>/</url-pattern> | |
</servlet-mapping> | |
</web-app> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment