Skip to content

Instantly share code, notes, and snippets.

@eeichinger
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eeichinger/e54b488f5aba73cf6942 to your computer and use it in GitHub Desktop.
Save eeichinger/e54b488f5aba73cf6942 to your computer and use it in GitHub Desktop.
CORS Request Filter for debugging/testing purposes
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 );
}
}
# Global properties file
application.modulename = auth
spring.profiles.active = production, auth-stub, ignore-web-security
<?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>
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() );
}
}
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 );
}
}
<?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>
<!--
&lt;!&ndash; config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes &ndash;&gt;
<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