Spring Security CORS Filter
We have added Spring Security
to our existing project.
From now on, we receive a 401 error No 'Access-Control-Allow-Origin' header is present on the requested resource
from our server.
This is because there is no header attached to the answer Access-Control-Allow-Origin
. To fix this, we added a custom filter that is chained Filter
before the exit filter, but the filter is not applied to our queries.
Our mistake:
XMLHttpRequest cannot load
http://localhost:8080/getKunden
. The requested resource does not have an "Access-Control-Allow-Origin" header. Originhttp://localhost:3000
is therefore not allowed. The response was HTTP 401 status code.
Our security configuration:
@EnableWebSecurity
@Configuration
@ComponentScan("com.company.praktikant")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilter filter;
@Override
public void configure(HttpSecurity http) throws Exception {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
source.registerCorsConfiguration("/**", config);
http.addFilterBefore(new MyFilter(), LogoutFilter.class).authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/*").permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
}
}
Our filter
@Component
public class MyFilter extends OncePerRequestFilter {
@Override
public void destroy() {
}
private String getAllowedDomainsRegex() {
return "individual / customized Regex";
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String origin = "http://localhost:3000";
response.addHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers",
"content-type, x-gwt-module-base, x-gwt-permutation, clientid, longpush");
filterChain.doFilter(request, response);
}
}
Our application
@SpringBootApplication
public class Application {
public static void main(String[] args) {
final ApplicationContext ctx = SpringApplication.run(Application.class, args);
final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(CORSConfig.class);
annotationConfigApplicationContext.refresh();
}
}
Our filter is registered from spring-boot:
2016-11-04 09: 19: 51.494 INFO 9704 --- [ost-startStop-1] osbwservlet.FilterRegistrationBean: Display filter: 'myFilter' to: [/ *]
Our generated filter chain:
2016-11-04 09: 19: 52.729 INFO 9704 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Create filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher @ 1, [org.springframework .secu rity.web.context.request.async.WebAsyncManagerIntegrationFilter @ 5d8c5a8a, org.spring framework.security.web.context.SecurityContextPersistenceFilter @ 7d6938f, org.springframework.security.web.header.HeaderWriterFilter@72aa8 9c, org.springframework. security.web.csrf.CsrfFilter @ 4af4df11, com.company.praktikant.MyFilter@5ba65db2 , org. springframework.security.web.authentication.logout.LogoutFilter @ 2330834f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter @ 396532d1, org.springframework. security.web.servletapi.SecurityContextHolderAwareRequestFilter @ 4fc0f1a2, org.springfram ework.security.web.authentication.AnonymousAuthenticationFilter @ 2357120f, o rg.springframework.security.web.session.Session @ ManagementFramework.security.web.session.Session @ManagementFramework. E xceptionTranslationFilter @ 4b8bf1fb, org.springfr amework.security.web.access.intercept.FilterSecurityInterceptor @ 42063cf1]
Answer: Answer headers
We tried the solution from spring, but it didn't work! The @CrossOrigin annotations in our controller didn't help either.
Edit 1:
Tried the solution from @Piotr Sołtysiak. The cors filter is not listed in the generated filter chain and we still get the same error.
2016-11-04 10: 22: 49.881 INFO 8820 --- [ost-startStop-1] ossweb.DefaultSecurityFilterChain: Create filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher @ 1, [org.springframework .secu rity.web.context.request.async.WebAsyncManagerIntegrationFilter @ 4c191377, org.spring framework.security.web.context.SecurityContextPersistenceFilter @ 28bad32a, org.springframework.security.web.header. HeaderWriterFilter @ 3c3ec668 , org.springframework.security.web.csrf.CsrfFilter @ 288460dd, org. springframework.security.web.authentication.logout.LogoutFilter @ 1c9cd096, org.springframework.s ecurity.web.authentication.UsernamePasswordAuthenticationFilter @3990c331, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter @ 1e8d4ac1, org.springfram ework.security.web.authentication.www.BasicAuthenticationFilter @ 2d61d2a4, org.sp ringframework.security.web.savedrequest.RequestCacheAwareFilter @ 380d9a9b, lderAwareRequestFilter @ abf2de3 org.springframework.security.web.servletapi.SecurityContextHo, org.springfram ework.security.web.authentication.AnonymousAuthenticationFilter @ 2a5c161b, o rg.springframework.security.web.session.SessionManagementFilter @ 3c1fd3e5, org .springframework.security .web.access.ExceptionTranslationFilter @ 3d7055ef, org.springframework. security.web.access.intercept.FilterSecurityInterceptor@5d27725a]
Btw we are using spring-security version 4.1.3.!
source to share
Ok, after more than two days of searching, we have finally fixed the issue. We removed all our filters and configurations and used these 5 lines of code in the application class instead.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
final ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:3000");
}
};
}
}
source to share
Since Spring Security 4.1, this is the correct way to make Spring CORS security support (also needed in Spring Boot 1.4 / 1.5):
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
and
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
http.cors();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request credentials mode is 'include'.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Do not do any of the following, which is the wrong way to solve the problem:
-
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
-
web.ignoring().antMatchers(HttpMethod.OPTIONS);
Link: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html
source to share
Since I had problems with other solutions (especially for it to work in all browsers, eg edge does not recognize "*" as a valid value for "Access-Control-Allow-Methods") I had to use a custom filter component which eventually worked for me and did exactly what I wanted to achieve.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods",
"ACL, CANCELUPLOAD, CHECKIN, CHECKOUT, COPY, DELETE, GET, HEAD, LOCK, MKCALENDAR, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, REPORT, SEARCH, UNCHECKOUT, UNLOCK, UPDATE, VERSION-CONTROL");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Key, Authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
public void init(FilterConfig filterConfig) {
// not needed
}
public void destroy() {
//not needed
}
}
source to share
-
You do not need:
@Configuration @ComponentScan("com.company.praktikant")
@EnableWebSecurity
is already there@Configuration
and I can't imagine why you put there@ComponentScan
. -
About the CORS filter, I would simply say:
@Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; }
Into the SecurityConfiguration class and remove the preference and configure the global methods. You don't need to set permissive organizations, headers and methods twice. Especially if you set different properties in your filter and spring security config :)
-
According to the above, your "MyFilter" class is redundant.
-
You can also remove those:
final AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(CORSConfig.class); annotationConfigApplicationContext.refresh();
From the application class.
-
In the end, a little advice - not related to the question. You don't want to put verbs in the URI. Instead,
http://localhost:8080/getKunden
you should use the HTTP GET method for the resourcehttp://localhost:8080/kunden
. You can learn RESTful API best practices here: http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
source to share
Using Spring Security in Spring Boot 2 to configure CORS globally (by enabling all development requests, for example), you can do the following:
@Bean
protected CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}
source to share
According to the CORS Filters documentation :
"Spring MVC provides fine-grained support for CORS configuration through annotations on controllers. However, when used with Spring Security, it is recommended to rely on the built-in CorsFilter , which must be ordered in front of the Spring Filter Security Chain."
Something like this will allow GET
access to /ajaxUri
:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AjaxCorsFilter extends CorsFilter {
public AjaxCorsFilter() {
super(configurationSource());
}
private static UrlBasedCorsConfigurationSource configurationSource() {
CorsConfiguration config = new CorsConfiguration();
// origins
config.addAllowedOrigin("*");
// when using ajax: withCredentials: true, we require exact origin match
config.setAllowCredentials(true);
// headers
config.addAllowedHeader("x-requested-with");
// methods
config.addAllowedMethod(HttpMethod.OPTIONS);
config.addAllowedMethod(HttpMethod.GET);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/startAsyncAuthorize", config);
source.registerCorsConfiguration("/ajaxUri", config);
return source;
}
}
Of course, your SpringSecurity configuration must provide access to the URI with the listed methods. See @ Hendy Irawan's answer.
source to share
In my case, I just added this class and used @EnableAutConfiguration
package com.package.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class SimpleCORSFilter extends GenericFilterBean {
/**
* The Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
logger.info("> doFilter");
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
//response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(req, resp);
logger.info("< doFilter");
}
}
source to share
In many places I see an answer that should add this code:
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
but in my case it throws an unexpected class type exception. corsFilter()
the bean corsFilter()
needs a type CorsFilter
, so I CorsFilter
changed these and put this bean definition in my config and now everything is fine.
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
source to share
For programs that are already deployed and cannot afford code changes (like add / update spring security), adding a simple proxy is one of the solutions: fooobar.com/questions/28291 / ...
source to share