Customizing CORS Filtering at Runtime
Let's make CORS work for you.
Join the DZone community and get the full member experience.
Join For FreeCross-origin resource sharing (CORS) is a mechanism that controls the AJAX calls to your resources outside the current origin. In some cases, you need to allow some origins to make these kinds of calls. For example, if you have a SAAS based product, there will be some clients who are connecting to your API and making AJAX calls.
In Spring, there are several ways to check the origin, method, headers, etc., of incoming requests.
You can use WebSecurityConfigurerAdapter
in Spring Security, and WebMvcConfigurer
, @CrossOrigin
annotation, CorsFilter
in Spring MVC.
We are providing a SaaS-based payment and wallet application. We're serving our payment page via JavaScript, and so many CORS requests come from our clients. In our case, we needed to change allowed origins dynamically because some merchants may be added, deleted, or updated.
Our tech stack in this project consists of Spring Boot, Spring Cloud, Netflix OSS, and our gateway is Netflix Zuul.
I thought filtering CORS requests in our gateway was the best choice, and I started to review the solutions which Spring provided us, and I selected creating a CorsFilter bean in one of our Configuration classes. Our configuration is like below.
x
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(clientService.getClientOrigins());
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
After making some tests, I noticed that allowed origins can not be updated after the bean is created, as you guess. Because CorsFilter's configSource
attribute is set while the bean is created and can not be changed after.
I reviewed the CorsFilter
class and see what it does inside. It simply extends OncePerRequestFilter
, sets the CorsConfigurationSource
object in its constructor, and does all the filtering in doFilterInternal
method with the help of DefaultCorsProcessor
.
I decided to write our CorsFilter which extends OncePerRequestFilter
, uses DefaultCorsProcessor
to process the CORS requests, does the same thing as CorsFilter
does in doFilterInternal
method, and creates CorsConfiguration at runtime. Also, I needed to create a new service and added it as a dependency to get the origins dynamically from the data source. I created OriginService class, which gets the origins of the clients from our data source. The main difference of our custom cors filter is creating CorsConfiguration at runtime by getting the origins via OriginService.
Our CustomCorsFilter class is like below.
x
public class CustomCorsFilter extends OncePerRequestFilter {
OriginService originService;
private final CorsProcessor processor = new DefaultCorsProcessor();
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
List<String> origins = originService.getOrigins();
config.setAllowedOrigins(origins);
config.setAllowedMethods(List.of("OPTIONS", "HEAD", "GET", "POST"));
config.addAllowedHeader("*");
source.registerCorsConfiguration("/**", config);
CorsConfiguration corsConfiguration = source.getCorsConfiguration(request);
boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
if (!isValid || CorsUtils.isPreFlightRequest(request)) {
return;
}
filterChain.doFilter(request, response);
}
}
Our CorsFilter is doing exactly what we want. When clients' origins are changed, CorsConfiguration is changed and Cors filtering starts to be done for the new origin list.
This is our solution for our problem, there may be some different and simpler solutions for this case. But our solution is working fine. The class may be changed for a very flexible configuration. It depends on your needs.
Opinions expressed by DZone contributors are their own.
Comments